Еще один мой проект в Godot 3 с использованием разных шейдеров, все шейдеры довольно простые.


Ссылка для запуска на itch.io, требуется WebGL2.
Исходный код проекта на github, проект graphic_demo_3d.


Статья разбита на такие разделы:


  1. Статические текстуры, генерация и плавная смена для эффекта освещения.
  2. Сглаживание и мультисэмплинг.
  3. Про используемые шейдеры и их логику.
  4. Немного про логику скриптов.

О чем это:


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


Скачать готовые сборки для 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 также работает очень хорошо.


Конец статьи


Кто прочитал — спасибо Вам что уделили столько времени этому тексту.