Рисуем на модели в Unity3d
Хотите добавить в вашу игру немного вандализма, в смысле уличного искусства, или добавить мейкап в редактор персонажей? Тогда вам должно понравиться.
Почему не цпу и SetPixels?
Во-первых: это очень, очень медленно. Ладно бы только перебирать массивы на цпу, но чтобы загрузить результат в видеопамять нужно использовать Apply, который сразу же угашивает фпс в ноль (если просто вызывать Apply в апдейте, на 4096 текстуре, на моей 3060 фпс просаживается до 15, и это без каких либо вычислений)
Во-вторых: непонятно как рисовать кистью если у модели есть несколько UV островов.
Что нам понадобится:
Кисть, холст, немного говнокода и костыли(куда же без них)
Кисть
Вот она:
Ну, на самом деле, от куба на нужна только world2object матрица которую мы скормим в шейдер.
Ставим наш кубик через рейкаст на модельку, поворачиваем по нормали и передаём в шейдер.
Также, в шейдере нам понадобятся текстура кисти и её цвет.
Чтобы нанести текстуру с кисти на холст, нам нужно её спроецировать на нашу модельку. Для этого нужно получить координаты пикселей в мировом пространстве.
Осталось использовать использовать эти координаты для проецирования нашей модельки на кист, а потом прочесть текстуру из этих координат.
Чтобы этого не происходило, просто не будем воздействовать на те пиксели которые находятся за пределами куба.
Холст
Кисть проецируется, а как мазки попадут в текстуру холста?
Нужно рисовать модель не как обычно, умножая координаты на MVP матрицу, а использовать UV координаты вместо позиций вертексов, тогда будет выводиться развёрнутая версия меша.
Осталось только отрисовать модельку в рендертекстуру и использовать эту текстуру как основу для следующего мазка.
Вообще будет работать и с одной текстурой, но писать в ту же текстуру из которой читаем- не очень хорошая идея, а если попробовать это сделать через Blit, то он просто будет спамить ошибку.
Поэтому делаем даблбуфферинг, создаём две текстуры и флаг который будет менять их местами.
Чтобы сэкономить память можно использовать формат со сжатием, буфер глубины не нужен т.к. модель развёрнута на плоскость и никаких пересечений не будет. Мипмапы не нужны, их можно будет сгенерировать когда мы закончим с рисованием, и например захотим сохранять холст как обычную текстуру (почему не нужны, думаю станет понятно в разделе костылей)
Говнокод
Отрисовывать модель мы будем не каждый кадр, а когда кисть пройдёт некое-то расстояние. Этим мы: сэкономим ресурсы, избавимся от неприятного эффекта, когда полупрозрачная область кисти, со временем становится сплошным цветом и сможем добиться полезного арт эффекта.
Чтобы отрисовать модельку с нужным шейдером есть несколько вариантов, я выбрал самый ленивый- создать дополнительную камеру, назначить ей маску слоёв в которой будет только моделька, на которой мы рисуем, но не будет курсора-кубика. И отрисовать её через RenderWithShader, который применяет назначений шейдер ко всему, что видит камера.
Т.к. текстуры у нас две, нужно назначать текстуру фронтбуфера на материале, который выводит финальную версию модели.
Костыли
Чтобы убрать швы, мы сначала зальём острова сплошным цветом с единичной альфой.
А потом закрасим прозрачные пиксели, цветом непрозрачных соседей.
Массив сдвигов в вершинном шейдере:
Закрашиваем пиксель если у него есть непрозрачные соседи:
Рисуем текстуру холста через Blit, применяя материал с шейдером краёв:
Цвет не совпадает из-за бага, при котором, если в проекте используется линейное цветовое пространство, к рендер текстуре применяется гамма коррекция, даже если текстура помечена как использующая линейное пространство.
Чтобы избавиться от этого эффекта, нужно применить обратную коррекцию в шейдере, который рисует финальный вариант модели.
Результат
Исходник проекта
Теги:
Окей, прикольно