Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Препроцесор шейдерів
Навіщо використовувати препроцесор шейдерів?
У мовах програмування препроцесор дозволяє змінювати код до того, як його прочитає компілятор. На відміну від компілятора, препроцесор не піклується про те, чи дійсний синтаксис попередньо обробленого коду. Препроцесор завжди виконує те, що вказують директиви. Директива — це оператор, що починається символом решетки (#). Це не ключове слово мови шейдерів (наприклад, if або for), а особливий вид токена в мові.
Щоб уникнути повторень та покращити повторне використання коду, ви можете використовувати препроцесор шейдерів у текстових шейдерах. Синтаксис подібний до того, що підтримує більшість компіляторів шейдерів GLSL (що, у свою чергу, подібне до препроцесора C/C++).
Примітка
Препроцесор шейдерів недоступний у visual shaders. Якщо вам потрібно додати оператори препроцесора до візуального шейдера, ви можете перетворити його на текстовий шейдер за допомогою параметра Перетворити на шейдер у спадному списку ресурсів інспектора VisualShader. Це перетворення є односторонньою операцією; текстові шейдери не можна перетворити назад на візуальні шейдери.
Директиви
Загальний синтаксис
Директиви препроцесора не використовують дужки (
{}), але можуть використовувати круглі дужки.Директиви препроцесора ніколи не закінчуються крапкою з комою (за винятком
#define, де це дозволено, але потенційно небезпечно).Директиви препроцесора можуть охоплювати кілька рядків, закінчуючи кожен рядок зворотною скісною рискою (
\). Перший розрив рядка без зі зворотною скісною рискою завершує оператор препроцесора.
#визначити
Синтаксис: #define <ідентифікатор> [код_заміни].
Визначає ідентифікатор після цієї директиви як макрос і замінює всі його послідовні входження кодом заміни, наданим у шейдері. Заміна виконується на основі «цілих слів», що означає, що заміна не виконується, якщо рядок є частиною іншого рядка (без пробілів або операторів, що розділяють його).
Визначення із замінами також можуть мати один або більше аргументів, які потім можна передати під час посилання на визначення (подібно до виклику функції).
Якщо код заміни не визначено, ідентифікатор можна використовувати лише з директивами #ifdef або #ifndef.
Якщо символ конкатенації (##) присутній у коді заміни, тоді він буде видалений після вставки макросу разом із будь-яким пробілом навколо нього та об’єднає навколишні слова та аргументи в новий маркер.
uniform sampler2D material0;
#define SAMPLE(N) vec4 tex##N = texture(material##N, UV)
void fragment() {
SAMPLE(0);
ALBEDO = tex0.rgb;
}
Порівняно з константами (const CONSTANT = value;), #define можна використовувати будь-де в шейдері (зокрема, у однорідних підказках). #define також можна використовувати для вставки довільного коду шейдера в будь-яке місце, тоді як константи не можуть цього зробити.
shader_type spatial;
// Notice the lack of semicolon at the end of the line, as the replacement text
// shouldn't insert a semicolon on its own.
// If the directive ends with a semicolon, the semicolon is inserted in every usage
// of the directive, even when this causes a syntax error.
#define USE_MY_COLOR
#define MY_COLOR vec3(1, 0, 0)
// Replacement with arguments.
// All arguments are required (no default values can be provided).
#define BRIGHTEN_COLOR(r, g, b) vec3(r + 0.5, g + 0.5, b + 0.5)
// Multiline replacement using backslashes for continuation:
#define SAMPLE(param1, param2, param3, param4) long_function_call( \
param1, \
param2, \
param3, \
param4 \
)
void fragment() {
#ifdef USE_MY_COLOR
ALBEDO = MY_COLOR;
#endif
}
Визначення #define для ідентифікатора, який уже визначено, призводить до помилки. Щоб запобігти цьому, використовуйте #undef <ідентифікатор>.
#undef
Синтаксис: #undef ідентифікатор
Директиву #undef можна використовувати для скасування попередньо визначеної директиви #define:
#define MY_COLOR vec3(1, 0, 0)
vec3 get_red_color() {
return MY_COLOR;
}
#undef MY_COLOR
#define MY_COLOR vec3(0, 1, 0)
vec3 get_green_color() {
return MY_COLOR;
}
// Like in most preprocessors, undefining a define that was not previously defined is allowed
// (and won't print any warning or error).
#undef THIS_DOES_NOT_EXIST
Без #undef у наведеному вище прикладі виникла б помилка перевизначення макросу.
#if
Синтаксис: #if <умова>
Директива #if перевіряє, чи виконано умову. Якщо він має ненульове значення, блок коду включається, інакше він пропускається.
Для правильного обчислення умова має бути виразом, що дає простий результат з плаваючою комою, ціле число або логічне число. Може бути кілька блоків умови, з'єднаних операторами && (І) або | (АБО). Вона може бути продовжена блоком #else, але повинна завершуватися директивою #endif.
#define VAR 3
#define USE_LIGHT 0 // Evaluates to `false`.
#define USE_COLOR 1 // Evaluates to `true`.
#if VAR == 3 && (USE_LIGHT || USE_COLOR)
// Condition is `true`. Include this portion in the final shader.
#endif
Використовуючи функцію препроцесора defined(), ви можете перевірити, чи переданий ідентифікатор визначено за допомогою директиви #define, розміщеної над цією директивою. Це корисно для створення кількох версій шейдерів в одному файлі. Його можна продовжити блоком #else, але він повинен завершуватися директивою #endif.
Результат функції defined() можна звести нанівець за допомогою символу ! (логічне НІ) перед ним. Це можна використати, щоб перевірити, чи визначення не встановлено.
#define USE_LIGHT
#define USE_COLOR
// Correct syntax:
#if defined(USE_LIGHT) || defined(USE_COLOR) || !defined(USE_REFRACTION)
// Condition is `true`. Include this portion in the final shader.
#endif
Будьте обережні, оскільки defined() має заключати лише один ідентифікатор у круглі дужки, ніколи більше:
// Incorrect syntax (parentheses are not placed where they should be):
#if defined(USE_LIGHT || USE_COLOR || !USE_REFRACTION)
// This will cause an error or not behave as expected.
#endif
Порада
У редакторі шейдерів гілки препроцесора, які мають значення false (і тому виключені з остаточно скомпільованого шейдера), відображатимуться сірим кольором. Це не стосується операторів if під час виконання.
#попередній процесор if проти оператора if: застереження щодо продуктивності
shading language підтримує оператори if під час виконання:
uniform bool USE_LIGHT = true;
if (USE_LIGHT) {
// This part is included in the compiled shader, and always run.
} else {
// This part is included in the compiled shader, but never run.
}
Якщо уніформа ніколи не змінюється, це поводиться так само, як наступне використання оператора препроцесора #if:
#define USE_LIGHT
#if defined(USE_LIGHT)
// This part is included in the compiled shader, and always run.
#else
// This part is *not* included in the compiled shader (and therefore never run).
#endif
Однак варіант #if може бути швидшим у певних сценаріях. Це пов’язано з тим, що всі гілки часу виконання в шейдері все ще скомпільовані, і змінні в цих гілках все ще можуть займати місце в реєстрі, навіть якщо вони ніколи не запускаються на практиці.
Сучасні графічні процесори є досить ефективними у виконанні "статичного" розгалуження. "Статичне" розгалуження стосується операторів if, де всі пікселі/вершини оцінюються як однаковий результат у заданому виклику шейдера. Однак велика кількість VGPRs (що може бути спричинено занадто великою кількістю розгалужень) все ще може значно уповільнити виконання шейдера.
#elif
Директива #elif розшифровується як «else if» і перевіряє передану умову, якщо вищезгаданий #if оцінено як false. #elif можна використовувати тільки в #if блоці. Можна використовувати декілька операторів #elif після оператора #if.
#define VAR 2
#if VAR == 0
// Not included.
#elif VAR == 1
// Not included.
#elif VAR == 2
// Condition is `true`. Include this portion in the final shader.
#else
// Not included.
#endif
Як і у випадку з #if, можна використовувати функцію препроцесора defined():
#define SHADOW_QUALITY_MEDIUM
#if defined(SHADOW_QUALITY_HIGH)
// High shadow quality.
#elif defined(SHADOW_QUALITY_MEDIUM)
// Medium shadow quality.
#else
// Low shadow quality.
#endif
#ifdef
Синтаксис: #ifdef <ідентифікатор>
Це скорочення від #if defined(...). Перевіряє, чи переданий ідентифікатор визначено директивою #define, розміщеною над цією директивою. Це корисно для створення кількох версій шейдерів в одному файлі. Його можна продовжити блоком #else, але він повинен завершуватися директивою #endif.
#define USE_LIGHT
#ifdef USE_LIGHT
// USE_LIGHT is defined. Include this portion in the final shader.
#endif
Процесор не підтримує #elifdef як ярлик для #elif defined(...). Замість цього використовуйте такі ряди #ifdef і #else, якщо вам потрібно більше ніж дві гілки:
#define SHADOW_QUALITY_MEDIUM
#ifdef SHADOW_QUALITY_HIGH
// High shadow quality.
#else
#ifdef SHADOW_QUALITY_MEDIUM
// Medium shadow quality.
#else
// Low shadow quality.
#endif // This ends `SHADOW_QUALITY_MEDIUM`'s branch.
#endif // This ends `SHADOW_QUALITY_HIGH`'s branch.
#ifndef
Синтаксис: #ifndef <ідентифікатор>
Це скорочення для #if !defined(...). Подібно до #ifdef, але перевіряє, чи переданий ідентифікатор не визначений #define перед цією директивою.
Це повна протилежність #ifdef; він завжди збігатиметься в ситуаціях, коли #ifdef ніколи не збігатиметься, і навпаки.
#define USE_LIGHT
#ifndef USE_LIGHT
// Evaluates to `false`. This portion won't be included in the final shader.
#endif
#ifndef USE_COLOR
// Evaluates to `true`. This portion will be included in the final shader.
#endif
#else
Синтаксис: #else
Визначає необов’язковий блок, який включається, коли попередньо визначена директива #if, #elif, #ifdef або #ifndef має значення false.
shader_type spatial;
#define MY_COLOR vec3(1.0, 0, 0)
void fragment() {
#ifdef MY_COLOR
ALBEDO = MY_COLOR;
#else
ALBEDO = vec3(0, 0, 1.0);
#endif
}
#endif
Синтаксис: #endif
Використовується як термінатор для директив #if, #ifdef, #ifndef або наступних директив #else.
#помилка
Синтаксис: #помилка <повідомлення>
Директива #error змушує препроцесор видавати помилку з додатковим повідомленням. Наприклад, це корисно при використанні в блоці #if, щоб забезпечити суворе обмеження визначеного значення.
#define MAX_LOD 3
#define LOD 4
#if LOD > MAX_LOD
#error LOD exceeds MAX_LOD
#endif
#include
Синтаксис: #include "path"
Директива #include включає весь вміст файлу включення шейдера в шейдері. "path" може бути абсолютним res:// шляхом або відносно поточного файлу шейдера. Відносні шляхи дозволені лише в шейдерах, збережених у файлах .gdshader або .gdshaderinc, тоді як абсолютні шляхи можна використовувати в шейдерах, вбудованих у файл сцени/ресурсу.
Ви можете створити нові включення шейдерів, скориставшись пунктом меню Файл > Створити шейдерне включення редактора шейдерів або створивши новий ресурс ShaderInclude у доку FileSystem.
Включення шейдерів можна включити з будь-якого шейдера або іншого включення шейдера в будь-яку точку файлу.
У разі включення шейдера до глобальної області шейдера рекомендується робити це після початкового оператора shader_type.
Ви також можете включити функцію шейдера, що включає всередину тіла. Будь ласка, зверніть увагу, що редактор шейдерів, імовірно, повідомлятиме про помилки для коду вашого шейдера, оскільки він може бути недійсним поза контекстом, для якого він був написаний. Ви можете або ігнорувати ці помилки (шейдер все одно компілюватиметься нормально), або ви можете загорнути включення в блок #ifdef, який перевіряє визначення з вашого шейдера.
#include корисний для створення бібліотек допоміжних функцій (або макросів) і зменшення дублювання коду. Використовуючи #include, будьте обережні щодо колізій імен, оскільки перевизначення функцій або макросів заборонено.
#include підпадає під кілька обмежень:
Можна включити лише ресурси включення шейдерів (закінчуються на
.gdshaderinc). Файли.gdshaderне можуть бути включені іншим шейдером, але файл.gdshaderincможе містити інші файли.gdshaderinc.Циклічні залежності не дозволені та призведуть до помилки.
Щоб уникнути нескінченної рекурсії, глибина включення обмежена 25 кроками.
Приклад включеного файлу шейдера:
// fancy_color.gdshaderinc
// While technically allowed, there is usually no `shader_type` declaration in include files.
vec3 get_fancy_color() {
return vec3(0.3, 0.6, 0.9);
}
Приклад базового шейдера (з використанням включеного файлу, який ми створили вище):
// material.gdshader
shader_type spatial;
#include "res://fancy_color.gdshaderinc"
void fragment() {
// No error, as we've included a definition for `get_fancy_color()` via the shader include.
COLOR = get_fancy_color();
}
#pragma
Синтаксис: #прагма значення
Директива #pragma надає додаткову інформацію препроцесору або компілятору.
Наразі він може мати лише одне значення: disable_preprocessor. Якщо вам не потрібен препроцесор, використовуйте цю директиву, щоб прискорити компіляцію шейдера, виключивши крок препроцесора.
#pragma disable_preprocessor
#if USE_LIGHT
// This causes a shader compilation error, as the `#if USE_LIGHT` and `#endif`
// are included as-is in the final shader code.
#endif
Вбудований визначає
Поточний рендерер
Починаючи з Godot 4.4, ви можете перевірити, який рендерер зараз використовується за допомогою вбудованих визначень CURRENT_RENDERER, RENDERER_COMPATIBILITY, RENDERER_MOBILE і RENDERER_FORWARD_PLUS:
CURRENT_RENDERERмає значення0,1або2залежно від поточного засобу відтворення.RENDERER_COMPATIBILITYзавжди0.RENDERER_MOBILEє завжди1.RENDERER_FORWARD_PLUSє завжди2.
Як приклад, цей шейдер встановлює для ALBEDO інший колір у кожному рендерері:
shader_type spatial;
void fragment() {
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
ALBEDO = vec3(0.0, 0.0, 1.0);
#elif CURRENT_RENDERER == RENDERER_MOBILE
ALBEDO = vec3(1.0, 0.0, 0.0);
#else // CURRENT_RENDERER == RENDERER_FORWARD_PLUS
ALBEDO = vec3(0.0, 1.0, 0.0);
#endif
}