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. Передбачається, що ви маєте базове розуміння шейдерів. Якщо потрібно ознайомитися з темою, почніть із посібника з шейдерів.
Ми використаємо шейдер Star Nest, який створив Pablo Andrioli (користувач «Kali» на Shadertoy). Це суто процедурний фрагментний шейдер, що за допомогою математичної чорної магії створює надзвичайно ефектне зоряне поле.

Шейдер містить лише 65 рядків досить складного коду GLSL, але не хвилюйтеся. Ми розглядатимемо його як чорну скриньку, що виконує свою роботу на основі кількох простих вхідних даних. Наше завдання — змінити шейдер так, щоб він взаємодіяв із Defold замість Shadertoy.
Star Nest — це суто фрагментний шейдер, тож нам потрібен лише об’єкт, який він текстуруватиме. Є кілька варіантів: спрайт, карта плиток, GUI або модель. У цьому уроці ми використаємо просту 3D-модель. Причина в тому, що рендеринг моделі легко перетворити на повноекранний ефект — це потрібно, наприклад, для візуальної постобробки.
Можемо почати з порожнього проєкту.

Можна використати вбудовану сітку quad.gltf з builtins/assets/meshes.
За бажанням можна також створити сітку квадратної площини в 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 у властивості Mesh.model.material.Модель має з’явитися в редакторі сцени, але вона відображається повністю чорною. Це тому, що для неї ще не задано текстуру:

star-nest.material: натисніть Right Mouse Button на папці main у панелі Assets, виберіть New->Material і назвіть його star-nest.
star-nest.vp і програму фрагментного шейдера star-nest.fp:star-nest.vp у Vertex Program.star-nest.fp у Fragment Program.view_proj” і задайте тип Viewproj (від «view projection» — проєкція виду).
Відкрийте файл програми вершинного шейдера star-nest.vp. Він має містити такий код:
#version 140
// positions are in world space
in vec4 position;
in vec2 texcoord0;
out vec2 var_texcoord0;
uniform vertex_inputs
{
mat4 view_proj;
};
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
Відкрийте файл програми фрагментного шейдера star-nest.fp і змініть код так, щоб колір фрагмента визначався на основі складових X і Y UV-координат (var_texcoord0). Так ми перевіримо, чи правильно налаштовано модель:
#version 140
in vec2 var_texcoord0;
out vec4 out_fragColor;
void main()
{
out_fragColor = vec4(var_texcoord0.xy, 0.0, 1.0);
}
У компоненті моделі ігрового об’єкта star-nest у main.collection задайте для властивості Material щойно створений матеріал star-nest.
Тепер редактор має відображати модель із новим шейдером, і ми зможемо чітко побачити, чи правильні UV-координати: нижній лівий кут має бути чорним (0, 0, 0), верхній лівий — зеленим (0, 1, 0), верхній правий — жовтим (1, 1, 0), а нижній правий — червоним (1, 0, 0):

Тепер можна запустити проєкт (Project->Build або сполучення клавіш Ctrl/Cmd + B), але ми побачимо чорний екран (ну, майже — можливо, за винятком одного крихітного пікселя в нижньому лівому куті). Це тому, що камери немає, а стандартний скрипт рендерингу використовує простий резервний варіант, який показує величезний 2D-простір, тоді як наша модель розташована в позиції (0,0,0) і має ширину лише 1.
Додаймо ігровий об’єкт із компонентом камери, щоб визначити, що ми бачитимемо в грі.
camera у позиції (0,0,1). (Важливо задати координату Z рівною 1, щоб цей ігровий об’єкт перебував перед нашою моделлю, оскільки зараз, у стандартному 2D-налаштуванні, вісь Z спрямована до нас).Camera, і ви побачите попередній перегляд камери з нашим чотирикутником усередині. За такого розташування стандартні властивості вдало підходять: нічого змінювати не потрібно, і ми вже маємо бачити правильний результат. Є лише один виняток — нам не потрібна така велика піраміда видимості камери, тож можна зменшити Far Z до 2.
За бажанням можна змінити тип камери, установивши Orthographic Projection у true, а потім також задати для Orthographic Zoom значення приблизно 600. Але в цьому разі співвідношення сторін не налаштовуватиметься автоматично, тому модель не заповнюватиме екран.
Тепер, коли все готово, почнімо працювати з кодом самого шейдера. Спочатку погляньмо на оригінальний код. Він складається з кількох частин:

Ми використаємо сучасний конвеєр із GLSL версії 140 — для цього оголосимо версію на початку файлу за допомогою #version 140.
Рядки 5–18 визначають низку констант. Їх можна залишити без змін. Це звичайні константи GLSL, які не залежать від особливостей Shadertoy чи Defold.
Рядки 21 і 63 містять вхідні текстурні координати X та Y фрагмента в екранному просторі (in vec2 fragCoord) і вихідний колір фрагмента (out vec4 fragColor).
Defold передає текстурні координати з вершинного шейдера до фрагментного через інтерпольовану змінну у вигляді UV-координат (у діапазоні 0–1). У нашому вершинному шейдері її оголошено з кваліфікатором out:
// in star-nest.vp
out vec2 var_texcoord0;
У фрагментному шейдері те саме значення отримується з кваліфікатором in:
// in star-nest.fp
in vec2 var_texcoord0;
Далі в GLSL 140 явно оголошуємо вихідну змінну фрагмента з кваліфікатором out:
// in star-nest.fp
out vec4 out_fragColor;
Отже, там, де оригінальний код Shadertoy записує значення у fragColor, наш шейдер Defold записує його в out_fragColor.
Рядки 23–27 задають розміри текстури, напрямок руху й масштабований час. У Shadertoy шейдер отримує позицію пікселя через fragCoord, а роздільна здатність області перегляду/текстури передається в шейдер як uniform vec3 iResolution. Шейдер обчислює координати на зразок UV із правильним співвідношенням сторін на основі координат фрагмента й роздільної здатності. Також застосовується певне зміщення відносно роздільної здатності, щоб отримати кращу композицію кадру.
У Defold ми починаємо не з координат пікселів. Натомість уже отримуємо нормалізовані UV-координати з вершинного шейдера через var_texcoord0. Ці координати охоплюють діапазон від 0.0 до 1.0 по всій поверхні чотирикутника, що рендериться.
У версії для Defold потрібно змінити ці обчислення, щоб використовувати UV-координати з var_texcoord0.
Типове перетворення має такий вигляд:
vec2 uv = var_texcoord0.xy;
uv = uv * 2.0 - 1.0;
uv.x *= aspect;
Точне значення aspect залежить від налаштування прикладу. Якщо ефект рендериться на повноекранному чотирикутнику з відомим розміром екрана, для уроку співвідношення сторін можна жорстко задати в коді. Якщо ефект має підтримувати довільні розміри вікна, передавайте роздільну здатність як фрагментну константу й розмістіть її всередині uniform-блока GLSL 140.
Тут також задається час. Він передається в шейдер як uniform float iGlobalTime. Defold (починаючи з версії 1.12.3) надає шейдерам час через спеціальну константу Time, яку ми й використаємо.
У сучасному Defold uniform-змінні, що не належать до непрозорих типів (non-opaque), оголошують усередині uniform-блоків. У фрагментному шейдері оголосимо це так:
uniform fragment_inputs
{
vec4 time;
};
Потім у star-nest.material додамо Fragment Constant з іменем time і встановимо для неї тип Time.
Далі це значення можна використовувати так:
float iGlobalTime = time.x;
float dt = time.y;
де time.x — це час від запуску рушія, а time.y — час, що минув із попереднього кадру.
Рядки 29–39 задають поворот об’ємного рендерингу, на який впливає позиція миші. Координати миші передаються в шейдер як uniform vec4 iMouse.
У цьому уроці ми пропустимо введення з миші.
Рядки 41–62 — це основна частина шейдера. Цей код можна залишити без змін.
Після опрацювання наведених вище частин і внесення потрібних змін отримуємо такий код шейдера. Його трохи впорядковано для кращої читабельності. Відмінності між версіями для Defold і Shadertoy позначено:
#version 140 // <1>
// 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
in vec2 var_texcoord0; // <2>
out vec4 out_fragColor; // <3>
uniform fragment_inputs // <4>
{
vec4 time;
};
void main() // <5>
{
// get coords and direction
vec2 res = vec2(1.0, 1.0); // <6>
vec2 uv = var_texcoord0.xy * res.xy - 0.5;
vec3 dir = vec3(uv * zoom, 1.0);
float iGlobalTime = time.x; // <7>
float shader_time = iGlobalTime * speed;
float a1 = 0.5; // <8>
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(shader_time * 2.0, shader_time, -2.0);
from.xz *= rot1;
from.xy *= rot2;
// volumetric rendering
float s = 0.1;
float 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 = 0.0;
float a = 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);
out_fragColor = vec4(v * 0.01, 1.0); // <9>
}
Збережіть програму фрагментного шейдера. Тепер модель має бути гарно текстурована зоряним полем у редакторі сцени й під час виконання:

Остання частина завдання — додати час, щоб зорі почали рухатися. Defold (починаючи з версії 1.12.3) надає його автоматично через фрагментну константу типу Time.
Time.
І це все! Ми вже обробляємо цей time у фрагментному шейдері. Готово!
Цікавою вправою для продовження буде додати до шейдера оригінальне введення руху миші. Потрібно створити нову Fragment Constant, цього разу типу User, та оновлювати її в on_input у скрипті, що виявляє рух миші, за допомогою функції go.set(), задаючи новій константі координати введення.
Приємної роботи з Defold!