Язык шейдеров

Введение

Godot использует язык шейдеров схожий с GLSL ES 3.0. Поддерживаются большинство типов данных и функций, а те что остались за бортом, скорее всего будет добавлены в будущем.

Если вы уже знакомы с GLSL, то Руководство по переходу на шейдеры Godot - это ресурс, который поможет вам перейти с обычного GLSL на язык шейдеров Godot.

Типы данных

Поддерживаются большинство типов данных из GLSL ES 3.0:

Тип

Описание

voіd

Пустой тип данных, используется только для функций которые ничего не возвращают.

bool

Булевый (логический) тип данных, может содержать только true или false.

bvеc2

Двух-компонентный вектор из булевых значений.

bvеc3

Трех-компонентный вектор из булевых значений.

bveс4

Четырех-компонентный вектор из булевых значений.

int

32-битное знаковое скалярное целое число.

ivеc2

Двух-компонентный вектор из целых чисел.

ivеc3

Трёх-компонентный вектор из целых чисел.

iveс4

Четырёх-компонентный вектор из целых чисел.

uіnt

Целое число без знака; не может содержать отрицательных значений.

uvеc2

Двух-компонентный вектор состоящий из беззнаковых чисел.

uvеc3

Трёх-компонентный вектор из беззнаковых целых чисел.

uvеc4

Четырёх-компонентный вектор из беззнаковых целых чисел.

float

32-битный скаляр с плавающей точкой.

vеc2

Двух-компонентный вектор из чисел с плавающей точкой.

vеc3

Трёх-компонентный вектор из чисел с плавающей точкой.

vеc4

Четырёх-компонентный вектор из чисел с плавающей точкой.

mаt2

Матрица 2x2, с развёрткой по столбцу.

mаt3

Матрица 3x3, с развёрткой по столбцу.

mаt4

Матрица 4x4, с развёрткой по столбцу.

samplеr2D

Тип сэмплера, для связки 2D текстур, которые читаются через числа с плавающей точкой.

isamplеr2D

Тип сэмплера, для связки 2D текстур, которые читаются через целые числа.

usamplеr2D

Тип сэмплера, для связки 2D текстур, которые читаются через целые беззнаковые числа.

sampler2DArray

Тип сэмплера, для связки 2D текстур, которые читаются через числа с плавающей точкой.

isampler2DArray

Тип сэмплера, для связки 2D текстур, которые читаются через целые числа.

usampler2DArray

Тип сэмплера, для связки 2D текстур, которые читаются через целые беззнаковые числа.

sampler3D

Тип сэмплера, для связки 2D текстур, которые читаются через числа с плавающей точкой.

isampler3D

Тип сэмплера, для связки 2D текстур, которые читаются через целые числа.

usampler3D

Тип сэмплера, для связки 2D текстур, которые читаются через целые беззнаковые числа.

samplerCubе

Тип сэмплера, для связки кубических текстур, которые читаются через числа с плавающей точкой.

samplerCubeArray

Тип сэмплера для привязки массивов Cubemap, которые читаются как float. Поддерживается только в Forward+ и Mobile, но не в режиме совместимости.

samplerExternalOES

Тип внешнего сэмплера. Поддерживается только в разделе Compatibility/Android платформы.

These types can also be put inside arrays or structs, which are also usable as function parameters or return values. Arrays can be used as uniforms, but structs cannot.

Предупреждение

Локальные переменные не инициализируются значениями по умолчанию, такими как 0.0. Если использовать переменную без предварительного присвоения ей значения, она будет содержать то значение, которое уже присутствовало в этой ячейке памяти, что может привести к непредсказуемым визуальным искажениям. Однако юниформы и вариации инициализируются значениями по умолчанию.

Комментарии

Язык шейдеров поддерживает тот же синтаксис комментариев, что используется в C# и C++, используя // для однострочных комментариев и /* */ для многострочных комментариев:

// Single-line comment.
int a = 2;  // Another single-line comment.

/*
Multi-line comment.
The comment ends when the ending delimiter is found
(here, it's on the line below).
*/
int b = 3;

Additionally, you can use documentation comments that are displayed in the inspector when hovering a shader parameter. Documentation comments are currently only supported when placed immediately above a uniform declaration. These documentation comments only support the multiline comment syntax (even if used on a single line) and must use two leading asterisks (/**) instead of just one (/*):

/**
 * This is a documentation comment.
 * These lines will appear in the inspector when hovering the shader parameter
 * named "Something".
 * You can use [b]BBCode[/b] [i]formatting[/i] in the comment.
 */
uniform int something = 1;

/** This is a single-line documentation comment. */
uniform float something_else = 1.0;

Звёздочки в последующих строках не обязательны, но рекомендуются согласно Руководство по стилю шейдеров. Эти звёздочки автоматически удаляются инспектором, поэтому они не будут отображаться во всплывающей подсказке.

Приведение переменных

Так же как и в GLSL ES 3.0, неявное приведение между скалярами и векторами одинакового размера но разного типа не разрешено. Приведение типов разной размерности также не разрешено. Конвертация должны быть произведена явно, через конструкторы.

Пример:

float a = 2; // invalid
float a = 2.0; // valid
float a = float(2); // valid

Числовые константы - это целые числа по умолчанию, так что приведение всегда необходимо при конвертации в беззнаковое целое:

int a = 2; // valid
uint a = 2; // invalid
uint a = uint(2); // valid

Доступ к компонентам

Доступ к каждому отдельному компоненту векторного типа может быть получен через свойства "x", "y", "z" и "w". Кроме этого, можно использовать эквивалентные им "r", "g", "b" и "a". Используйте то, что вы считаете лучшим для ваших задач.

Для матриц используйте синтаксис индексации m[column][row] для доступа к каждому скаляру или m[column] для доступа к вектору по индексу столбца. Например, для доступа к y-компоненте перевода матрицы преобразования mat4 (4-й столбец, 2-я строка) используйте m[3][1] или m[3].y.

Создание

При создании векторного типа вы всегда должны передавать:

// The required amount of scalars
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
// Complementary vectors and/or scalars
vec4 a = vec4(vec2(0.0, 1.0), vec2(2.0, 3.0));
vec4 a = vec4(vec3(0.0, 1.0, 2.0), 3.0);
// A single scalar for the whole vector
vec4 a = vec4(0.0);

Для построения матричных типов требуются векторы той же размерности, что и матрица, интерпретируемые как столбцы. Диагональную матрицу также можно построить, используя синтаксис matx(float). Соответственно, mat4(1.0) является единичной матрицей.

mat2 m2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));
mat3 m3 = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0));
mat4 identity = mat4(1.0);

Матрицы также могут быть построены из матрицы другой размерности. Существует два правила:

1. If a larger matrix is constructed from a smaller matrix, the additional rows and columns are set to the values they would have in an identity matrix. 1. If a smaller matrix is constructed from a larger matrix, the top, left submatrix of the larger matrix is used.

mat3 basis = mat3(MODEL_MATRIX);
mat4 m4 = mat4(basis);
mat2 m2 = mat2(m4);

Перестановка

Можно получить любую комбинацию компонентов в любом порядке, если в результате получается другой тип вектора (или скаляр). Это легче показать, чем объяснить:

vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
vec3 b = a.rgb; // Creates a vec3 with vec4 components.
vec3 b = a.ggg; // Also valid; creates a vec3 and fills it with a single vec4 component.
vec3 b = a.bgr; // "b" will be vec3(2.0, 1.0, 0.0).
vec3 b = a.xyz; // Also rgba, xyzw are equivalent.
vec3 b = a.stp; // And stpq (for texture coordinates).
float c = b.w; // Invalid, because "w" is not present in vec3 b.
vec3 c = b.xrt; // Invalid, mixing different styles is forbidden.
b.rrr = a.rgb; // Invalid, assignment with duplication.
b.bgr = a.rgb; // Valid assignment. "b"'s "blue" component will be "a"'s "red" and vice versa.

Точность

Можно добавить модификаторы точности к типам данных; используйте их для униформ, переменных, аргументов и вариаций:

lowp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // low precision, usually 8 bits per component mapped to 0-1
mediump vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // medium precision, usually 16 bits or half float
highp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // high precision, uses full float or integer range (32 bit default)

Использование более низкой точности для некоторых операций может ускорить вычисление (за счет меньшей точности). Это редко требуется в функции вершинного процессора (где большую часть времени требуется полная точность), но часто полезно во фрагментном процессоре.

Некоторые архитектуры (в основном мобильные) могут значительно выиграть от этого, но есть и минусы, например, дополнительные накладные расходы на преобразование между точными значениями. За дополнительной информацией обратитесь к документации по целевой архитектуре. Во многих случаях мобильные драйверы вызывают непоследовательное или неожиданное поведение, поэтому лучше не указывать точность, если это не требуется.

Массивы

Массивы - это контейнеры для нескольких переменных одного типа.

Локальные массивы

Локальные массивы объявляются в функциях. Они могут использовать все разрешённые типы данных, кроме сэмплеров. Объявление массива следует синтаксису в стиле C: [константа] + [точность] + имя_типа + идентификатор + [размер массива].

void fragment() {
    float arr[3];
}

Они могут быть инициализированы в начале, например:

float float_arr[3] = float[3] (1.0, 0.5, 0.0); // first constructor

int int_arr[3] = int[] (2, 1, 0); // second constructor

vec2 vec2_arr[3] = { vec2(1.0, 1.0), vec2(0.5, 0.5), vec2(0.0, 0.0) }; // third constructor

bool bool_arr[] = { true, true, false }; // fourth constructor - size is defined automatically from the element count

В одном выражении можно объявить несколько массивов (даже разного размера):

float a[3] = float[3] (1.0, 0.5, 0.0),
b[2] = { 1.0, 0.5 },
c[] = { 0.7 },
d = 0.0,
e[5];

Чтобы получить доступ к элементу массива, используйте синтаксис индексации:

float arr[3];

arr[0] = 1.0; // setter

COLOR.r = arr[0]; // getter

Массивы также имеют встроенную функцию .length() (не путать со встроенной функцией length()). Она не принимает никаких параметров и возвращает размер массива.

float arr[] = { 0.0, 1.0, 0.5, -1.0 };
for (int i = 0; i < arr.length(); i++) {
    // ...
}

Примечание

Если вы используете индекс меньше 0 или больше размера массива, шейдер будет аварийно завершен и прервёт рендеринг. Чтобы предотвратить это, используйте функции length(), if или clamp(), чтобы убедиться, что индекс находится между 0 и длиной массива. Всегда тщательно тестируйте и проверяйте свой код. Если вы передадите константное выражение или число, редактор проверит его границы, чтобы предотвратить этот сбой.

Глобальные массивы

Массивы в глобальном пространстве можно объявлять как const или uniform:

shader_type spatial;

const lowp vec3 v[1] = lowp vec3[1] ( vec3(0, 0, 1) );
uniform lowp vec3 w[1];

void fragment() {
  ALBEDO = v[0] + w[0];
}

Примечание

Глобальные массивы используют тот же синтаксис, что и локальные, за исключением добавления к их объявлению ключевых слов const или uniform. Обратите внимание, что однородные массивы не могут иметь значение по умолчанию.

Константы

Используйте ключевое слово const перед объявлением переменной, чтобы сделать ее неизменяемой, что означает, что она не может быть изменена. Все базовые типы, кроме сэмплеров, могут быть объявлены как константы. Доступ и использование константного значения происходит немного быстрее, чем использование uniform-переменных. Константы должны быть инициализированы при их объявлении.

const vec2 a = vec2(0.0, 1.0);
vec2 b;

a = b; // invalid
b = a; // valid

Константы не могут быть изменены и, кроме того, не могут иметь подсказок, но несколько из них (если они имеют один и тот же тип) могут быть объявлены в одном выражении, например

const vec2 V1 = vec2(1, 1), V2 = vec2(2, 2);

Как и переменные, массивы также могут быть объявлены с помощью const.

const float arr[] = { 1.0, 0.5, 0.0 };

arr[0] = 1.0; // invalid

COLOR.r = arr[0]; // valid

Константы могут быть объявлены как глобально (вне функции), так и локально (внутри функции). Глобальные константы полезны, когда вы хотите иметь доступ к значению во всём шейдере, которое не нужно изменять. Как и uniform-переменные, глобальные константы используются совместно на всех этапах шейдера, но они недоступны за пределами шейдера.

shader_type spatial;

const float GOLDEN_RATIO = 1.618033988749894;

Константы типа float должны инициализироваться с помощью обозначения . после десятичной части или с помощью научной нотации. Также поддерживается необязательный постсуффикс f.

float a = 1.0;
float b = 1.0f; // same, using suffix for clarity
float c = 1e-1; // gives 0.1 by using the scientific notation

Константы типа uint (целое беззнаковое) должны иметь суффикс u, чтобы отличать их от знаковых целых чисел. Альтернативно это можно сделать с помощью встроенной функции преобразования uint(x).

uint a = 1u;
uint b = uint(1);

Структуры

Структуры - это составные типы, которые можно использовать для лучшей абстракции шейдерного кода. Вы можете объявить их в глобальной области видимости, например:

struct PointLight {
    vec3 position;
    vec3 color;
    float intensity;
};

После объявления их можно инстанцировать и инициализировать, например:

void fragment()
{
    PointLight light;
    light.position = vec3(0.0);
    light.color = vec3(1.0, 0.0, 0.0);
    light.intensity = 0.5;
}

Или используйте конструктор struct для той же цели:

PointLight light = PointLight(vec3(0.0), vec3(1.0, 0.0, 0.0), 0.5);

Структуры могут содержать другие структуры или массивы, вы также можете использовать их в качестве глобальных констант:

shader_type spatial;

...

struct Scene {
    PointLight lights[2];
};

const Scene scene = Scene(PointLight[2](PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0), PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0)));

void fragment()
{
    ALBEDO = scene.lights[0].color;
}

Вы также можете передавать их в функции:

shader_type canvas_item;

...

Scene construct_scene(PointLight light1, PointLight light2) {
    return Scene({light1, light2});
}

void fragment()
{
    COLOR.rgb = construct_scene(PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0), PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 1.0), 1.0)).lights[0].color;
}

Операторы

Язык шейдеров Godot поддерживает тот же набор операторов, что и GLSL ES 3.0. Ниже приведён их список в порядке старшинства:

Приоритет

Класс

Оператор

1 (высший)

группировка скобок

()

2

унарный (unary)

+, -, !, ~

3

мультипликативный (multiplicative)

/, *, %

4

аддитивный (additive)

+, -

5

побитовый сдвиг

<<, >>

6

реляционный (relational)

<, >, <=, >=

7

равенства (equality)

==, !=

8

побитовое И (AND)

&

9

побитовое исключающее ИЛИ

^

10

побитовое включающее ИЛИ

|

11

логическое И

&&

12 (низший)

логическое включающее ИЛИ

||

Примечание

Большинство операторов, которые принимают векторы или матрицы (умножение, деление и т. д.), работают покомпонентно, то есть функция применяется к первому значению каждого вектора, затем ко второму значению каждого вектора и т. д. Несколько примеров:

Операция

Эквивалентная скалярная операция

vec3(4, 5, 6) + 2

vec3(4 + 2, 5 + 2, 6 + 2)

vec2(3, 4) * vec2(10, 20)

vec2(3 * 10, 4 * 20)

mat2(vec2(1, 2), vec2(3, 4)) + 10

mat2(vec2(1 + 10, 2 + 10), vec2(3 + 10, 4 + 10))

В разделе 5.10 "Операции с векторами и матрицами" Спецификации языка GLSL говорится следующее:

За некоторыми исключениями, операции выполняются покомпонентно. Обычно, когда оператор работает с вектором или матрицей, он действует независимо над каждым компонентом вектора или матрицы, покомпонентно. [...] Исключениями являются умножение матрицы на вектор, умножение вектора на матрицу и умножение матрицы на матрицу. Эти операции не выполняются покомпонентно, а выполняют корректное линейное алгебраическое умножение.

Контроль над потоком

Шейдерный язык Godot поддерживает наиболее распространённые типы управления потоком:

// `if`, `else if` and `else`.
if (cond) {

} else if (other_cond) {

} else {

}

// Ternary operator.
// This is an expression that behaves like `if`/`else` and returns the value.
// If `cond` evaluates to `true`, `result` will be `9`.
// Otherwise, `result` will be `5`.
int result = cond ? 9 : 5;

// `switch`.
switch (i) { // `i` should be a signed integer expression.
    case -1:
        break;
    case 0:
        return; // `break` or `return` to avoid running the next `case`.
    case 1: // Fallthrough (no `break` or `return`): will run the next `case`.
    case 2:
        break;
    //...
    default: // Only run if no `case` above matches. Optional.
        break;
}

// `for` loop. Best used when the number of elements to iterate on
// is known in advance.
for (int i = 0; i < 10; i++) {

}

// `while` loop. Best used when the number of elements to iterate on
// is not known in advance.
while (cond) {

}

// `do while`. Like `while`, but always runs at least once even if `cond`
// never evaluates to `true`.
do {

} while (cond);

Помните, что в современных GPU может существовать бесконечный цикл, который может заморозить ваше приложение (включая редактор). Godot не может защитить вас от этого, поэтому будьте осторожны и не допускайте такой ошибки!

Кроме того, сравнивая значения с плавающей точкой с числом, убедитесь, что они сравниваются с диапазоном, а не с точным числом.

Сравнение типа if (value == 0.3) может не привести к значению true. Математика с плавающей точкой часто является приблизительной и может не соответствовать ожиданиям. Кроме того, она может вести себя по-разному в зависимости от аппаратного обеспечения.

Не делайте этого.

float value = 0.1 + 0.2;

// May not evaluate to `true`!
if (value == 0.3) {
    // ...
}

Вместо этого всегда выполняйте сравнение диапазонов со значением эпсилон. Чем больше число с плавающей точкой (и чем менее точное число с плавающей точкой), тем больше должно быть значение эпсилон.

const float EPSILON = 0.0001;
if (value >= 0.3 - EPSILON && value <= 0.3 + EPSILON) {
    // ...
}

Дополнительную информацию см. в разделе floating-point-gui.de.

Отбрасывание

Фрагмент, Light (Свет) и пользовательские функции (вызываемые из фрагмента или Light) могут использовать ключевое слово discard. При использовании этого слова фрагмент удаляется, и ничего не записывается.

Имейте в виду, что использование discard приводит к снижению производительности, так как не позволяет препассу глубины быть эффективным на любых поверхностях, использующих этот шейдер. Кроме того, отброшенный пиксель всё равно должен быть отрисован в вершинном шейдере, что означает, что шейдер, использующий discard для всех своих пикселей, все равно будет более дорогим для рендеринга по сравнению с отсутствием рендеринга каких-либо объектов.

Функции

В шейдере Godot можно определять функции. Они используют следующий синтаксис:

ret_type func_name(args) {
    return ret_type; // if returning a value
}

// a more specific example:

int sum2(int a, int b) {
    return a + b;
}

Вы можете использовать только те функции, которые были определены выше (выше в редакторе) той функции, из которой вы их вызываете. Переопределение функции, которая уже была определена выше (или является именем встроенной функции), приведет к ошибке.

Аргументы функции могут иметь специальные идентификаторы:

  • in: Означает, что аргумент предназначен только для чтения (по умолчанию).

  • out: Означает, что аргумент предназначен только для записи.

  • inout: Означает, что аргумент полностью передаётся по ссылке.

  • const: Означает, что аргумент является константой и не может быть изменён, может сочетаться с квалификатором in.

Пример ниже:

void sum2(int a, int b, inout int result) {
    result = a + b;
}

Поддерживается перегрузка функций. Вы можете определить несколько функций с одним и тем же именем, но разными аргументами. Обратите внимание, что implicit casting (неявное приведение) в вызовах перегруженных функций не допускается, например, из int в float (1 в 1.0).

vec3 get_color(int t) {
    return vec3(1, 0, 0); // Red color.
}
vec3 get_color(float t) {
    return vec3(0, 1, 0); // Green color.
}
void fragment() {
    vec3 red = get_color(1);
    vec3 green = get_color(1.0);
}

Varуings

Для передачи данных от функций вершинного процессора к функции фрагментного процессора (или света) используются varyings. Они устанавливаются для каждой примитивной вершины в вершинном процессоре, и значение интерполируется для каждого пикселя во фрагментном процессоре.

shader_type spatial;

varying vec3 some_color;

void vertex() {
    some_color = NORMAL; // Make the normal the color.
}

void fragment() {
    ALBEDO = some_color;
}

void light() {
    DIFFUSE_LIGHT = some_color * 100; // optionally
}

Varying также может быть массивом:

shader_type spatial;

varying float var_arr[3];

void vertex() {
    var_arr[0] = 1.0;
    var_arr[1] = 0.0;
}

void fragment() {
    ALBEDO = vec3(var_arr[0], var_arr[1], var_arr[2]); // red color
}

Также можно передавать данные из фрагментного (fragment) процессора в процессоры light, используя ключевое слово varying. Для этого вы можете присвоить его в fragment и затем использовать в функции light.

shader_type spatial;

varying vec3 some_light;

void fragment() {
    some_light = ALBEDO * 100.0; // Make a shining light.
}

void light() {
    DIFFUSE_LIGHT = some_light;
}

Обратите внимание, что переменные не могут быть назначены в пользовательских функциях или функциях светового процессора (light), например:

shader_type spatial;

varying float test;

void foo() {
    test = 0.0; // Error.
}

void vertex() {
    test = 0.0;
}

void light() {
    test = 0.0; // Error too.
}

Это ограничение было введено для предотвращения неправильного использования до инициализации.

Интерполяционные квалификаторы

Некоторые значения интерполируются во время конвейера затенения. Вы можете изменить способ интерполяции, используя квалификаторы интерполяции.

shader_type spatial;

varying flat vec3 our_color;

void vertex() {
    our_color = COLOR.rgb;
}

void fragment() {
    ALBEDO = our_color;
}

Существует два возможных квалификатора интерполяции:

Квалификатор

Описание

flаt

Значение не интерполируется.

smоoth

Значение интерполируется с учётом коррекции перспективы. Это используется по умолчанию.

Uniform-переменные

Передача значений в шейдеры возможна с помощью uniforms, которые определены в глобальной области видимости шейдера, вне функций. При последующем назначении шейдера материалу, uniforms появятся как редактируемые параметры в инспекторе материала. Uniforms нельзя записывать из шейдера. Uniforms может быть любым data type, кроме void.

shader_type spatial;

uniform float some_value;

uniform vec3 colors[3];

Вы можете задать униформы в редакторе в инспекторе материалов. Также их можно задать из кода.

Единые подсказки

Godot предоставляет необязательные подсказки по униформе, чтобы компилятор понимал, для чего используется униформа и как редактор должен позволять пользователям изменять её.

shader_type spatial;

uniform vec4 color : source_color;
uniform float amount : hint_range(0, 1);
uniform vec4 other_color : source_color = vec4(1.0); // Default values go after the hint.
uniform sampler2D image : source_color;

Униформе также можно присвоить значения по умолчанию:

shader_type spatial;

uniform vec4 some_vector = vec4(0.0);
uniform vec4 some_color : source_color = vec4(1.0);

Обратите внимание, что при добавлении значения по умолчанию и подсказки, значение по умолчанию идёт после подсказки.

Полный список подсказок по униформе приведен ниже:

Тип

Подсказка

Описание

vec3, vec4

source_color

Используется как цвет.

int

hint_enum("String1", "String2")

Отображает входные данные типа int в виде раскрывающегося виджета в редакторе.

int, float

hint_range(min, max[, step])

Ограничение на значения в диапазоне (мин/макс/шаг).

samplеr2D

source_color

Используется как цвет альбедо.

samplеr2D

hіnt_normal

Используется как карта нормалей.

samplеr2D

hint_default_white

В качестве значения или цвета альбедо, по умолчанию - непрозрачный белый.

samplеr2D

hint_default_black

В качестве значения или цвета альбедо, по умолчанию - непрозрачный чёрный.

samplеr2D

hint_default_transparent

В качестве значения или цвета альбедо, по умолчанию - прозрачный черный.

samplеr2D

hint_anisotropy

Как карта потока, по умолчанию справа.

samplеr2D

hint_roughness[_r, _g, _b, _a, _normal, _gray]

Используется для ограничителя шероховатости при импорте (попытки уменьшить сглаживание блика). _normal - это карта нормалей, которая направляет работу ограничителя шероховатости, при этом шероховатость увеличивается в областях с высокочастотными деталями.

samplеr2D

filter[_nearest, _linear][_mipmap][_anisotropic]

Включает фильтрацию заданных текстур.

samplеr2D

repeat[_enable, _disable]

Включает повтор текстуры.

samplеr2D

hint_screen_texture

Texture - это текстура экрана.

samplеr2D

hint_depth_texture

Текстура глубины.

samplеr2D

hint_normal_roughness_texture

Texture - обычная текстура шероховатости (поддерживается только в Forward+).

С использованием hint_enum

Доступ к значениям int можно получить как к читаемому выпадающему виджету, используя униформу hint_enum:

uniform int noise_type : hint_enum("OpenSimplex2", "Cellular", "Perlin", "Value") = 0;

Вы можете назначить явные значения униформе hint_enum, используя синтаксис с двоеточием, аналогичный GDScript:

uniform int character_speed: hint_enum("Slow:30", "Average:60", "Very Fast:200") = 60;

Значение будет сохранено как целое число, соответствующее индексу выбранного параметра (например, 0, 1 или 2), или как значение, заданное с помощью двоеточия (например, 30, 60 или 200). При установке значения с помощью set_shader_parameter() необходимо использовать целое число, а не имя String.

С использованием source_color

Для корректной выборки любой текстуры, содержащей данные о цвете sRGB, требуется подсказка source_color. Это связано с тем, что Godot рендерит в линейном цветовом пространстве, но некоторые текстуры содержат данные о цвете sRGB. Если эта подсказка не используется, текстура будет выглядеть размытой.

Текстуры альбедо и цвета обычно должны иметь подсказку source_color. Текстуры нормалей, шероховатости, металлики и высоты обычно не требуют подсказки source_color.

Использование подсказки source_color обязательно в рендерах Forward+ и Mobile, а также в шейдерах canvas_item, если включён HDR 2D. Подсказка source_color необязательна для рендера Compatibility и для шейдеров canvas_item, если HDR 2D отключён. Тем не менее, рекомендуется всегда использовать подсказку source_color, поскольку она работает даже при смене рендеров или отключении HDR 2D.

Однородные группы

Чтобы сгруппировать несколько униформ в разделе инспектора, можно использовать ключевое слово group_uniform, например:

group_uniforms MyGroup;
uniform sampler2D test;

Вы можете закрыть группу, используя:

group_uniforms;

Синтаксис также поддерживает подгруппы (перед этим необязательно объявлять базовую группу):

group_uniforms MyGroup.MySubgroup;

Глобальные uniforms

Иногда требуется изменить параметр сразу в нескольких шейдерах. При использовании обычной униформы это требует много работы, поскольку необходимо отслеживать все шейдеры и устанавливать униформу для каждого из них. Глобальные униформы позволяют создавать и обновлять униформы, которые будут доступны во всех шейдерах, в каждом типе шейдеров (canvas_item, spatial, particles, sky и fog).

Глобальные униформы особенно полезны для эффектов окружающей среды, которые влияют на многие объекты в сцене, например, когда листва изгибается, когда рядом находится игрок, или когда объекты движутся под действием ветра.

Примечание

Глобальные юниформы — это не то же самое, что глобальная область действия (scope) для отдельного шейдера. В то время как обычные юниформы определяются вне функций шейдера и, следовательно, находятся в глобальной области действия шейдера, глобальные юниформы являются глобальными для всех шейдеров во всём проекте (но внутри каждого шейдера также находятся в глобальной области действия).

Чтобы создать глобальную униформу, откройте Project Settings и перейдите на вкладку Shader Globals. Укажите имя униформы (с учетом регистра) и тип, затем нажмите Add в правом верхнем углу диалога. Затем вы можете изменить значение, присвоенное униформе, щёлкнув значение в списке:

Добавление глобальной униформы на вкладке Shader Globals в Настройках проекта

Добавление глобальной униформы на вкладке Shader Globals в Настройках проекта

После создания глобальной uniform-переменной, вы можете использовать её в шейдере следующим образом:

shader_type canvas_item;

global uniform vec4 my_color;

void fragment() {
    COLOR = my_color.rgb;
}

Обратите внимание, что глобальная uniform-переменная должна существовать в Настройках проекта на момент сохранения шейдера, иначе компиляция будет неудачной. Хотя вы можете присвоить значение по умолчанию, используя global uniform vec4 my_color = ... в коде шейдера, оно будет проигнорировано, так как глобальная uniform-переменная в любом случае должна быть определена в Настройках проекта.

Чтобы изменить значение глобальной переменной во время выполнения, используйте метод RenderingServer.global_shader_parameter_set в скрипте:

RenderingServer.global_shader_parameter_set("my_color", Color(0.3, 0.6, 1.0))

Присваивать глобальные uniform-значения можно сколько угодно раз без ущерба для производительности, поскольку установка данных не требует синхронизации между CPU и GPU.

Вы также можете добавлять или удалять глобальные униформы во время выполнения:

RenderingServer.global_shader_parameter_add("my_color", RenderingServer.GLOBAL_VAR_TYPE_COLOR, Color(0.3, 0.6, 1.0))
RenderingServer.global_shader_parameter_remove("my_color")

Добавление или удаление глобальных унифицированных значений во время выполнения влияет на производительность, хотя и не так сильно по сравнению с получением глобальных унифицированных значений из скрипта (см. предупреждение ниже).

Предупреждение

Хотя вы можете запросить значение глобальной униформы во время выполнения скрипта, используя RenderingServer.global_shader_parameter_get("uniform_name"), это приводит к значительному снижению производительности, поскольку потоку рендеринга необходимо синхронизироваться с вызывающим потоком.

Поэтому не рекомендуется постоянно считывать значения глобальной шейдерной uniform-переменной в скрипте. Если вам нужно читать значения в скрипте после их установки, подумайте о создании autoload, где вы храните значения, которые вам нужно запросить, одновременно устанавливая их в качестве глобальных uniform-переменных.

Uniform-переменные на экземпляр

Примечание

Униформы для каждого экземпляра доступны как в шейдерах canvas_item (2D), так и в spatial (3D).

Иногда требуется изменить параметр на каждом узле с помощью материала. Например, в лесу, полном деревьев, когда вы хотите, чтобы каждое дерево имело немного другой цвет, который можно редактировать вручную. Без uniform-переменных для каждого экземпляра это требует создания уникального материала для каждого дерева (каждый из которых имеет немного другой оттенок). Это усложняет управление материалами, а также увеличивает производительность, поскольку сцена требует больше уникальных экземпляров материалов. Здесь также можно использовать цвета вершин, но это потребует создания уникальных копий сетки для каждого цвета, что также приведёт к снижению производительности.

Uniform-переменная задаётся для каждого экземпляра GeometryInstance3D, а не для каждого экземпляра материала. Учитывайте это при работе с сетками, которым назначено несколько материалов, или при настройке MultiMesh.

shader_type spatial;

// Provide a hint to edit as a color. Optionally, a default value can be provided.
// If no default value is provided, the type's default is used (e.g. opaque black for colors).
instance uniform vec4 my_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);

void fragment() {
    ALBEDO = my_color.rgb;
}

После сохранения шейдера вы можете изменить значение uniform-переменной для каждого экземпляра с помощью инспектора:

Установка значения uniform-переменной для каждого экземпляра в разделе инспектора GeometryInstance3D

Установка значения uniform-переменной для каждого экземпляра в разделе инспектора GeometryInstance3D

Единые значения для каждого экземпляра также можно задать во время выполнения с помощью метода set_instance_shader_parameter на узле, который наследует от GeometryInstance3D:

$MeshInstance3D.set_instance_shader_parameter("my_color", Color(0.3, 0.6, 1.0))

При использовании uniform-переменной для каждого экземпляра есть некоторые ограничения, о которых вы должны знать:

  • Per-instance uniforms do not support textures or arrays, only regular scalar and vector types. As a workaround, you can pass a texture array as a regular uniform, then pass the index of the texture to be drawn using a per-instance uniform.

Примечание

In GLSL versions before 4.0 (i.e. GLSL 3.3 and lower), you cannot directly index a texture array using a per-instance uniform, as sampler arrays can only be indexed by compile-time constant expressions. This affects shaders compiled with the Compatibility renderer.

If you are affected, use the switch statement to select the texture:

uniform sampler2D texture_array[2];
instance uniform int texture_index;

void fragment() {
    vec4 color;
    switch (texture_index) {
        case 0:
            color = texture(texture_array[0], UV);
            break;
        case 1:
            color = texture(texture_array[1], UV);
            break;
    }

    COLOR = color;
}
  • Максимальный практический предел - 16 экземпляров uniform-переменной на шейдер.

  • Если в сетке используется несколько материалов, параметры первого найденного материала сетки "победят" последующие, если только они не имеют одинаковое имя, индекс и тип. В этом случае все параметры будут затронуты корректно.

  • Если вы столкнулись с описанной выше ситуацией, вы можете избежать столкновений, указав вручную индекс (0-15) uniform-переменной экземпляра с помощью подсказки instance_index:

instance uniform vec4 my_color : source_color, instance_index(5);

Установка униформ из кода

Вы можете задать униформы из GDScript, используя метод set_shader_parameter():

material.set_shader_parameter("some_value", some_value)

material.set_shader_parameter("colors", [Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)])

Примечание

Первый аргумент set_shader_parameter() — это имя юниформ-переменной в шейдере. Оно должно точно совпадать с именем юниформ-переменной в шейдере, иначе оно не будет распознано.

GDScript использует переменные, отличающиеся от GLSL, поэтому при передаче переменных из GDScript в шейдер Godot автоматически преобразует тип. В таблице ниже указаны соответствующие типы:

Типы GLSL

Типы GDScript

Примечания

bool

bool

bvеc2

int

Побитовая упаковка int, где бит 0 (LSB) соответствует x.

Например, bvec2 из (bx, by) можно создать следующим образом:

bvec2_input: int = (int(bx)) | (int(by) << 1)

bvеc3

int

Побитовая упаковка int, где бит 0 (LSB) соответствует x.

bveс4

int

Побитовая упаковка int, где бит 0 (LSB) соответствует x.

int

int

ivеc2

Vector2i

ivеc3

Vector3i

iveс4

Vector4i

uіnt

int

uvеc2

Vector2i

uvеc3

Vector3i

uvеc4

Vector4i

float

float

vеc2

Vector2

vеc3

Vector3, Color

Когда используется Color (цвет), он интерпретируется как (r, g, b).

vеc4

Vector4, Color, Rect2, Plane (Плоскость), Quaternion (Кватернион)

Когда используется Color (цвет), он интерпретируется как (r, g, b, a).

Если используется Rect2 (прямоугольник), он будет интерпретирован как (position.x, position.y, size.x, size.y).

Когда используется Plane (плоскость), это будет интерпретироваться как (normal.x, normal.y, normal.z, d).

mаt2

Transform2D

mаt3

Basis

mаt4

Projection, Transform3D

При использовании Transform3D вектор w устанавливается на идентичность.

samplеr2D

Texture2D

isamplеr2D

Texture2D

usamplеr2D

Texture2D

sampler2DArray

Texture2DArray

isampler2DArray

Texture2DArray

usampler2DArray

Texture2DArray

sampler3D

Texture3D

isampler3D

Texture3D

usampler3D

Texture3D

samplerCubе

Cubemap

Инструкции по импорту кубических карт для использования в Godot см. в Изменение типа импорта.

samplerCubeArray

CubemapArray

Поддерживается только в Forward+ и Mobile, но не в режиме Compatibility.

samplerExternalOES

ExternalTexture

Поддерживается только на платформе Android/Compatibility.

Примечание

Будьте осторожны при установке юниформ шейдеров из GDScript, поскольку при несовпадении типов ошибка не возникнет. Ваш шейдер просто будет вести себя неопределённо. В частности, это включает установку int/float (64-бит) из GDScript в int/float (32-бит) из языка шейдеров Godot. Это может привести к непредвиденным последствиям, если требуется высокая точность.

Единые пределы

Существует ограничение на общий размер юниформ-объектов шейдера, которые можно использовать в одном шейдере. На большинстве настольных платформ этот предел составляет 65536 байт, или 4096 юниформ-объектов vec4. На мобильных платформах ограничение обычно составляет 16384 байт, или 1024 юниформ-объекта vec4. Векторные юниформ-объекты меньше vec4, такие как vec2 или vec3, дополняются до размера vec4. Скалярные юниформ-объекты, такие как int или float, не дополняются, а bool дополняется до размера int.

Массивы учитываются по общему размеру их содержимого. Если вам нужен однородный массив, превышающий этот предел, рассмотрите вариант упаковки данных в текстуру, поскольку содержимое текстуры не учитывается при подсчёте этого предела, а учитывается только размер однородных элементов сэмплера.

Встроенные переменные

Доступно большое количество встроенных переменных, таких как UV, COLOR и VERTEX. Набор доступных переменных зависит от типа шейдера (spatial, canvas_item, particle и т. д.) и используемой функции (vertex, fragment, light, start, process, sky или fog). Список доступных встроенных переменных см. на соответствующих страницах:

Встроенные функции

Поддерживается большое количество встроенных функций, соответствующих GLSL ES 3.0. Подробности см. на странице Built-in functions.