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.

着色器风格指南

本风格指南列出了编写优雅着色器的规范。其目的是鼓励编写干净、可读的代码,并促进各项目、讨论和教程的一致性。希望这也能支持自动格式化工具的发展。

Since the Godot shader language is close to C-style languages and GLSL, this guide is inspired by Godot's own GLSL formatting. You can view an example of a GLSL file in Godot's source code here.

风格指南并不是硬性的规则手册。有时,您可能无法应用下面的一些规范。当这种情况发生时,请使用你最好的判断,并询问其他开发人员的见解。

一般来说,在项目和团队中保持代码的一致性比一板一眼地遵循本指南更为重要。

备注

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.shader

uniform float brightness = 0.8;
uniform float contrast = 1.5;
uniform float saturation = 1.8;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

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样式" <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 comment.
//return;

糟糕的 :

//This is a comment.
// return;

如果你的注释可以容纳在单行上, 就不要使用多行注释语法:

/* This is another comment. */

备注

在着色器编辑器中, 要使选定的代码成为注释或取消注释, 按 Ctrl + K . 该功能在所选行的开头添加或删除 // .

空格

在运算符周围和逗号后面总是使用一个空格. 另外, 避免在函数调用中使用多余的空格.

良好的 :

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的数字. 这样可以更容易区分浮点数和整数, 以及区分大于1和小于1的数字.

良好的 :

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

糟糕的 :

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

访问向量成员

如果向量包含颜色, 在访问向量成员时使用 r , g , ba . 如果向量中不包含颜色, 则使用 x , y , zw . 这可以让那些阅读你的代码的人更好地理解基础数据的含义.

良好的 :

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

糟糕的 :

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

命名约定

这些命名约定遵循 Godot 引擎风格. 打破这些都会使你的代码与内置的命名约定冲突, 导致风格不一致的代码.

函数与变量

函数与变量使用 snake_case 命名:

void some_function() {
     float some_variable = 0.5;
}

常量

使用 CONSTANT_CASE, 全部大写, 用下划线(_)分隔单词 :

const float GOLDEN_RATIO = 1.618;

Preprocessor directives

Shader preprocessor directives should be written in CONSTANT__CASE. Directives should be written without any indentation before them, even if nested within a function.

To preserve the natural flow of indentation when shader errors are printed to the console, extra indentation should not be added within #if, #ifdef or #ifndef blocks:

良好的 :

#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. “公共”在“私有”之前。在着色器语言的语境中,“公共”指的是用户可以轻易调整的东西(uniform)。

局部变量

声明局部变量的位置离首次使用它的位置越近越好. 这让人更容易跟上代码的思路, 而不需要上下翻找该变量的声明位置.