Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

着色器风格指南

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

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.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 样式”,建议将与控制语句关联的大括号放在同一行上。始终对语句使用大括号,即使只占一行。这样更易于重构,并且能够避免在向 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;

预处理器指令

着色器预处理器 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)。

局部变量

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