В этой теме я покажу вам, как рисовать плавные линии, как в Cocos2dx.

Эта проблема

Любой, кто достаточно опытен в компьютерной графике, знает, что рисовать линии сложно! Это может быть особенно сложно для WebGL и OpenGL ES на мобильных платформах. Чтобы добиться в этом успеха, вам нужно как-то разобраться с наложением имен и с тем фактом, что OpenGL не может рисовать кривые произвольной толщины.

Решением этой проблемы может быть реализация такого алгоритма тесселяции (тесселяция - это, по сути, умный способ представления линии набором треугольников):



Или использовать один из существующих движков рендеринга, который реализует его за вас, например:



В нативной разработке мы можем использовать Skia, который реализует хитрое решение AA для путей, но когда дело касается игрового движка Cocos2dx, мы можем заметить отсутствие готовых решений. Эти вопросы неоднократно поднимались на форуме cocos: один, два, три, четыре, и т. Д.. Но ни одно из предложенных решений у меня не сработало, поэтому некоторое время назад я написал свое собственное. Вы можете частично отследить мои исследования после этого из этой темы на форуме cocos. На данный момент пост собрал более 3,6 тысяч просмотров, так что я думаю, что проблема довольно частая.

DrawNode

Стандартным DrawNode мы можем нарисовать что-то вроде этого:

node = DrawNode::create(9);
node->drawCardinalSpline(pts, tension, segments, Color4F::RED);
for (int i = 0; i < pts->count(); ++i) {
    node->drawPoint(pts->getControlPointAtIndex(i), 10, Color4F::BLUE);
}

Это изображение имеет следующие артефакты:

  1. Границы ступенчатой ​​кривой (сглаживание)
  2. Некоторые прорезают отверстия внутри линии.

Изменение количества сегментов или «рисование, затем масштаб» может устранить лишь небольшую часть всех артефактов, в основном потому, что проблема заключается не в точном сглаживании краев линии, а в правильном представлении ее с помощью набора треугольников - тесселяции.

Треугольник

Во-первых, давайте расширим стандартный класс DrawNode дополнительным методом рисования треугольника:

Этот метод отличается от оригинального, который позволял рисовать только монохромные треугольники:

void DrawNode::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, const Color4F &color) {...}

Реализация очень похожа на исходный код, просто передайте аргументы:

Почему это важно для наших целей? Потому что, если мы назначим разные цвета для разных вершин, OpenGL предоставит цветовую интерполяцию для нашего треугольника:

И это также работает для значения альфа-канала (непрозрачности). Так что с помощью только этого метода мы уже можем пойти довольно далеко. Мы можем избавиться от перьев на линии, если нарисуем такие треугольники:

Изменяя значение альфа, мы можем сделать градиент от полноцветного до полностью прозрачного по краю линии. Используя только этот метод, я смог добиться следующих результатов (снимок экрана из приложения для Android):

Эта кривая была нарисована путем установки координат и цветов для каждой вершины треугольника. Это выглядит солидно, но действительно ли мне нужен движок, если я рисую треугольники сам?

Сегмент

Итак, я продолжил свое исследование того, как кокосы рисуют предметы, и после некоторых экспериментов я обнаружил единственное, что у кокосов рисует странно гладко - сегмент:

Как видите, он нарисован из 6 треугольников, и что делает края его линий настолько гладкими, так это эта линия фрагментного шейдера:

gl_FragColor = v_color*smoothstep(0.0, 0.1, 1.0 - length(v_texcoord));

когда DrawNode устанавливает смещение текстуры на drawSegment.

Кардинальные сплайны

Мы можем повторно использовать такой классный примитив внутри основных сплайнов:

Я получил код от DrawNode::drawCardinalSpline, исправил ошибку в математике кардинальных сплайнов, которая, по-видимому, существует с начала DrawNode,, и начал рисовать сегменты сегментами, а не какими-то резкими поли-примитивами. И вот что у меня есть:

Если вы увеличите масштаб этих изображений, вы увидите, что при большой ширине мы получили хорошие результаты, но при тонких линиях все еще есть некоторые артефакты.

Мозаика

Для таких случаев я реализовал простой алгоритм тесселяции:

Я начал с отрезка линии:

а затем отрегулируйте стыки линий:

Я использовал 4 треугольника для каждого сегмента линии и установил непрозрачность вершин границы на ноль. Такая схема плохо работает с широкими линиями - для этого нам нужно больше слоев треугольников, но, к счастью, мне это нужно только для тонких линий:

И объединив оба подхода

void AwesomeNode::drawACardinalSpline(PointArray *config, float tension, unsigned int segments,float w, const Color4F &color) {

    auto *vertices = calculateVertices(config, tension, segments);
    if (w < LINE_SIZE_THRESHOLD) {
        tessellation(vertices, segments, w, color);
    } else {
        for (int i = 2; i <= segments + 1; ++i) {
            drawSegment(vertices[i - 2], vertices[i - 1], w / 2, color);
        }
    }

    CC_SAFE_DELETE_ARRAY(vertices);
}

у нас будут результаты, которые вы можете увидеть в заголовке этого сообщения.

Окончательное решение

Эти эксперименты привели к созданию библиотеки под названием AwesomeNode, которую вы можете найти здесь:



Вам просто нужно скопировать и вставить AwesomeNode.h и AwesomeNode.cpp в свой проект на основе cocos2d-x, и все готово для рисования сверхплавных линий! (Не забудьте добавить AwesomeNode.cpp в LOCAL_SRC_FILES раздел на Android.mk на Android).

Если у вас есть какие-либо вопросы, я хотел бы ответить на них здесь, или вы можете открыть вопрос или отправить мне PR на GitHub.