This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
Shadertoy.com — это сайт, где собраны пользовательские GL-шейдеры. Это отличный источник шейдерного кода и вдохновения. В этом учебнике мы возьмём шейдер из Shadertoy и заставим его работать в Defold. Предполагается базовое понимание шейдеров. Если вам нужно освежить тему, хорошим началом будет руководство по Shader.
В качестве примера мы будем использовать шейдер Star Nest от Pablo Andrioli (пользователь “Kali” на Shadertoy). Это полностью процедурный фрагментный шейдер на математической чёрной магии, который рисует очень эффектное звёздное поле.

Шейдер занимает всего 65 строк довольно сложного GLSL-кода, но не беспокойтесь. Мы будем относиться к нему как к чёрному ящику, который делает своё дело на основе нескольких простых входных параметров. Наша задача — изменить шейдер так, чтобы он взаимодействовал с Defold, а не с Shadertoy.
Шейдер Star Nest — это чистый фрагментный шейдер, поэтому нам нужен лишь объект, который он будет текстурировать. Вариантов несколько: sprite, tilemap, GUI или model. В этом учебнике мы будем использовать простую 3D-модель. Причина в том, что так мы легко можем превратить рендеринг модели в полноэкранный эффект, что полезно, например, для постобработки.
Начнём с создания квадратной плоскости в Blender (или любой другой программе для 3D-моделирования). Для удобства 4 вершины должны иметь координаты -1 и 1 по оси X и -1 и 1 по оси Y. В Blender по умолчанию ось Z направлена вверх, поэтому нужно повернуть меш на 90° вокруг оси X. Также убедитесь, что для меша созданы корректные UV-координаты. В Blender выберите меш, перейдите в Edit Mode, затем выполните Mesh ▸ UV unwrap... ▸ Unwrap.
Blender — бесплатная 3D-программа с открытым исходным кодом, её можно скачать с blender.org.

quad.gltf из builtins/assets/meshes.Модель должна появиться в редакторе сцены, но она будет полностью чёрной. Это потому, что материал ещё не задан:

Создайте новый файл материала star-nest.material, вершинный шейдер star-nest.vp и фрагментный шейдер star-nest.fp:
star-nest.vp.star-nest.fp.view_proj” (сокращение от “view projection”).CONSTANT_TYPE_VIEWPROJ.Добавьте тег “tile” в Tags. Это нужно, чтобы квадрат включался в проход рендеринга, когда рисуются спрайты и тайлы.

Откройте файл вершинного шейдера star-nest.vp. Он должен содержать следующий код. Оставьте его без изменений.
// star-nest.vp
uniform mediump mat4 view_proj;
// positions are in world space
attribute mediump vec4 position;
attribute mediump vec2 texcoord0;
varying mediump vec2 var_texcoord0;
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
Откройте файл фрагментного шейдера star-nest.fp и измените код так, чтобы цвет фрагмента задавался на основе X и Y координат UV (var_texcoord0). Мы делаем это, чтобы убедиться, что модель настроена правильно:
// star-nest.fp
varying mediump vec2 var_texcoord0;
void main()
{
gl_FragColor = vec4(var_texcoord0.xy, 0.0, 1.0);
}
Теперь редактор должен отрисовывать модель новым шейдером, и мы сможем ясно увидеть, верны ли UV-координаты: нижний левый угол должен быть чёрным (0, 0, 0), верхний левый — зелёным (0, 1, 0), верхний правый — жёлтым (1, 1, 0), а нижний правый — красным (1, 0, 0):

Теперь всё готово, чтобы заняться собственно кодом шейдера. Сначала посмотрим на оригинал. Он состоит из нескольких частей:

Строки 5–18 задают набор констант. Их можно оставить как есть.
Строки 21 и 63 содержат входные экранные координаты фрагмента (in vec2 fragCoord) и выходной цвет фрагмента (out vec4 fragColor).
В Defold входные текстурные координаты передаются из вершинного шейдера как UV-координаты (в диапазоне 0–1) через varying-переменную var_texcoord0. Выходной цвет фрагмента задаётся во встроенной переменной gl_FragColor.
Строки 23–27 задают размеры текстуры, направление движения и масштабированное время. Разрешение viewport/texture передаётся в шейдер как uniform vec3 iResolution. Шейдер вычисляет UV-подобные координаты с правильным aspect ratio на основе координат фрагмента и разрешения. Также выполняется небольшое смещение, чтобы кадрирование выглядело лучше.
В версии для Defold эти вычисления нужно изменить так, чтобы использовать UV-координаты из var_texcoord0.
Здесь же задаётся время. Оно передаётся в шейдер как uniform float iGlobalTime. Defold в данный момент не поддерживает uniform типа float, поэтому нам придётся передавать время через vec4.
Строки 29–39 задают вращение объёмного рендеринга, на которое влияет положение мыши. Координаты мыши передаются в шейдер как uniform vec4 iMouse.
В этом учебнике мы пропустим ввод мыши.
Строки 41–62 — это ядро шейдера. Этот код можно оставить без изменений.
Если пройтись по описанным выше разделам и внести необходимые изменения, получится следующий шейдер. Он немного приведён в порядок для лучшей читаемости. Отличия между версиями Defold и Shadertoy отмечены:
// Star Nest by Pablo Román Andrioli
// This content is under the MIT License.
#define iterations 17
#define formuparam 0.53
#define volsteps 20
#define stepsize 0.1
#define zoom 0.800
#define tile 0.850
#define speed 0.010
#define brightness 0.0015
#define darkmatter 0.300
#define distfading 0.730
#define saturation 0.850
varying mediump vec2 var_texcoord0; // <1>
void main() // <2>
{
// get coords and direction
vec2 res = vec2(1.0, 1.0); // <3>
vec2 uv = var_texcoord0.xy * res.xy - 0.5;
vec3 dir = vec3(uv * zoom, 1.0);
float time = 0.0; // <4>
float a1=0.5; // <5>
float a2=0.8;
mat2 rot1=mat2(cos(a1),sin(a1),-sin(a1),cos(a1));
mat2 rot2=mat2(cos(a2),sin(a2),-sin(a2),cos(a2));
dir.xz*=rot1;
dir.xy*=rot2;
vec3 from = vec3(1.0, 0.5, 0.5);
from += vec3(time * 2.0, time, -2.0);
from.xz *= rot1;
from.xy *= rot2;
//volumetric rendering
float s = 0.1, fade = 1.0;
vec3 v = vec3(0.0);
for(int r = 0; r < volsteps; r++) {
vec3 p = from + s * dir * 0.5;
// tiling fold
p = abs(vec3(tile) - mod(p, vec3(tile * 2.0)));
float pa, a = pa = 0.0;
for (int i=0; i < iterations; i++) {
// the magic formula
p = abs(p) / dot(p, p) - formuparam;
// absolute sum of average change
a += abs(length(p) - pa);
pa = length(p);
}
//dark matter
float dm = max(0.0, darkmatter - a * a * 0.001);
a *= a * a;
// dark matter, don't render near
if(r > 6) fade *= 1.0 - dm;
v += fade;
// coloring based on distance
v += vec3(s, s * s, s * s * s * s) * a * brightness * fade;
fade *= distfading;
s += stepsize;
}
// color adjust
v = mix(vec3(length(v)), v, saturation);
gl_FragColor = vec4(v * 0.01, 1.0); // <6>
}
var_texcoord0 с UV-координатами. Нам нужно его объявить.void mainImage(out vec4 fragColor, in vec2 fragCoord). В Defold функция main() не получает параметров. Поэтому вместо этого мы читаем varying var_texcoord0 и пишем результат в gl_FragColor.vec2 = vec2(1.0, 1.0);. Если бы модель была прямоугольной, размером 1280⨉720, мы бы вместо этого задали vec2 res = vec2(1.78, 1.0); и умножали UV-координаты на него, чтобы получить правильное соотношение сторон.time устанавливается в ноль. На следующем этапе мы добавим время.iMouse. Обратите внимание, что расчёты поворота всё равно сохраняются, чтобы уменьшить визуальную симметрию объёмного рендеринга.Сохраните файл фрагментного шейдера. Теперь модель должна красиво отображаться в редакторе сцены со звёздным полем:

Последний кусочек пазла — добавление времени, чтобы звёзды двигались. Чтобы передать время в шейдер, нам нужна шейдерная константа, uniform. Чтобы добавить новую константу:
CONSTANT_TYPE_USER. Оставьте компоненты x, y, z и w равными 0.
Теперь нужно изменить код шейдера, чтобы он объявлял и использовал новую константу:
...
varying mediump vec2 var_texcoord0;
uniform lowp vec4 time; // <1>
void main()
{
//get coords and direction
vec2 res = vec2(2.0, 1.0);
vec2 uv = var_texcoord0.xy * res.xy - 0.5;
vec3 dir = vec3(uv * zoom, 1.0);
float time = time.x * speed + 0.25; // <2>
...
vec4 с именем “time”. Для него достаточно точности lowp (низкая точность).x uniform-переменной времени и используем его для вычисления значения времени.Последний шаг — передать значение времени в шейдер:
star-nest.script.function init(self)
self.t = 0 -- <1>
end
function update(self, dt)
self.t = self.t + dt -- <2>
go.set("#model", "time", vmath.vector4(self.t, 0, 0, 0)) -- <3>
end
t в компоненте скрипта (self) и инициализируем его нулём.self.t на количество секунд, прошедших с предыдущего кадра. Это значение доступно в параметре dt (delta time) и равно 1/60 (update() вызывается 60 раз в секунду).vector4, поэтому для времени мы используем компонент x.В завершение добавьте star-nest.script как компонент script к игровому объекту “star-nest”:

И всё. Готово!
Хорошее продолжение в качестве упражнения — добавить в шейдер исходный ввод движения мыши. Это должно быть довольно просто, если вы понимаете, как обрабатывать ввод.
Приятной работы с Defold!