This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this manual in English
Programy shaderów są podstawą renderowania grafiki. To programy napisane w języku podobnym do C, zwanym GLSL (GL Shading Language), które uruchamia sprzęt graficzny, aby wykonywać operacje na danych 3D bazowych (wierzchołkach) albo na pikselach, które trafiają na ekran (czyli na „fragmentach”). Shaderów używa się do rysowania sprite’ów, oświetlania modeli 3D, tworzenia pełnoekranowych efektów postprocessingu i do wielu, wielu innych rzeczy.
Ta instrukcja opisuje, jak potok renderowania Defold współpracuje z shaderami GPU. Aby tworzyć shadery dla swoich zasobów, musisz też rozumieć pojęcie materiałów oraz to, jak działa potok renderowania.
Specyfikacje OpenGL ES 2.0 (OpenGL for Embedded Systems) oraz OpenGL ES Shading Language znajdziesz w Khronos OpenGL Registry.
Zwróć uwagę, że na komputerach stacjonarnych można pisać shadery z użyciem funkcji niedostępnych w OpenGL ES 2.0. Sterownik twojej karty graficznej może bez problemu skompilować i uruchomić kod shaderów, który nie będzie działał na urządzeniach mobilnych.
Wejściem shadera wierzchołków są dane wierzchołków (w postaci attributes) oraz stałe (uniforms). Typowymi stałymi są macierze potrzebne do przekształcania i projekcji pozycji wierzchołka do przestrzeni ekranu.
Wyjściem shadera wierzchołków jest obliczona pozycja wierzchołka na ekranie (gl_Position). Można też przekazywać dane z shadera wierzchołków do shadera fragmentów za pomocą zmiennych varying.
Wejściem shadera fragmentów są stałe (uniforms) oraz wszystkie zmienne varying ustawione przez shader wierzchołków.
Wyjściem shadera fragmentów jest wartość koloru dla konkretnego fragmentu (gl_FragColor).
Wejściem shadera obliczeniowego są bufory stałych (uniforms), obrazy tekstur (image2D), samplery (sampler2D) i bufory przechowywania (buffer).
Wyjście shadera obliczeniowego nie jest jawnie zdefiniowane. W przeciwieństwie do shaderów wierzchołków i fragmentów nie trzeba generować żadnego konkretnego rodzaju wyjścia. Ponieważ shadery obliczeniowe są ogólne, to programista decyduje, jaki rodzaj wyniku ma wygenerować shader obliczeniowy.
Gdy model jest umieszczany w świecie gry, jego lokalne współrzędne wierzchołków trzeba przekształcić do współrzędnych świata. To przekształcenie wykonuje macierz transformacji świata, która określa, jakie przemieszczenie, obrót i skalowanie należy zastosować do wierzchołków modelu, aby poprawnie umieścić go w układzie współrzędnych świata gry.


position i texcoord0.position i texcoord0.position, textcoord0 i color.position, texcoord0 i color.position, texcoord0 i normal.position, texcoord0, face_color, outline_color i shadow_color.uniform w programie shadera. Uniformy samplera dodaje się do sekcji Samplers materiału, a następnie deklaruje jako uniform w programie shadera. Macierze potrzebne do wykonywania przekształceń wierzchołków w shaderze wierzchołków są dostępne jako stałe:
CONSTANT_TYPE_WORLD to macierz świata, która mapuje lokalną przestrzeń współrzędnych obiektu do przestrzeni świata.CONSTANT_TYPE_VIEW to macierz widoku, która mapuje przestrzeń świata do przestrzeni kamery.CONSTANT_TYPE_PROJECTION to macierz projekcji, która mapuje przestrzeń kamery do przestrzeni ekranu.world * view, view * projection oraz world * view * projection.CONSTANT_TYPE_USER to stała typu vec4, której możesz używać dowolnie.Instrukcja do materiałów wyjaśnia, jak określać stałe.
sampler2D pobiera próbki z dwuwymiarowej tekstury obrazu.sampler2DArray pobiera próbki z dwuwymiarowej tekstury tablicowej. Najczęściej używa się tego w atlasach wielostronicowych.samplerCube pobiera próbki z sześciennej tekstury cubemap złożonej z 6 obrazów.image2D wczytuje (i potencjalnie zapisuje) dane tekstury do obiektu obrazu. Najczęściej używa się tego w shaderach obliczeniowych do przechowywania danych.Samplera możesz używać tylko w funkcjach wyszukiwania tekstur z biblioteki standardowej GLSL. Instrukcja do materiałów wyjaśnia, jak określać ustawienia samplerów.

Mapa UV jest zwykle generowana w programie do modelowania 3D i przechowywana w siatce. Współrzędne tekstury dla każdego wierzchołka są przekazywane do shadera wierzchołków jako atrybut. Następnie zmienna varying służy do wyznaczenia współrzędnej UV dla każdego fragmentu przez interpolację z wartości wierzchołków.

Na przykład ustawienie varying na wartość koloru RGB vec3 w każdym rogu trójkąta spowoduje interpolację kolorów na całym kształcie. Podobnie ustawienie współrzędnych odczytu mapy tekstur (czyli współrzędnych UV) dla każdego wierzchołka w prostokącie pozwala shaderowi fragmentów odczytać wartości koloru tekstury dla całego obszaru kształtu.

Ponieważ silnik Defold obsługuje wiele platform i interfejsów API grafiki, pisanie shaderów działających wszędzie musi być dla twórców proste. Potok zasobów realizuje to głównie na dwa sposoby, dalej nazywane shader pipelines:
Od Defold 1.9.2 zaleca się pisać shadery korzystające z nowego potoku. Aby to osiągnąć, większość shaderów trzeba przepisać do wersji co najmniej 140 (OpenGL 3.1). Aby przenieść shader, upewnij się, że spełnione są następujące wymagania:
Umieść #version 140 na początku shadera:
#version 140
Tak wybierany jest potok shaderów w procesie budowania, dlatego nadal można używać starych shaderów. Jeśli nie zostanie znaleziony preprocesor wersji, Defold przełączy się na starszy potok.
W shaderach wierzchołków zastąp słowo kluczowe attribute przez in:
// zamiast:
// attribute vec4 position;
// użyj:
in vec4 position;
Uwaga: Shadery fragmentów (i shadery obliczeniowe) nie przyjmują żadnych wejść wierzchołków.
W shaderach wierzchołków zmienne varying powinny mieć prefiks out. W shaderach fragmentów zmienne varying stają się in:
// W shaderze wierzchołków, zamiast:
// varying vec4 var_color;
// użyj:
out vec4 var_color;
// W shaderze fragmentów, zamiast:
// varying vec4 var_color;
// użyj:
in vec4 var_color;
Nieprzezroczyste typy uniformów (samplery, obrazy, atomiki, SSBO) nie wymagają żadnej migracji, można ich używać tak jak dotychczas:
uniform sampler2D my_texture;
uniform image2D my_image;
Dla nieprzezroczystych typów uniformów trzeba je umieścić w uniform block. Blok uniformów to po prostu kolekcja zmiennych uniform, deklarowana za pomocą słowa kluczowego uniform:
uniform vertex_inputs
{
mat4 mtx_world;
mat4 mtx_proj;
mat4 mtx_view;
mat4 mtx_normal;
...
};
void main()
{
// Pojedyncze elementy bloku uniformów można używać bezpośrednio
gl_Position = mtx_proj * mtx_view * mtx_world * vec4(position, 1.0);
}
Wszystkie elementy bloku uniformów są udostępniane materiałom i komponentom jako pojedyncze stałe. Do używania buforów stałych renderowania ani go.set i go.get nie jest potrzebna żadna migracja.
W shaderach fragmentów gl_FragColor jest przestarzały od wersji 140. Zamiast tego użyj out:
// zamiast:
// gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// użyj:
out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
}
Konkretne funkcje pobierania próbek tekstur, takie jak texture2D i texture2DArray, już nie istnieją. Zamiast nich użyj po prostu funkcji texture:
uniform sampler2D my_texture;
uniform sampler2DArray my_texture_array;
// zamiast:
// vec4 sampler_2d = texture2D(my_texture, uv);
// vec4 sampler_2d_array = texture2DArray(my_texture_array, vec3(uv, slice));
// użyj:
vec4 sampler_2d = texture(my_texture, uv);
vec4 sampler_2d_array = texture(my_texture_array, vec3(uv, slice));
Wcześniej jawne ustawienie precyzji dla zmiennych, wejść, wyjść i podobnych elementów było wymagane, aby zachować zgodność z kontekstami OpenGL ES. Nie jest to już potrzebne, ponieważ precyzja jest teraz ustawiana automatycznie na platformach, które to obsługują.
Na koniec, jako przykład łączący wszystkie te zasady, poniżej pokazano wbudowane shadery sprite’a przekonwertowane do nowego formatu:
#version 140
uniform vx_uniforms
{
mat4 view_proj;
};
// pozycje są w przestrzeni świata
in vec4 position;
in vec2 texcoord0;
out vec2 var_texcoord0;
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
#version 140
in vec2 var_texcoord0;
out vec4 color_out;
uniform sampler2D texture_sampler;
uniform fs_uniforms
{
vec4 tint;
};
void main()
{
// Premnożenie alfa, ponieważ wszystkie tekstury używane w czasie działania już tak są zapisane
vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
color_out = texture(texture_sampler, var_texcoord0.xy) * tint_pm;
}
Shadery w Defold obsługują dołączanie kodu źródłowego z plików w projekcie, które mają rozszerzenie .glsl. Aby dołączyć plik GLSL z poziomu shadera, użyj dyrektywy #include z cudzysłowami albo nawiasami ostrymi. Dołączane pliki muszą mieć ścieżkę względną względem projektu albo ścieżkę względną względem pliku, który wykonuje dołączenie:
// W pliku /main/my-shader.fp
// Ścieżka bezwzględna
#include "/main/my-snippet.glsl"
// Plik znajduje się w tym samym folderze
#include "my-snippet.glsl"
// Plik znajduje się w podfolderze na tym samym poziomie co 'my-shader'
#include "sub-folder/my-snippet.glsl"
// Plik znajduje się w podfolderze katalogu nadrzędnego, tj. /some-other-folder/my-snippet.glsl
#include "../some-other-folder/my-snippet.glsl"
// Plik znajduje się w katalogu nadrzędnym, tj. /root-level-snippet.glsl
#include "../root-level-snippet.glsl"
Do sposobu działania dołączeń obowiązują pewne zastrzeżenia:
/.const float #include "my-float-name.glsl" = 1.0 nie zadziała.Fragmenty kodu mogą same dołączać inne pliki .glsl, co oznacza, że ostatecznie wygenerowany shader może zawierać ten sam kod kilka razy. W zależności od zawartości tych plików może to prowadzić do problemów z kompilacją, ponieważ te same symbole są zadeklarowane więcej niż raz. Aby temu zapobiec, możesz użyć osłon nagłówkowych (header guards), czyli rozwiązania znanego z wielu języków programowania. Przykład:
// W my-shader.vs
#include "math-functions.glsl"
#include "pi.glsl"
// W math-functions.glsl
#include "pi.glsl"
// W pi.glsl
const float PI = 3.14159265359;
W tym przykładzie stała PI zostanie zdefiniowana dwa razy, co spowoduje błędy kompilatora podczas uruchamiania projektu. Zamiast tego należy zabezpieczyć zawartość za pomocą osłon nagłówkowych:
// W pi.glsl
#ifndef PI_GLSL_H
#define PI_GLSL_H
const float PI = 3.14159265359;
#endif // PI_GLSL_H
Kod z pi.glsl zostanie rozwinięty dwa razy w my-shader.vs, ale ponieważ został opakowany osłonami nagłówkowymi, symbol PI zostanie zdefiniowany tylko raz i shader skompiluje się poprawnie.
Nie zawsze jest to jednak konieczne, zależnie od przypadku użycia. Jeśli chcesz ponownie wykorzystać kod lokalnie, wewnątrz funkcji albo gdzieś indziej, gdzie nie potrzebujesz globalnej dostępności wartości w kodzie shadera, prawdopodobnie nie powinieneś używać osłon nagłówkowych. Przykład:
// W red-color.glsl
vec3 my_red_color = vec3(1.0, 0.0, 0.0);
// W my-shader.fp
vec3 get_red_color()
{
#include "red-color.glsl"
return my_red_color;
}
vec3 get_red_color_inverted()
{
#include "red-color.glsl"
return 1.0 - my_red_color;
}
Gdy shadery są renderowane w widoku edytora Defold, dostępna jest definicja preprocesora EDITOR. Pozwala to pisać kod shaderów, który zachowuje się inaczej podczas działania w edytorze niż w rzeczywistym silniku gry.
Jest to szczególnie przydatne do:
Użyj dyrektywy preprocesora #ifdef EDITOR, aby warunkowo kompilować kod, który ma działać tylko w edytorze:
#ifdef EDITOR
// Ten kod wykona się tylko wtedy, gdy shader jest renderowany w edytorze Defold
color_out = vec4(1.0, 0.0, 1.0, 1.0); // kolor magenta do podglądu w edytorze
#else
// Ten kod wykona się podczas uruchamiania gry
color_out = texture(texture_sampler, var_texcoord0) * tint_pm;
#endif
Zanim dane tworzone dla gry trafią na ekran, przechodzą przez serię etapów:

Wszystkie komponenty wizualne (sprite’y, węzły GUI, efekty cząsteczkowe i modele) składają się z wierzchołków, czyli punktów w świecie 3D opisujących kształt komponentu. Zaletą tego podejścia jest możliwość oglądania kształtu z dowolnego kąta i z dowolnej odległości. Zadaniem programu shadera wierzchołków jest wzięcie pojedynczego wierzchołka i przekształcenie go do pozycji w oknie widoku, tak aby kształt mógł pojawić się na ekranie. Dla kształtu z 4 wierzchołkami program shadera wierzchołków uruchamia się 4 razy, każdorazowo równolegle.

Wejściem programu jest pozycja wierzchołka (oraz inne dane atrybutów powiązane z tym wierzchołkiem), a wyjściem jest nowa pozycja wierzchołka (gl_Position) oraz wszystkie zmienne varying, które mają zostać zinterpolowane dla każdego fragmentu.
Najprostszy program shadera wierzchołków po prostu ustawia pozycję wyjściową na wierzchołek zerowy (co nie jest zbyt przydatne):
void main()
{
gl_Position = vec4(0.0,0.0,0.0,1.0);
}
Bardziej kompletnym przykładem jest wbudowany shader wierzchołków sprite’a:
-- sprite.vp
uniform mediump mat4 view_proj; -- [1]
attribute mediump vec4 position; -- [2]
attribute mediump vec2 texcoord0;
varying mediump vec2 var_texcoord0; -- [3]
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0); -- [4]
var_texcoord0 = texcoord0; -- [5]
}
uniform zawierająca pomnożone macierze widoku i projekcji.position jest już przekształcone do przestrzeni świata. texcoord0 zawiera współrzędną UV dla wierzchołka.varying. Ta zmienna będzie interpolowana dla każdego fragmentu pomiędzy wartościami ustawionymi dla każdego wierzchołka i zostanie przekazana do shadera fragmentów.gl_Position zostaje ustawione na pozycję wyjściową bieżącego wierzchołka w przestrzeni projekcji. Ta wartość ma 4 składowe: x, y, z i w. Składowa w służy do obliczania interpolacji z korekcją perspektywy. Zwykle ma wartość 1.0 dla każdego wierzchołka przed zastosowaniem jakiejkolwiek macierzy przekształcenia.Po cieniowaniu wierzchołków ustalana jest widoczna na ekranie postać komponentu: generowane są i rasteryzowane prymitywy, czyli sprzęt graficzny dzieli każdy kształt na fragmenty albo piksele. Następnie uruchamia program shadera fragmentów, raz dla każdego fragmentu. Dla obrazu o rozmiarze 16x24 piksele program uruchamia się 384 razy, każdorazowo równolegle.

Wejściem programu jest to, co dostarczą potok renderowania i shader wierzchołków, zwykle współrzędne UV fragmentu, kolory tint itd. Wyjściem jest końcowy kolor piksela (gl_FragColor).
Najprostszy program shadera fragmentów po prostu ustawia kolor każdego piksela na czarny (znów, nie jest to zbyt użyteczny program):
void main()
{
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
Ponownie, bardziej kompletnym przykładem jest wbudowany shader fragmentów sprite’a:
// sprite.fp
varying mediump vec2 var_texcoord0; // [1]
uniform lowp sampler2D DIFFUSE_TEXTURE; // [2]
uniform lowp vec4 tint; // [3]
void main()
{
lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w); // [4]
lowp vec4 diff = texture2D(DIFFUSE_TEXTURE, var_texcoord0.xy);// [5]
gl_FragColor = diff * tint_pm; // [6]
}
varying. Jej wartość będzie interpolowana dla każdego fragmentu pomiędzy wartościami ustawionymi dla każdego wierzchołka kształtu.sampler2D. Sampler, razem z interpolowanymi współrzędnymi tekstury, służy do pobierania tekstury, aby sprite mógł zostać poprawnie teksturowany. Ponieważ to jest sprite, silnik przypisze ten sampler do obrazu ustawionego we właściwości Image sprite’a.CONSTANT_TYPE_USER i zadeklarowano ją jako uniform. Jej wartość służy do koloryzacji sprite’a. Domyślnie jest to czysta biel.gl_FragColor zostaje ustawione na kolor wyjściowy fragmentu: kolor diffuse z tekstury pomnożony przez wartość tint.Wynikowy fragment następnie przechodzi przez testy. Częstym testem jest depth test, w którym wartość głębi fragmentu jest porównywana z wartością bufora głębi dla piksela, który jest testowany. W zależności od wyniku testu fragment może zostać odrzucony albo nowa wartość zostanie zapisana do bufora głębi. Częstym zastosowaniem tego testu jest pozwolenie, aby grafika bliżej kamery zasłaniała grafikę znajdującą się dalej.
Jeśli test uzna, że fragment ma zostać zapisany do bufora ramki, zostanie blended z już obecnymi w buforze danymi pikseli. Parametry mieszania ustawione w skrypcie renderowania pozwalają połączyć kolor źródłowy (wartość zapisaną przez shader fragmentów) i kolor docelowy (kolor z obrazu w buforze ramki) na różne sposoby. Częstym zastosowaniem mieszania jest umożliwienie renderowania przezroczystych obiektów.
Shadertoy zawiera ogromną liczbę shaderów tworzonych przez użytkowników. To świetne źródło inspiracji, z którego można się uczyć różnych technik cieniowania. Wiele shaderów pokazanych na stronie można przenieść do Defold przy bardzo niewielkim wysiłku. Samouczek Shadertoy przeprowadza przez kroki konwersji istniejącego shadera do Defold.
Samouczek Grading pokazuje, jak stworzyć pełnoekranowy efekt korekcji kolorów z użyciem tekstur tabeli lookup do gradingu.
The Book of Shaders nauczy cię, jak używać shaderów i integrować je z projektami, poprawiając ich wydajność i jakość grafiki.