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...
着色器风格指南
本风格指南列出了编写优雅着色器的规范。其目的是鼓励编写干净、可读的代码,并促进各项目、讨论和教程的一致性。希望这也能支持自动格式化工具的发展。
由于 Godot 的着色器(Shader)语言与 C 风格语言以及 GLSL 非常接近,因此本指南的格式规范是参考了 Godot 自身的 GLSL 格式来制定的。如果你想看具体的例子,可以在 here 查看 Godot 源码中的 GLSL 文件。
风格指南并不是硬性的规则教条,有些情况下,你可能无法施行下面的一些规范。如果这种情况发生在你身上,最好自行进行选择,并询问其他开发人员的见解。
一般来说,在项目和团队中保持代码风格的一致性,比一板一眼地遵循本指南更为重要。
备注
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);
}
文档注释
在 uniform 变量上方编写文档注释时,请使用以下格式:必须以 两个 星号( /** )开头,并且每一行的开头都要跟上星号:
/**
* 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;
当你把鼠标悬停在检查器(Inspector)的某个属性上时,这些注释就会显示出来。如果你不希望注释在检查器里出现,那就使用标准的注释语法(也就是用(// ... 或者只带一个星号的 /* ... */ )。
空格
在运算符周围和逗号后面总是使用一个空格. 另外, 避免在函数调用中使用多余的空格.
规范示例 :
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 , 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 引擎风格,不遵循这些规定都会使你的代码与内置的命名规定相冲突,导致风格不一致的代码.
函数与变量
函数与变量使用 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
}
自动进行格式化
想要自动格式化着色器文件,你可以对单个或多个 .gdshader 文件使用 clang-format ,因为它的语法和 C 风格的语言非常接近。
不过,clang-format 的默认样式并没有遵循这份风格指南,所以你需要在项目根目录下保存一个名为 .clang-format 的文件(来覆盖默认设置):
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
在项目根目录下,你可以在终端中调用 clang-format -i path/to/shader.gdshader 来格式化单个着色器文件,或者使用 clang-format -i path/to/folder/*.gdshader 来格式化某个文件夹内的所有着色器。
代码顺序
我们建议以这种方式组织着色器代码:
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
我们优化了代码顺序,从上往下阅读代码更加容易,帮助第一次阅读代码的开发人员了解代码的工作原理,同时避免与变量声明顺序相关的错误。
此代码顺序遵循两个经验法则:
先是元数据和属性, 然后是方法.
“公共”在“私有”之前。在着色器语言的语境中,“公共”指的是用户可以轻易调整的东西(uniform)。
局部变量
局部变量的声明位置离首次使用该局部变量的位置越近越好,让人更容易跟上代码的思路,而不需要上翻下找该变量的声明位置。
注释间距
普通注释开头应该留一个空格,但如果是为了停用代码而将其注释掉则不需要留。这样可以用来区分文本注释和停用的代码。
规范示例 :
不规范示例 :
如果你的注释可以容纳在单行上, 就不要使用多行注释语法:
/* This is another comment. */备注
在着色器编辑器中, 要使选定的代码成为注释或取消注释, 按 Ctrl + K . 该功能在所选行的开头添加或删除
//.