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.

Керівництво по стилю шейдерів

У цьому посібнику зі стилю перераховано правила написання елегантних шейдерів. Мета полягає в тому, щоб заохотити писати чистий, читабельний код і сприяти узгодженості між проектами, обговореннями та навчальними посібниками. Сподіваємось, це також підтримає розробку інструментів автоматичного форматування.

Оскільки мова шейдерів Godot близька до мов у стилі C і GLSL, цей посібник натхненний власним форматуванням Godot GLSL. Ви можете переглянути приклади файлів GLSL у вихідному коді Godot тут.

Посібники зі стилів не розглядаються як жорсткі правила. Часом ви не зможете застосувати деякі наведені нижче вказівки. Коли це станеться, використовуйте найкраще рішення та запитайте в колег-розробників їх думку.

Загалом, стабільне дотримання визначеного стилю у ваших проєктах, та серед вашої команди, важливіше, ніж дотримання цього посібника.

Примітка

Вбудований редактор шейдерів Godot використовує багато цих умов за замовчуванням. Нехай це допоможе вам.

Ось повний приклад шейдера на основі цих вказівок:

shader_type canvas_item;
// Screen-space shader to adjust a 2D scene's brightness, contrast
// and saturation. Taken from
// https://github.com/godotengine/godot-demo-projects/blob/master/2d/screen_space_shaders/shaders/BCS.gdshader

uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
uniform float brightness = 0.8;
uniform float contrast = 1.5;
uniform float saturation = 1.8;

void fragment() {
    vec3 c = textureLod(screen_texture, SCREEN_UV, 0.0).rgb;

    c.rgb = mix(vec3(0.0), c.rgb, brightness);
    c.rgb = mix(vec3(0.5), c.rgb, contrast);
    c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation);

    COLOR.rgb = c;
}

Форматування

Кодування та спеціальні символи

  • Використовуйте символи зміни рядка (LF) для переривання рядків, не CRLF, чи CR. (за замовчуванням редактора)

  • Використовуйте символи зміни рядка в кінці кожного файлу. (за замовчуванням редактора)

  • Використовуйте кодування UTF-8 без маркера порядку байтів. (за замовчування редактора)

  • Використовуйте для відступів Tab замість пробілів. (за замовчуванням редактора)

Відступ

Кожен рівень відступу має бути на одну табуляцію більшим за блок, що його містить.

Правильно:

void fragment() {
    COLOR = vec3(1.0, 1.0, 1.0);
}

Неправильно:

void fragment() {
        COLOR = vec3(1.0, 1.0, 1.0);
}

Використовуйте 2 відступи, щоб відрізнити рядки продовження від звичайних блоків коду.

Правильно:

vec2 st = vec2(
        atan(NORMAL.x, NORMAL.z),
        acos(NORMAL.y));

Неправильно:

vec2 st = vec2(
    atan(NORMAL.x, NORMAL.z),
    acos(NORMAL.y));

Розриви рядків та порожні рядки

Для загального правила відступів дотримуйтесь "Стиль 1TBS" який рекомендує розмістити фігурну дужку, пов’язану з керуючим оператором, у тому самому рядку. Завжди використовуйте дужки для операторів, навіть якщо вони тільки охопити один рядок. Це полегшує їх рефакторинг і дозволяє уникнути помилок під час додавання додаткових рядків до оператора if чи подібного.

Правильно:

void fragment() {
    if (true) {
        // ...
    }
}

Неправильно:

void fragment()
{
    if (true)
        // ...
}

Порожні рядки

Визначення функцій оточіть одним (і тільки одним) порожнім рядком:

void do_something() {
    // ...
}

void fragment() {
    // ...
}

Використовуйте один (і тільки один) порожній рядок у функціях для розділення логічних розділів.

Довжина рядка

Зберігайте в окремих рядках коду не більше 100 символів.

Якщо можете, намагайтеся тримати рядки менше 80 символів. Це допомагає читати код на маленьких дисплеях і з двома шейдерами, відкритими поруч у зовнішньому текстовому редакторі. Наприклад, при перегляді диференціальної ревізії.

Одне твердження на рядок

Ніколи не поєднуйте кілька тверджень в одному рядку.

Правильно:

void fragment() {
    ALBEDO = vec3(1.0);
    EMISSION = vec3(1.0);
}

Неправильно:

void fragment() {
    ALBEDO = vec3(1.0); EMISSION = vec3(1.0);
}

Єдиним винятком із цього правила є потрійний оператор:

void fragment() {
     bool should_be_white = true;
     ALBEDO = should_be_white ? vec3(1.0) : vec3(0.0);
 }

Пробіл коментаря

Регулярні коментарі повинні починатися з пробілу, але не код, який ви закоментовуєте. Це допомагає відрізняти текстові коментарі від відключеного коду.

Правильно:

// This is a comment.
//return;

Неправильно:

//This is a comment.
// return;

Не використовуйте синтаксис багаторядкового коментаря, якщо ваш коментар може поміститися в один рядок:

/* This is another comment. */

Примітка

У редакторі шейдерів, щоб зробити вибраний код коментарем (або розкоментувати його), натисніть Ctrl + K. Ця функція додає або видаляє // на початку вибраних рядків.

Коментарі до документації

Використовуйте такий формат для коментарів до документації над формою, з двома зірочками на початку (/**) і наступними зірочками в кожному рядку:

/**
 * 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;

Ці коментарі з’являться під час наведення курсора на властивість в інспекторі. Якщо ви не бажаєте, щоб коментар був видимим в інспекторі, замість цього використовуйте стандартний синтаксис коментаря (// ... або /* ... */ лише з однією зірочкою на початку).

Вільний простір

Завжди використовуйте один пробіл навколо операторів і після ком. Також уникайте зайвих пробілів у викликах функцій.

Правильно:

COLOR.r = 5.0;
COLOR.r = COLOR.g + 0.1;
COLOR.b = some_function(1.0, 2.0);

Неправильно:

COLOR.r=5.0;
COLOR.r = COLOR.g+0.1;
COLOR.b = some_function (1.0,2.0);

Не використовуйте пробіли для вирівнювання виразів по вертикалі:

ALBEDO.r   = 1.0;
EMISSION.r = 1.0;

Числа з плаваючою комою

Завжди вказуйте принаймні одну цифру як для цілої, так і для дробової частини. Це полегшує розрізнення чисел з плаваючою комою від цілих чисел, а також розрізнення чисел, більших за 1, від менших за 1.

Правильно:

void fragment() {
    ALBEDO.rgb = vec3(5.0, 0.1, 0.2);
}

Неправильно:

void fragment() {
    ALBEDO.rgb = vec3(5., .1, .2);
}

Доступ до елементів вектора

Використовуйте r, g, b і a під час доступу до елементів вектора, якщо він містить колір. Якщо вектор містить щось інше, крім кольору, використовуйте x, y, z і w. Це дозволяє тим, хто читає ваш код, краще зрозуміти, що представляють основні дані.

Правильно:

COLOR.rgb = vec3(5.0, 0.1, 0.2);

Неправильно:

COLOR.xyz = vec3(5.0, 0.1, 0.2);

Конвенції іменування

Ці конвенції іменування відповідають стилю Godot Engine. Якщо їх порушити, ваш код зіткнеться із вбудованими конвенціями іменування, що призведе до непослідовного коду.

Функції та змінні

Використовуйте snake_case (всі слова з маленької букви відокремлені підкресленням) для назв функцій та змінних:

void some_function() {
     float some_variable = 0.5;
}

Константи

Записуйте константи в стилі CONSTANT_CASE (всі букви написані з великим регістром, слова відокремлені підкресленням (_)):

const float GOLDEN_RATIO = 1.618;

Директиви препроцесора

Директиви Препроцесор шейдерів мають бути записані в CONSTANT__CASE. Директиви слід писати без будь-яких відступів перед ними, навіть якщо вони вкладені у функцію.

Щоб зберегти природний потік відступів, коли помилки шейдера друкуються на консолі, додаткові відступи не слід додавати до блоків #if, #ifdef або #ifndef:

Правильно:

#define HEIGHTMAP_ENABLED

void fragment() {
    vec2 position = vec2(1.0, 2.0);

#ifdef HEIGHTMAP_ENABLED
    sample_heightmap(position);
#endif
}

Неправильно:

#define heightmap_enabled

void fragment() {
    vec2 position = vec2(1.0, 2.0);

    #ifdef heightmap_enabled
        sample_heightmap(position);
    #endif
}

Порядок коду

Ми пропонуємо організувати код шейдера таким чином:

01. shader type declaration
02. render mode declaration
03. // docstring

04. uniforms
05. constants
06. varyings

07. other functions
08. vertex() function
09. fragment() function
10. light() function

Ми оптимізували порядок, щоб полегшити читання коду зверху вниз, щоб допомогти розробникам, які вперше читають код, зрозуміти, як він працює, та уникнути помилок, пов'язаних із порядком оголошення змінних.

Цей порядок кодів відповідає двом практичним правилам:

  1. Спочатку метадані та властивості, а потім методи.

  2. "Публічний" стоїть перед "приватним". У контексті мови шейдерів «загальнодоступний» означає те, що користувач легко регулює (уніформа).

Локальні змінні

Оголошуйте локальні змінні якомога ближче до їх першого використання. Це полегшує відстеження коду, без необхідності прокручувати занадто багато, щоб знайти де оголошено змінну.