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
Программы шейдеров являются ядром рендеринга графики. Они являются программами написанными на C-подобном языке под названием GLSL (GL Shading Language), которые исполняются аппаратными обеспечением для выполнентя операций либо над 3D данными (вершины) или над пикселями, которые в конечном итоге появляются на экране (“фрагменты”). Шейдеры используются для отрисовки спрайтов, подсветки 3D моделей, создания полноэкранных пост-эффектов и многого другого.
Данное руководство объясняет как рендеринг конвейер Defold-а интерфейсно общается с вершинными и фрагментными шейдерами. Чтобы создать шейдеры для вашего контента, вам также нужно понять концепцию материалов, а также принцип работы рендер конвейера.
Спецификации OpenGL ES 2.0 (OpenGL для встраиваемых систем) и OpenGL ES Shading Language можно найти по адресу https://www.khronos.org/registry/gles/
Обратите внимание, что на настольных компьютерах можно писать шейдеры задействуя фичи не доступные в OpenGL ES 2.0. Драйвер вашей графической карты может радостно скомпилировать и запустить код шейдера, который не заработает на мобильных устройствах.
Входные данные вершинного шейдера это данные вершин (в форме attributes
) и константы (uniforms
). Общие константы это матрицы, необходимые для преобразования и проецирования позиции вершины в экранное пространство.
Выходные данные вершинного шейдера это вычисленная позиция вершины на экране (gl_Position
). Также есть возможность передать данные из вершинного шейдера на вход фрагментному шейдеру через varying
переменные.
Входные данные фрагментного шейдера это константы (uniforms
) и также любые varying
переменные переданные вершинным шейдером.
Выходные данные фрагментного шейдера это цветовое значение для конкретного фрагмента (gl_FragColor
).
Входными данными вычислительного шейдера являются буферы констант (uniforms
), текстурные изображения (image2D
), сэмплеры (sampler2D
) и буферы хранения (buffer
).
Выход вычислительного шейдера не определяется явно, в отличие от вершинных и фрагментных шейдеров, нет необходимости выдавать конкретный результат. Поскольку вычислительные шейдеры универсальны, программист сам определяет, какой результат должен быть получен.
Когда модель помещается в игровой мир, локальные координаты вершин модели должны быть переведены в координаты мирового пространства. Этот перевод делается посредством мировой матрицы преобразования (world transform matrix), которая говорит, какой сдвиг, поворот и масштабирование должны быть применены к вершинам модели, чтобы корректно разместить в системе координат игрового мира.
position
и texcoord0
.position
и texcoord0
.position
, textcoord0
и color
.position
, texcoord0
и color
.position
, texcoord0
и normal
.position
, texcoord0
, face_color
, outline_color
и shadow_color
.uniform
переменные в программе шейдера. Uniforms Сэмплеры добавляются в секции Samplers материала и затем объявляются как uniform
переменные в программе шейдера. Матрицы необходимые для выполнения вершинных преобразований в вершинном шейдере доступны в качестве констант:
CONSTANT_TYPE_WORLD
— это матрица мира которая делает отображение из локальных координат пространства объекта в мировое пространство.CONSTANT_TYPE_VIEW
— это матрица мира которая делает отображение из пространства мира в пространство камеры.CONSTANT_TYPE_PROJECTION
— это проекционная матрица которая делает отображение из пространства камеры в пространство экрана.CONSTANT_TYPE_USER
— это константа типа vec4
которую вы можете использовать по своему усмотрению.Руководство по материалам объясняет как задавать константы.
sampler2D
— считывает данные из 2D текстуры изображения.sampler2DArray
— считывает данные из массива 2D текстур. В основном используется для атласов с разбиением на страницы.samplerCube
— считывает данные из текстуры кубической карты, состоящей из 6 изображений.image2D
— загружает (и потенциально сохраняет) данные текстуры в объект изображения. В основном используется в вычислительных шейдерах для хранения данных.Вы можете использовать сэмплер только в функциях поиска текстуры стандартной библиотеки GLSL. Руководство по материалам объясняет как задавать настройки сэмплера.
UV-карта обычно создается в программе 3D-моделирования и сохраняется в меше. Координаты текстуры для каждой вершины предоставляются вершинному шейдеру в качестве атрибута. Затем varying переменная используется для нахождения UV-координаты для каждого фрагмента, будучи интерполированной на основе значений вершин.
Например, установка varying переменной в RGB цвет vec3
типа для каждого угла треугольника приведет к интерполяции цветов по всей форме. Точно так же установка координат поиска текстурной карты (или * UV-координат *) для каждой вершины в прямоугольнике позволяет фрагментному шейдеру искать значения цвета текстуры для всей области фигуры.
Поскольку движок Defold поддерживает несколько платформ и графических API, разработчикам должно быть легко писать шейдеры, работающие везде. Конвейер ассетов реализует это главным образом двумя способами (обозначаемыми как шейдерные пайплайны
):
Начиная с Defold 1.9.2 рекомендуется писать шейдеры с использованием нового пайплайна. Для этого большинство шейдеров нужно мигрировать на GLSL версии как минимум 140 (OpenGL 3.1). Чтобы выполнить миграцию, убедитесь, что соблюдены следующие требования:
Укажите #version 140
в начале шейдера:
#version 140
Именно так шейдерный пайплайн выбирается в процессе сборки, поэтому старые шейдеры всё ещё работают. Если директива версии не указана, Defold использует устаревший пайплайн по умолчанию.
В вершинных шейдерах замените ключевое слово attribute
на in
:
// вместо:
// attribute vec4 position;
// используйте:
in vec4 position;
Примечание: фрагментные (и compute) шейдеры не принимают входные значения от вершин.
В вершинных шейдерах varying переменные должны быть с префиксом out
. Во фрагментных шейдерах — с префиксом in
:
// В вершинном шейдере, вместо:
// varying vec4 var_color;
// используйте:
out vec4 var_color;
// Во фрагментном шейдере, вместо:
// varying vec4 var_color;
// используйте:
in vec4 var_color;
Непрозрачные типы uniforms (sampler, image, atomic, SSBO) не требуют миграции, они могут использоваться как и раньше:
uniform sampler2D my_texture;
uniform image2D my_image;
Для непрозрачных типов необходимо объединить переменные в uniform block
. Это просто группа uniform переменных, объявленная с использованием ключевого слова uniform
:
uniform vertex_inputs
{
mat4 mtx_world;
mat4 mtx_proj;
mat4 mtx_view;
mat4 mtx_normal;
...
};
void main()
{
// Individual members of the uniform block can be used as-is
gl_Position = mtx_proj * mtx_view * mtx_world * vec4(position, 1.0);
}
Все переменные в uniform-блоке становятся доступными для материалов и компонентов как отдельные константы. Миграция для использования render constant buffers или go.set
и go.get
не требуется.
Начиная с версии 140 во фрагментных шейдерах использование gl_FragColor
устарело. Вместо этого используйте out
:
// вместо:
// gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
// используйте:
out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
}
Функции вроде texture2D
и texture2DArray
больше не поддерживаются. Вместо них используйте texture
:
uniform sampler2D my_texture;
uniform sampler2DArray my_texture_array;
// вместо:
// vec4 sampler_2d = texture2D(my_texture, uv);
// vec4 sampler_2d_array = texture2DArray(my_texture_array, vec3(uv, slice));
// используйте:
vec4 sampler_2d = texture(my_texture, uv);
vec4 sampler_2d_array = texture(my_texture_array, vec3(uv, slice));
Ранее для соответствия спецификациям OpenGL ES требовалось явно указывать точность переменных, входов, выходов и т. д. В этом больше нет необходимости — теперь точность автоматически назначается для платформ, которые это поддерживают.
Ниже приведён пример встроенного вершинного и фрагментного шейдера спрайта, переведённого на современный формат:
#version 140
uniform vx_uniforms
{
mat4 view_proj;
};
// positions are in world space
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()
{
// Premultiply alpha since all runtime textures already are
vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
color_out = texture(texture_sampler, var_texcoord0.xy) * tint_pm;
}
В Defold шейдерах поддерживается включение исходного кода из файлов с расширением .glsl
, находящихся внутри проекта. Чтобы включить GLSL-файл в шейдер, используйте директиву #include
— либо с двойными кавычками, либо со скобками. Пути в директиве должны быть либо относительными к корню проекта, либо относительными к текущему включающему файлу:
// In file /main/my-shader.fp
// Absolute path
#include "/main/my-snippet.glsl"
// The file is in the same folder
#include "my-snippet.glsl"
// The file is in a sub-folder on the same level as 'my-shader'
#include "sub-folder/my-snippet.glsl"
// The file is in a sub-folder on the parent directory, i.e /some-other-folder/my-snippet.glsl
#include "../some-other-folder/my-snippet.glsl"
// The file is on the parent directory, i.e /root-level-snippet.glsl
#include "../root-level-snippet.glsl"
Особенности использования:
/
.const float #include "my-float-name.glsl" = 1.0
не будет работать.Сниппеты могут сами включать другие .glsl
файлы, что может привести к тому, что итоговый шейдер будет включать один и тот же код несколько раз. В зависимости от содержимого файлов это может привести к ошибкам компиляции из-за повторного определения одних и тех же символов. Чтобы избежать этого, используйте защиту от повторного включения (header guards) — это распространённая практика в нескольких языках программирования. Пример:
// In my-shader.vs
#include "math-functions.glsl"
#include "pi.glsl"
// In math-functions.glsl
#include "pi.glsl"
// In pi.glsl
const float PI = 3.14159265359;
В этом примере константа PI
будет определена дважды, что приведёт к ошибкам компиляции при запуске проекта. Вместо этого следует защитить содержимое файла с помощью защиты от повторного включения (header guards):
// In pi.glsl
#ifndef PI_GLSL_H
#define PI_GLSL_H
const float PI = 3.14159265359;
#endif // PI_GLSL_H
Код из pi.glsl
будет вставлен дважды в my-shader.vs
, но поскольку он обёрнут защитой от повторного включения, символ PI
будет определён только один раз, и шейдер успешно скомпилируется.
Однако в зависимости от ситуации это не всегда строго необходимо. Если вы, например, хотите повторно использовать код локально внутри функции или в другом месте, где значения не должны быть доступны глобально в коде шейдера, тогда, скорее всего, не стоит использовать защиту от повторного включения. Пример:
// В red-color.glsl
vec3 my_red_color = vec3(1.0, 0.0, 0.0);
// In 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;
}
Когда шейдеры отображаются в окне редактора Defold, доступна препроцессорная директива EDITOR
. Это позволяет писать код, который ведёт себя по-разному в редакторе и в реальной игре.
Это особенно полезно для:
Используйте директиву препроцессора #ifdef EDITOR
, чтобы условно компилировать код только для редактора:
#ifdef EDITOR
// Этот код будет выполняться только при отображении шейдера в редакторе Defold
color_out = vec4(1.0, 0.0, 1.0, 1.0); // Пурпурный цвет для предпросмотра
#else
// Этот код будет выполняться при запуске в игре
color_out = texture(texture_sampler, var_texcoord0) * tint_pm;
#endif
Прежде чем оказаться на экране, данные, которые вы создаете для своей игры, проходят ряд шагов:
Все визуальные компоненты (спрайты, GUI-ноды, частицы или модели) состоят из вершин, точек в трехмерном мире, которые описывают форму компонента. Хорошая новость здесь в том, что форму можно рассматривать под любым углом и с любого расстояния. Задача программы вершинного шейдера — взять одну вершину и перевести ее в позицию в окне просмотра так, чтобы форма могла появиться на экране. Для фигуры с 4 вершинами программа вершинного шейдера запускается 4 раза, в параллели для всех запусков.
Входными данными программы являются позиция вершины (и другие данные, связанные с вершиной), а выходными данными — новая позиция вершины (gl_Position
), а также любые varying
переменные, которые должны быть интерполированы для каждого фрагмента.
Самая простая программа вершинного шейдера просто устанавливает позицию на выходе в нулевую вершину (не очень-то и полезный результат):
void main()
{
gl_Position = vec4(0.0,0.0,0.0,1.0);
}
Более сложный пример — встроенный вершинный шейдер спрайтов:
-- 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]
}
gl_Position
в выходную позицию текущей вершины в пространстве проекции. Это значение состоит из 4 компонентов: «x», «y», «z» и «w». Компонент w
используется для вычисления интерполяции с правильной перспективой. Это значение обычно составляет 1,0 для каждой вершины до применения какой-либо матрицы преобразования.После вершинного шейдинга определяется экранная форма компонента: генерируются и растеризуются примитивные формы, что означает, что графическое аппаратное обеспечение разбивает каждую форму на фрагменты или пиксели. Затем оно запускает программу фрагментного шейдера, по одному разу для каждого из фрагментов. Для изображения на экране размером 16x24 пикселей программа запускается 384 раза параллельно.
Входные данные программы — это то, что отправляет конвейер рендеринга и вершинный шейдер, обычно это uv-координаты фрагмента, цвета оттенка и т. д. Результатом является окончательный цвет пикселя (gl_FragColor
).
Самая простая программа фрагментного шейдера просто устанавливает черный цвет для каждого пикселя (опять же, не очень полезная программа) :
void main()
{
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
Опять же, более сложным примером является встроенный фрагментный шейдер спрайтов:
// 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]
}
sampler2D
переменную. Сэмплер вместе с интерполированными координатами текстуры используется для поиска текстуры, чтобы спрайт мог быть текстурирован должным образом. Поскольку это спрайт, движок назначит этот сэмплер изображению, заданному в свойстве Image спрайта.CONSTANT_TYPE_USER
определена в материале и объявлена типом uniform
. Ее значения используются для задания цветового оттенка спрайту. Значение по-умолчанию — чистый белый цвет.gl_FragColor
выставлен в цвет для фрагмента на вывод: рассеянный свет из текстуры помноженный на значение цвета оттенка.Полученное значение фрагмента затем проходит тесты. Обычным тестом является тест глубины, в котором значение глубины фрагмента сравнивается со значением буфера глубины для тестируемого пикселя. В зависимости от теста, фрагмент может быть отброшен либо в буфер глубины записывается новое значение. Обычно этот тест используется для того, чтобы позволить графике, находящейся ближе к камере, перекрывать графику находящуюся далеко позади.
Если тест пришел к выводу, что фрагмент должен быть записан в буфер кадра, он будет смешан с пиксельными данными, уже присутствующими в буфере. Параметры наложения, которые задаются в рендер скрипте, позволяют различными способами комбинировать исходный цвет (значение, записанное фрагментным шейдером) и целевой цвет (цвет из изображения в буфере кадра). Обычно смешивание используется для включения рендеринга прозрачных объектов.
Shadertoy содержит огромное количество шейдеров, добавленных пользователями. Это отличный источник вдохновения, где вы можете узнать о различных методах шейдинга. Многие шейдеры, представленные на сайте, могут быть перенесены в Defold с минимальными изменениями. Туториал по Shadertoy проходится по этапам преобразования существующего шейдера в Defold.
Grading туториал показывает, как создать эффект цветокоррекции в полноэкранном режиме, используя текстуры с таблицами поиска цвета для грейдинга (процесса художественной цветокоррекции).
The Book of Shaders научит вас тому, как использовать и встраивать шейдеры в свои проекты, улучшая их производительность и качество графики.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB