Руководство по стилю шейдеров
В этом руководстве по стилю перечислены соглашения, необходимые для написания элегантных шейдеров. Цель — побудить писать чистый, читабельный код и обеспечить единообразие в проектах, обсуждениях и учебных пособиях. Надеемся, что это также будет способствовать разработке инструментов автоматического форматирования.
Поскольку язык шейдеров Godot близок к языкам программирования на языке C и GLSL, данное руководство основано на собственном формате GLSL Godot. Примеры 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 без маркера последовательности байтов. (редактор по умолчанию)
Используйте табы вместо пробелов для отступов. (редактор по умолчанию)
Отступ
Каждый уровень отступа должен быть на одну табуляцию больше, чем содержащий его блок.
Хорошо:
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 Style" <https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_(OTBS)>`_ который рекомендует поместить скобку, связанную с управляющим оператором, в ту же строку. Всегда используйте фигурные скобки для операторов, даже если они охватывают только одну строку. Это облегчает их рефакторинг и позволяет избежать ошибок при добавлении большего количества строк в оператор 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 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
}
Applying formatting automatically
To automatically format shader files, you can use
clang-format on one or several
.gdshader files, as the syntax is close enough to a C-style language.
However, the default style in clang-format doesn't follow this style guide,
so you need to save this file as .clang-format in your project's root folder:
BasedOnStyle: LLVM
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
AlignTrailingComments:
Kind: Never
OverEmptyLines: 0
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: Inline
BreakConstructorInitializers: AfterColon
ColumnLimit: 0
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentWidth: 4
InsertBraces: true
KeepEmptyLinesAtTheStartOfBlocks: false
RemoveSemicolon: true
SpacesInLineCommentPrefix:
Minimum: 0 # We want a minimum of 1 for comments, but allow 0 for disabled code.
Maximum: -1
TabWidth: 4
UseTab: Always
While in the project root, you can then call clang-format -i path/to/shader.gdshader
in a terminal to format a single shader file, or clang-format -i path/to/folder/*.gdshader
to format all shaders in a folder.
Порядок кода
Мы предлагаем организовать код шейдера следующим образом:
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
Мы оптимизировали порядок, чтобы упростить чтение кода сверху вниз и помочь разработчикам, которые читают этот код впервые, понять, как он работает и избежать ошибок, связанных с порядком объявления переменных.
Этот порядок кодов следует двум практическим правилам:
Сначала метаданные и свойства, затем методы.
"Public" предшествует "private". В контексте языка шейдеров "private" относится к тому, что пользователь может легко изменить (uniforms).
Локальные переменные
Объявляйте локальные переменные как можно ближе к их первому использованию. Это облегчает отслеживание кода, не требуя слишком большой прокрутки, чтобы найти, где была объявлена переменная.
Интервал в комментариях
Обычные комментарии должны начинаться с пробела, но не с кода, который вы комментируете. Это помогает отличить текстовые комментарии от отключенного кода.
Хорошо:
Плохо:
Не используйте синтаксис многострочных комментариев, если ваш комментарий может поместиться на одной строке:
/* This is another comment. */Примечание
В редакторе шейдеров, чтобы сделать выделенный код комментарием (или раскомментировать его), нажмите Ctrl + K. Эта функция добавляет или удаляет
//в начале выделенных строк.