Up to date

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

編輯器風格指南

本風格指南列出了編寫優雅著色器的規範。其目的是鼓勵編寫乾淨、可讀的程式碼,並促進各專案、討論和教學的一致性。希望這也能支援自動格式化工具的發展。

由於 Godot 的著色器語言與 C 風格語言和 GLSL 很接近,所以本指南的靈感來自於 Godot 自己的 GLSL 格式。你可以在 Godot 的原始程式碼中查看 GLSL 檔的`例子 <https://github.com/godotengine/godot/blob/master/drivers/gles2/shaders/copy.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。 (編輯器預設值)

  • 每個檔案都以 LF 換行字元來結束。 (編輯器預設值)

  • 使用不帶 BOMUTF-8(編輯器預設值)

  • 使用 Tab 字元來縮排而不是空白字元。 (編輯器預設值)

縮排

每個縮進級別應比包含它的區塊多一個定位字元.

正確例 :

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

錯誤例 :

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

使用兩個縮排等級來區分連續行與一般的程式碼區塊。

正確例 :

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 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. “公共”在“私有”之前。在著色器語言的語境中,“公共”指的是使用者可以輕易調整的東西(uniform)。

區域變數

儘量在首次使用變數前定義區域變數。這樣一來在讀程式碼的時候就比較容易理解,而不需要為了找變數在哪裡定義的而往前翻太多。