Еще один мой проект в Godot 3 с использованием разных шейдеров, все шейдеры довольно простые.
Ссылка для запуска на itch.io, требуется WebGL2.
Исходный код проекта на github, проект graphic_demo_3d.
Статья разбита на такие разделы:
- Статические текстуры, генерация и плавная смена для эффекта освещения.
- Сглаживание и мультисэмплинг.
- Про используемые шейдеры и их логику.
- Немного про логику скриптов.
О чем это:
В статье описание используемых шейдеров, и ссылки на первоисточник там где использовался сторонний материал.
Скачать готовые сборки для Linux и Windows ссылка на itch.io.
Исходный код проекта ссылка на github.
Для разработки использовался официальный Godot 3.2.1, без модификаций.
Статические текстуры
Ray-tracer и убирание шума:
Для генерации всех текстур освещения, как не сложно догадаться, использовался raytracer. В основном это Blender-cycles.
В качестве деноизера я использовал этот код шейдера glslSmartDeNoise.
Разные цвета освещения:
В каталоге проекта graphic_demo_3d/game/models/objects/arc/
есть папки orig+цвет
где все текстуры освещения для этой геометрии(меша).
И эти текстуры синхронно меняются через логику скрипта arc.gd
Панорамы:
Для создания текстур панорам, которые используются как текстура отражения на сферах, делал cubemap снимок и трансформировал в панораму, в Godot такое сделать быстрее чем в Blender.
Как пример многократные отражения друг друга у фиолетовых сфер — это просто несколько раз сделать снимок и применить прошлый снимок к окружающим шарам, в итоге многократные отражения.
Сглаживание и мультисэмплинг
Мультисэмплинг(MSAA) на весь экран очень дорогой, и в этом проекте он не используется.
На видео выше шейдер рисует линии анимации используя smoothstep
, и для сглаживания радиус smoothstep
увеличивается в зависимости от положения фрагмента шейдера, на этом видеоролике справа этаже анимация без сглаживания.
На фиолетовых сферах используется мультисэмплинг текстуры, код шейдера:
const int AA=4;
for (int mx = 0; mx < AA; mx++)
for (int nx = 0 ; nx < AA; nx++) {
vec2 o = vec2(float(mx), float(nx)) / float(AA) - 0.5;
o=tuv+o*baa;
tot+=texture(p_o,o).rgb;
}
tot /= float(AA * AA);
Где baa
это удаление фрагмента от камеры.
Это работает достаточно хорошо для этого конкретного случая.
В этом случае mipmaps нельзя использовать, так как эта панорама и сфера(размер пикселей не равномерный), кубемап на сферу тоже не выйдет натянуть без потери mipmaps…
Еще один случай, где нужно сглаживание — когда с удалением яркие объекты стали слишком маленькими, и пикселей не хватает чтоб их нарисовать:
Справа рисуется без фокусов честная геометрия(меш), и с удалением объект сверху на темной пирамиде становится слишком маленький и начинает прыгать по пикселям.
Я просто взял точки(GL_POINT) и поставил их внутрь этих мелких объектов, и с удалением точка сохраняет свой размер и заменяет пропадающий объект. Вариант с точками на левой части видео.
Про используемые шейдеры и их логику
Шейдер outline для одного объекта
Корректный способ такое сделать описан по ссылке в разделе Silhouette Effect.
В Godot 3 нельзя, без модификации кода движка, записывать ни ID материала ни какие-либо дополнительные данные в процессе создания кадра.
Поэтому единственный способ это использовать дополнительную framebuffer(Viewport) где еще раз рендерить все нужные объекты, и пока объектов мало можно не волноваться о производительности, но это плохой способ в любом случае, ждем Godot 4.
В моем случае я поставил еще одну камеру на сцену, и эта камера видит только один объект — волка, поэтому всегда контур будет, камера в Viewport разрешение которого в 2 раза меньше текущего экрана.
Шейдер для освещения и теней
В Godot довольно мало возможностей влиять на освещение и тени, и некоторые из заявленных возможностей в Godot 3 просто не реализованы и перенесены в Godot 4.
Код этого шейдера в файле box/floor.shader
void light() {
vec3 col=texture(floor_img,UV).rgb;
if(col.r>0.001){
float dif = clamp(dot(NORMAL,LIGHT), 0.0, 1.0);
vec3 hal = normalize(VIEW+LIGHT);
float spe = pow(clamp(dot(hal, NORMAL), 0.0, 1.0), 32.0);
vec3 ta=ATTENUATION;
vec2 tuv=UV*vec2(15.,10.)*50.;
vec2 ddx = dFdx(tuv);
vec2 ddy = dFdy(tuv);
float tx=filteredSquares(tuv, ddx, ddy);
float tx2=filteredCrosses(tuv, ddx, ddy);
DIFFUSE_LIGHT = dif*co*spe*4.* ta*col;
DIFFUSE_LIGHT = clamp(DIFFUSE_LIGHT,0.,1.);
SPECULAR_LIGHT = mix(ta,vec3(0.)+DIFFUSE_LIGHT,clamp(dif*spe*5.,0.,1.))*tx+0.5*((1.-tx2)*(clamp(1.-ta*5.,0.,1.)));
SPECULAR_LIGHT = clamp(SPECULAR_LIGHT*col,0.,1.);
}
else{
DIFFUSE_LIGHT=vec3(0.);
SPECULAR_LIGHT=vec3(0.);
}
}
Логика такая что, берется значение текстуры floor_img
чтобы ограничить тень и свет в пределах белого прямоугольника текстуры, функции filteredSquares
и filteredCrosses
от iquilezles.org.
Эти функции выводят свой узор(патерн), и этот узор накладывается в зависимости от ATTENUATION
, и дальше разделение цвета SPECULAR_LIGHT от DIFFUSE_LIGHT чтоб один сделать белым другой желтого цвета.
Depth — контур у фиолетового щита
Вся логика работы с глубиной(depth) отсюда godot_force_shield_shader
Шейдер шума на основе этого ссылка на shadertoy
Сам код шейдера в файле shield.shader.
Этот эффект рисуется двумя слоями, внутренним и внешним, внутренний слой после внешнего чтобы не влиять на SCREEN_TEXTURE
на основе которого строится смещение(волны) снаружи.
Area lights
Используя материал Real-Time Polygonal-Light Shading with Linearly Transformed Cosines этот код на shadertoy ссылка, для прямоугольных источников.
И код этого шейдера для сферы и трубы ссылка на shadertoy.
Код этих шейдеров в этих файлах area_lights/floor.shader и area_lights2/floor_area.shader
Никакой сложности их переноса в Godot нет, все так же как во многих других движках, где этот эффект уже встроенный.
Немного про логику скриптов
Навигация модели волка — использует встроенную в Godot возможность Navigation, логика скопирована из туториала Godot 3D Navigation Mesh. Волк бежит к камере по клику правой кнопкой мыши.
Пирамиды-лампы вокруг поля сделаны частицами, не сильно портят производительность.
Смена цвета через шейдер, определение расстояния до камеры.
Анимация квадратов на полу — используется отдельный framebuffer(Viewport) разрешения 20x20 пикселей.
В нем храниться состояние анимации всех квадратов, и передается положение камеры и волка, двух персонажей.
Бесконечное поле:
Перемещение игрока в симметричную позицию на другой стороне при достижении определенной позиции. (да есть немного заметный лаг, я уже не исправлял это)
Используются две сторонние модели с анимациями.
Взяты из sketchfab, ссылки на оригинал — модель волка и модель робота.
Производительность — я запускал на Nvidia 750 и Vega 8 картах, работает на 60fps в 1080p разрешении, думаю производительность в пределах нормы. Проверил на Windows и Linux — работает. В WebGL2 также работает очень хорошо.
Конец статьи
Кто прочитал — спасибо Вам что уделили столько времени этому тексту.
Coverbacker
OpenSource это святое) Спасибо за статью!