著色語言
前言
Godot 使用類似 GLSL ES 3.0 的著色語言。大多數資料型別與函式皆已支援,剩餘未支援的型別和函式也有可能隨時間加入。
如果你已熟悉 GLSL,可以參考 Godot 著色器遷移指南,協助你從標準 GLSL 轉換到 Godot 的著色語言。
資料型別
支援大多數 GLSL ES 3.0 資料型別:
型別 |
說明 |
|---|---|
void |
Void 資料型別,僅用於不回傳任何值的函式。 |
bool |
布林資料型別,只能包含 |
bvec2 |
布林值的雙分量向量。 |
bvec3 |
布林值的三分量向量。 |
bvec4 |
布林值的四分量向量。 |
int |
32 位元有符號標量整數。 |
ivec2 |
有符號整數的雙分量向量。 |
ivec3 |
有符號整數的三分量向量。 |
ivec4 |
有符號整數的四分量向量。 |
uint |
無符號標量整數,不能包含負數。 |
uvec2 |
無符號整數的雙分量向量。 |
uvec3 |
無符號整數的三分量向量。 |
uvec4 |
無符號整數的四分量向量。 |
float |
32 位元浮點標量。 |
vec2 |
浮點值的雙分量向量。 |
vec3 |
浮點值的三分量向量。 |
vec4 |
浮點值的四分量向量。 |
mat2 |
2x2 矩陣,以 column-major(欄主序)排列。 |
mat3 |
3x3 矩陣,以 column-major(欄主序)排列。 |
mat4 |
4x4 矩陣,以 column-major(欄主序)排列。 |
sampler2D |
用於綁定 2D 紋理的取樣器型別,以 float 讀取。 |
isampler2D |
用於綁定 2D 紋理的取樣器型別,以有符號整數讀取。 |
usampler2D |
用於綁定 2D 紋理的取樣器型別,以無符號整數讀取。 |
sampler2DArray |
用於綁定 2D 紋理陣列的取樣器型別,以 float 讀取。 |
isampler2DArray |
用於綁定 2D 紋理陣列的取樣器型別,以有符號整數讀取。 |
usampler2DArray |
用於綁定 2D 紋理陣列的取樣器型別,以無符號整數讀取。 |
sampler3D |
用於綁定 3D 紋理的取樣器型別,以 float 讀取。 |
isampler3D |
用於綁定 3D 紋理的取樣器型別,以有符號整數讀取。 |
usampler3D |
用於綁定 3D 紋理的取樣器型別,以無符號整數讀取。 |
samplerCube |
用於綁定立方體貼圖的取樣器型別,以 float 讀取。 |
samplerCubeArray |
用於綁定立方體貼圖陣列的取樣器型別,以 float 讀取。僅 Forward+ 與 Mobile 支援,不相容於 Compatibility。 |
samplerExternalOES |
外部取樣器型別。僅支援於 Compatibility/Android 平台。 |
警告
區域變數不會自動初始化為像是 0.0 這種預設值。如果你在賦值前就使用變數,它將包含記憶體中原本的隨機值,這會導致不可預期的視覺異常。然而,uniform 與 varying 會自動初始化為預設值。
型別轉換
就像 GLSL ES 3.0 一樣,不允許在相同大小但不同型別的標量與向量間進行隱式轉型,也不允許不同大小的型別之間轉型。必須透過建構子明確進行型別轉換。
範例:
float a = 2; // invalid
float a = 2.0; // valid
float a = float(2); // valid
預設的整數常數為有符號,因此轉換成無符號時必須進行型別轉換:
int a = 2; // valid
uint a = 2; // invalid
uint a = uint(2); // valid
成員
向量型別的每個標量成員可以透過 "x"、"y"、"z" 和 "w" 存取。也可以用 "r"、"g"、"b"、"a",效果相同。請依需求選用。
對於矩陣,請使用 m[欄][列] 索引語法來存取每個標量,或用 m[欄] 來取得對應欄的向量。例如,要取得 mat4 轉換矩陣的平移 y 分量(第 4 欄,第 2 行),可以寫 m[3][1] 或 m[3].y。
建構
建構向量型別時必須傳入:
// The required amount of scalars
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
// Complementary vectors and/or scalars
vec4 a = vec4(vec2(0.0, 1.0), vec2(2.0, 3.0));
vec4 a = vec4(vec3(0.0, 1.0, 2.0), 3.0);
// A single scalar for the whole vector
vec4 a = vec4(0.0);
建構矩陣型別時,必須傳入與矩陣維度相同的向量(視為欄)。也可用 matx(float) 語法建立對角矩陣。例如 mat4(1.0) 就是單位矩陣(identity matrix)。
mat2 m2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));
mat3 m3 = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0));
mat4 identity = mat4(1.0);
矩陣也可以由其他維度的矩陣建構。有兩個規則:
1. If a larger matrix is constructed from a smaller matrix, the additional rows and columns are set to the values they would have in an identity matrix. 2. If a smaller matrix is constructed from a larger matrix, the top, left submatrix of the larger matrix is used.
mat3 basis = mat3(MODEL_MATRIX);
mat4 m4 = mat4(basis);
mat2 m2 = mat2(m4);
分量混寫(Swizzling)
你可以用任何順序組合向量的分量,只要結果也是向量型別(或標量)即可。範例如下:
vec4 a = vec4(0.0, 1.0, 2.0, 3.0);
vec3 b = a.rgb; // Creates a vec3 with vec4 components.
vec3 b = a.ggg; // Also valid; creates a vec3 and fills it with a single vec4 component.
vec3 b = a.bgr; // "b" will be vec3(2.0, 1.0, 0.0).
vec3 b = a.xyz; // Also rgba, xyzw are equivalent.
vec3 b = a.stp; // And stpq (for texture coordinates).
float c = b.w; // Invalid, because "w" is not present in vec3 b.
vec3 c = b.xrt; // Invalid, mixing different styles is forbidden.
b.rrr = a.rgb; // Invalid, assignment with duplication.
b.bgr = a.rgb; // Valid assignment. "b"'s "blue" component will be "a"'s "red" and vice versa.
精度
可以為資料型別加上精度修飾詞,適用於 uniform、變數、參數和 varying:
lowp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // low precision, usually 8 bits per component mapped to 0-1
mediump vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // medium precision, usually 16 bits or half float
highp vec4 a = vec4(0.0, 1.0, 2.0, 3.0); // high precision, uses full float or integer range (32 bit default)
部分運算若使用較低精度,能提升效能(但精度會下降)。頂點處理器通常需高精度,比較少用低精度,但片段處理器則經常可以這樣做。
某些架構(主要是行動平台)這樣做能有明顯效益,但不同精度之間轉換會有額外負擔。請參閱目標平台的文件以獲取詳情。許多行動平台驅動可能會導致不一致或不可預期的行為,除非有必要,否則建議避免額外指定精度。
陣列
陣列是用於儲存多個相同型別變數的容器。
區域陣列
區域陣列需在函式內宣告。可使用所有允許的資料型別(取樣器除外)。陣列宣告語法類似 C 語言:[const] + [precision] + typename + identifier + [array size]。
void fragment() {
float arr[3];
}
可以在宣告時直接初始化,例如:
float float_arr[3] = float[3] (1.0, 0.5, 0.0); // first constructor
int int_arr[3] = int[] (2, 1, 0); // second constructor
vec2 vec2_arr[3] = { vec2(1.0, 1.0), vec2(0.5, 0.5), vec2(0.0, 0.0) }; // third constructor
bool bool_arr[] = { true, true, false }; // fourth constructor - size is defined automatically from the element count
也可以在一行宣告多個(不同大小也可以):
float a[3] = float[3] (1.0, 0.5, 0.0),
b[2] = { 1.0, 0.5 },
c[] = { 0.7 },
d = 0.0,
e[5];
要存取陣列元素,請使用索引語法:
float arr[3];
arr[0] = 1.0; // setter
COLOR.r = arr[0]; // getter
陣列有一個內建函式 .length()``(請勿與全域 ``length() 函式混淆)。它不接受參數,會回傳陣列大小。
float arr[] = { 0.0, 1.0, 0.5, -1.0 };
for (int i = 0; i < arr.length(); i++) {
// ...
}
備註
如果你使用小於 0 或超過陣列大小的索引,著色器將當機並導致渲染失敗。請使用 length()、if 或 clamp() 來保證索引合法。務必仔細測試和檢查你的程式碼。若索引是常數,編輯器會自動檢查界限避免當機。
全域陣列
你可以在全域範圍宣告陣列為 const 或 uniform:
shader_type spatial;
const lowp vec3 v[1] = lowp vec3[1] ( vec3(0, 0, 1) );
uniform lowp vec3 w[1];
void fragment() {
ALBEDO = v[0] + w[0];
}
備註
全域陣列語法與區域陣列相同,只是在前面加上 const 或 uniform。注意 uniform 陣列不能有預設值。
常數
在變數宣告前加上 const 關鍵字,該變數就會成為不可變(immutable),也就是無法被修改。所有基本型別(取樣器除外)都可以宣告為常數。存取常數比 uniform 略快。常數必須在宣告時初始化。
const vec2 a = vec2(0.0, 1.0);
vec2 b;
a = b; // invalid
b = a; // valid
常數不可修改,也不能加提示,但相同型別的多個常數可以在同一行宣告,例如:
const vec2 V1 = vec2(1, 1), V2 = vec2(2, 2);
陣列也可以像變數一樣用 const 宣告為常數。
const float arr[] = { 1.0, 0.5, 0.0 };
arr[0] = 1.0; // invalid
COLOR.r = arr[0]; // valid
常數既可在全域(任何函式外)也可在區域(函式內)宣告。當你需要在整個著色器中使用且不需更動的值時,全域常數很實用。與 uniform 類似,全域常數會在所有著色器階段共用,但無法在著色器外部存取。
shader_type spatial;
const float GOLDEN_RATIO = 1.618033988749894;
float 型別的常數必須以小數點(如 1.0)或科學記號初始化,也支援可選的 f 後綴。
float a = 1.0;
float b = 1.0f; // same, using suffix for clarity
float c = 1e-1; // gives 0.1 by using the scientific notation
uint``(無符號整數)型別的常數必須加上 ``u 後綴以區分有符號整數。也可以用 uint(x) 內建轉換函式。
uint a = 1u;
uint b = uint(1);
結構(Structs)
結構(struct)是一種複合型別,可用來讓著色器程式碼更具抽象性。你可以在全域範圍宣告結構:
struct PointLight {
vec3 position;
vec3 color;
float intensity;
};
宣告後,你可以這樣初始化實體:
void fragment()
{
PointLight light;
light.position = vec3(0.0);
light.color = vec3(1.0, 0.0, 0.0);
light.intensity = 0.5;
}
也可以直接用結構建構子來初始化:
PointLight light = PointLight(vec3(0.0), vec3(1.0, 0.0, 0.0), 0.5);
結構可以包含其他結構或陣列,也可以宣告為全域常數:
shader_type spatial;
...
struct Scene {
PointLight lights[2];
};
const Scene scene = Scene(PointLight[2](PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0), PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0)));
void fragment()
{
ALBEDO = scene.lights[0].color;
}
也可以將結構作為參數傳入函式:
shader_type canvas_item;
...
Scene construct_scene(PointLight light1, PointLight light2) {
return Scene({light1, light2});
}
void fragment()
{
COLOR.rgb = construct_scene(PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), 1.0), PointLight(vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 1.0), 1.0)).lights[0].color;
}
運算子
Godot 著色語言支援與 GLSL ES 3.0 相同的運算子。以下為運算子優先順序列表:
優先順序 |
類別 |
運算子 |
1(最高) |
括號分組 |
() |
2 |
單元運算 |
+, -, !, ~ |
3 |
乘除法 |
/, *, % |
4 |
加減法 |
+, - |
5 |
位元移位 |
<<, >> |
6 |
關係運算 |
<, >, <=, >= |
7 |
等值比較 |
==, != |
8 |
位元 AND |
& |
9 |
位元 XOR(異或) |
^ |
10 |
位元 OR(或) |
| |
11 |
邏輯 AND |
&& |
12(最低) |
邏輯 OR |
|| |
備註
大多數接受向量或矩陣的運算子(如乘法、除法等)都會對各分量分別運算,即對每個分量獨立執行該運算。舉例如下:
運算 |
等價標量運算 |
|---|---|
|
|
|
|
|
|
依據 GLSL 語言規範 第 5.10 節(向量與矩陣運算)說明:
除少數例外,運算皆以分量為單位執行。通常,當運算子用於向量或矩陣時,會對每個分量分別運算。[...] 例外情況為:矩陣乘以向量、向量乘以矩陣、及矩陣乘以矩陣。這些情況不按分量運算,而是進行正確的線性代數乘法。
流程控制
Godot 著色語言支援常用的流程控制語句:
// `if`, `else if` and `else`.
if (cond) {
} else if (other_cond) {
} else {
}
// Ternary operator.
// This is an expression that behaves like `if`/`else` and returns the value.
// If `cond` evaluates to `true`, `result` will be `9`.
// Otherwise, `result` will be `5`.
int result = cond ? 9 : 5;
// `switch`.
switch (i) { // `i` should be a signed integer expression.
case -1:
break;
case 0:
return; // `break` or `return` to avoid running the next `case`.
case 1: // Fallthrough (no `break` or `return`): will run the next `case`.
case 2:
break;
//...
default: // Only run if no `case` above matches. Optional.
break;
}
// `for` loop. Best used when the number of elements to iterate on
// is known in advance.
for (int i = 0; i < 10; i++) {
}
// `while` loop. Best used when the number of elements to iterate on
// is not known in advance.
while (cond) {
}
// `do while`. Like `while`, but always runs at least once even if `cond`
// never evaluates to `true`.
do {
} while (cond);
請注意,在現代 GPU 上,無窮迴圈可能導致應用程式(包含編輯器)當機。Godot 無法保護你不受此影響,請務必避免這種錯誤!
此外,比較浮點數時,請務必與「範圍」做比較,而非直接比對精確數值。
像 if (value == 0.3) 這樣的比較可能不會等於 true。浮點運算常有誤差,結果可能不如你預期,不同硬體上的表現也可能不同。
切勿 這樣寫。
float value = 0.1 + 0.2;
// May not evaluate to `true`!
if (value == 0.3) {
// ...
}
請改用 epsilon(誤差容許值)進行範圍比較。數字越大,或浮點精度越低,epsilon 應設得越大。
const float EPSILON = 0.0001;
if (value >= 0.3 - EPSILON && value <= 0.3 + EPSILON) {
// ...
}
如需更多資訊,請參考:floating-point-gui.de 。
丟棄(Discard)
片段(fragment)、光源(light)以及由 fragment 或 light 呼叫的自訂函式中,可以使用 discard 關鍵字。使用時,該片段會被丟棄,不會寫入任何資料。
請注意,使用 discard 會有效能損耗,因為這會讓使用此著色器的表面無法執行深度預處理。被丟棄的像素頂點仍需經過頂點著色器運算,因此如果所有像素都用 discard,其渲染成本仍然高於完全不渲染。
函式
你可以在 Godot 著色器中自訂函式。語法如下:
ret_type func_name(args) {
return ret_type; // if returning a value
}
// a more specific example:
int sum2(int a, int b) {
return a + b;
}
只能呼叫已在前面(編輯器中較上方)定義過的函式。若重複定義同名函式(或與內建函式同名),將會發生錯誤。
函式參數可以有特殊修飾詞:
in:參數僅供讀取(預設)。
out:參數僅供寫入。
inout:參數以參考(reference)方式傳遞,可讀可寫。
const:參數為常數,無法修改,可與 in 一起使用。
範例如下:
void sum2(int a, int b, inout int result) {
result = a + b;
}
支援函式多載(overloading)。你可以定義多個同名但參數不同的函式。注意,多載呼叫時不支援 隱式型別轉換 ,例如 int 轉 float ( 1 不能自動變成 1.0 )。
vec3 get_color(int t) {
return vec3(1, 0, 0); // Red color.
}
vec3 get_color(float t) {
return vec3(0, 1, 0); // Green color.
}
void fragment() {
vec3 red = get_color(1);
vec3 green = get_color(1.0);
}
Varying(變量)
若要將資料從頂點處理器傳遞到片段(或光源)處理器,請使用 varying。在頂點處理器中,每個基本頂點都設定了 varying,然後在片段處理器中針對每個像素進行插值。
shader_type spatial;
varying vec3 some_color;
void vertex() {
some_color = NORMAL; // Make the normal the color.
}
void fragment() {
ALBEDO = some_color;
}
void light() {
DIFFUSE_LIGHT = some_color * 100; // optionally
}
Varying 也可以宣告為陣列:
shader_type spatial;
varying float var_arr[3];
void vertex() {
var_arr[0] = 1.0;
var_arr[1] = 0.0;
}
void fragment() {
ALBEDO = vec3(var_arr[0], var_arr[1], var_arr[2]); // red color
}
你也可以用 varying 關鍵字,將資料從 fragment 傳到 light 處理器。在 fragment 中賦值,之後在 light 函式中使用即可。
shader_type spatial;
varying vec3 some_light;
void fragment() {
some_light = ALBEDO * 100.0; // Make a shining light.
}
void light() {
DIFFUSE_LIGHT = some_light;
}
注意,varying 不能在自訂函式或 light 處理器中直接賦值,例如:
shader_type spatial;
varying float test;
void foo() {
test = 0.0; // Error.
}
void vertex() {
test = 0.0;
}
void light() {
test = 0.0; // Error too.
}
這項限制是為了避免初始化前的不正確用法。
插值修飾詞
著色管線中某些值會被插值。你可以用 插值修飾詞 來調整插值方式。
shader_type spatial;
varying flat vec3 our_color;
void vertex() {
our_color = COLOR.rgb;
}
void fragment() {
ALBEDO = our_color;
}
目前有兩種插值修飾詞:
修飾詞 |
說明 |
|---|---|
flat |
該值不進行插值。 |
smooth |
該值以透視修正的方式進行插值(預設)。 |
Uniform(統一變數)
你可以用 uniform (統一變量)將資料傳入著色器,uniform 必須在著色器全域範圍(函式外)宣告。當著色器指派給材質時,uniform 會出現在材質屬性視窗中,可供編輯。uniform 不能在著色器內部寫入。除了 void 以外,任何 資料型別 都可宣告為 uniform。
shader_type spatial;
uniform float some_value;
uniform vec3 colors[3];
你可以在編輯器的材質屬性檢視器中設定 uniform,或是 透過程式碼 設定。
Uniform 提示
Godot 提供可選的 uniform 提示,有助於編譯器了解 uniform 的用途,並讓編輯器決定如何讓使用者調整其值。
shader_type spatial;
uniform vec4 color : source_color;
uniform float amount : hint_range(0, 1);
uniform vec4 other_color : source_color = vec4(1.0); // Default values go after the hint.
uniform sampler2D image : source_color;
Uniform 也可以設定預設值:
shader_type spatial;
uniform vec4 some_vector = vec4(0.0);
uniform vec4 some_color : source_color = vec4(1.0);
注意:當同時有預設值與提示時,預設值必須寫在提示之後。
完整的 uniform 提示如下:
型別 |
提示 |
說明 |
|---|---|---|
vec3, vec4 |
source_color |
作為顏色使用。 |
int |
hint_enum("String1", "String2") |
將 int 輸入顯示為下拉選單。 |
int, float |
hint_range(min, max[, step]) |
限制為指定範圍內的值(最小/最大/步進)。 |
sampler2D |
source_color |
作為反照率顏色使用。 |
sampler2D |
hint_normal |
作為法線貼圖使用。 |
sampler2D |
hint_default_white |
作為數值或反照率顏色時,預設為不透明白色。 |
sampler2D |
hint_default_black |
作為數值或反照率顏色時,預設為不透明黑色。 |
sampler2D |
hint_default_transparent |
作為數值或反照率顏色時,預設為透明黑色。 |
sampler2D |
hint_anisotropy |
作為 flowmap 使用,預設朝右。 |
sampler2D |
hint_roughness[_r, _g, _b, _a, _normal, _gray] |
用於匯入時的粗糙度限制器(用於減少高光鋸齒)。 |
sampler2D |
filter[_nearest, _linear][_mipmap][_anisotropic] |
啟用指定的紋理過濾模式。 |
sampler2D |
repeat[_enable, _disable] |
啟用紋理重複。 |
sampler2D |
hint_screen_texture |
這個紋理為螢幕紋理。 |
sampler2D |
hint_depth_texture |
這個紋理為深度紋理。 |
sampler2D |
hint_normal_roughness_texture |
這個紋理為 normal roughness 紋理(僅 Forward+ 支援)。 |
使用 hint_enum
你可以利用 hint_enum 讓 int 變數在編輯器中以可讀的下拉選單顯示:
uniform int noise_type : hint_enum("OpenSimplex2", "Cellular", "Perlin", "Value") = 0;
你也可以用類似 GDScript 的冒號語法,為 hint_enum uniform 指定明確的數值:
uniform int character_speed: hint_enum("Slow:30", "Average:60", "Very Fast:200") = 60;
這個值會以整數形式儲存,對應到選項的索引(如 0、1、2)或用冒號語法指定的值(如 30、60、200)。用 set_shader_parameter() 設定時必須用整數值,不能用字串名稱。
使用 source_color
任何包含 sRGB 色彩資料 的紋理都必須加上 source_color 提示,才能正確取樣。因為 Godot 採用線性色彩空間算繪,但有些紋理本身為 sRGB 色彩資料。如果沒有這個提示,紋理顏色會看起來偏灰、失真。
albedo 與色彩紋理通常應加上 source_color 提示。法線、粗糙度、金屬度與高度紋理通常不需 source_color。
在 Forward+ 與 Mobile 渲染器下,以及 canvas_item 著色器啟用 HDR 2D 時,必須加上 source_color。如果是 Compatibility 渲染器,或 canvas_item 未啟用 HDR 2D 則為選擇性。但建議一律使用 source_color,即使未來切換渲染器或 HDR 2D 狀態都不會有色彩問題。
Uniform 分組
如果你想要在屬性檢視器將多個 uniform 分組顯示,可以用 group_uniform 關鍵字,例如:
group_uniforms MyGroup;
uniform sampler2D test;
你可以用如下語法結束該分組:
group_uniforms;
這個語法也支援子分組(不必先宣告父分組):
group_uniforms MyGroup.MySubgroup;
全域 uniform
有時你會想要一次修改多個著色器的參數。如果用一般 uniform,必須逐一設定所有著色器,非常麻煩。全域 uniform 可讓你建立可在所有著色器、所有型別(如 canvas_item、spatial、particles、sky、fog)共用的 uniform 變數。
全域 uniform 特別適合需要同時影響場景多個物件的環境特效,例如玩家靠近時所有樹葉彎曲,或物件隨風搖曳等。
備註
全域 uniform 與單一著色器的 全域範圍 不同。一般 uniform 只是在著色器內的全域範圍,而全域 uniform 則是整個專案所有著色器都能取得的全域變數。
要建立全域 uniform,請開啟 專案設定,切換到 Shader Globals 標籤。輸入 uniform 名稱(區分大小寫)及型別,然後點選對話框右上角的 新增。接著即可在 uniform 列表點擊並編輯其值:
在專案設定的 Shader Globals 標籤新增全域 uniform
建立全域 uniform 後,可以在著色器中這樣使用:
shader_type canvas_item;
global uniform vec4 my_color;
void fragment() {
COLOR = my_color.rgb;
}
請注意,全域 uniform 必須 在儲存著色器時已存在於專案設定內,否則編譯會失敗。雖然你可以在著色器程式碼裡寫 global uniform vec4 my_color = ... 指定預設值,但這個值會被忽略,因為全域 uniform 必須始終於專案設定中定義。
若要在執行時更改全域 uniform 的值,請於腳本中使用 RenderingServer.global_shader_parameter_set 方法:
RenderingServer.global_shader_parameter_set("my_color", Color(0.3, 0.6, 1.0))
你可以不限次數地設定全域 uniform 值,而不會影響效能,因為這不需 CPU 與 GPU 的同步。
你也可以在執行時新增或移除全域 uniform:
RenderingServer.global_shader_parameter_add("my_color", RenderingServer.GLOBAL_VAR_TYPE_COLOR, Color(0.3, 0.6, 1.0))
RenderingServer.global_shader_parameter_remove("my_color")
於執行時新增或刪除全域 uniform 會有效能損耗,雖然比從腳本讀取全域 uniform 值小,但仍需注意(詳見下方警示)。
警告
雖然你 可以 在腳本中用 RenderingServer.global_shader_parameter_get("uniform_name") 於執行時查詢全域 uniform 的值,但這會嚴重拖慢效能,因為渲染執行緒需與呼叫執行緒同步。
因此,不建議在腳本中頻繁讀取全域 uniform 值。如果你需要在腳本設定後讀取該值,請考慮建立 autoload 來同步儲存並查詢這些值。
每個實例的 uniform
備註
per-instance uniforms 同時適用於 canvas_item (2D) 與 spatial (3D) 著色器。
有時你會想要讓每個節點使用材質時都能調整參數。舉例來說,如果你想讓森林裡每棵樹的顏色都可個別調整,若無 per-instance uniform,就必須為每棵樹建立一個專屬材質(色調略有不同)。這不僅讓材質管理變複雜,場景也會因為產生更多材質實體而拖慢效能。雖然也能用頂點色實現,但每種顏色都要複製一份網格,效能負擔同樣很重。
per-instance uniform 是設在每個 GeometryInstance3D(幾何實例)上,不是設在每個材質上。當你的網格有多個材質或採用 MultiMesh 時,請特別留意這點。
shader_type spatial;
// Provide a hint to edit as a color. Optionally, a default value can be provided.
// If no default value is provided, the type's default is used (e.g. opaque black for colors).
instance uniform vec4 my_color : source_color = vec4(1.0, 0.5, 0.0, 1.0);
void fragment() {
ALBEDO = my_color.rgb;
}
儲存著色器後,可以在屬性檢視器中調整每個實例 uniform 的值:
在屬性檢視器的 GeometryInstance3D 區塊設定 per-instance uniform 的值
你也可以於執行時,在繼承自 GeometryInstance3D 的節點上用 set_instance_shader_parameter 方法設定 per-instance uniform 的值:
$MeshInstance3D.set_instance_shader_parameter("my_color", Color(0.3, 0.6, 1.0))
使用 per-instance uniform 時,請注意以下限制:
per-instance uniforms 不支援紋理或陣列,只支援一般的純量與向量型別。可行的替代作法是,將紋理陣列作為一般 uniform 傳入,再用 per-instance uniform 傳遞要繪製的紋理索引。
每個著色器最多允許 16 個 per-instance uniform。
如果網格使用多個材質,找到的第一個材質的 per-instance uniform 會「覆蓋」後續材質,除非名稱、索引與型別都相同,此時所有參數才會正確套用。
若遇到上述情形,可以用
instance_index提示手動指定 per-instance uniform 的索引(0-15),以避免衝突:
instance uniform vec4 my_color : source_color, instance_index(5);
從程式碼設定 uniform
你可以在 GDScript 中用 set_shader_parameter() 設定 uniform:
material.set_shader_parameter("some_value", some_value)
material.set_shader_parameter("colors", [Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)])
備註
set_shader_parameter() 第一個參數是著色器內 uniform 的名稱。這個名稱必須*完全*相符,否則將無法辨識。
GDScript 的變數型別與 GLSL 不同,因此從 GDScript 傳值給著色器時,Godot 會自動轉換型別。以下為型別對應表:
GLSL 型別 |
GDScript 型別 |
說明 |
|---|---|---|
bool |
bool |
|
bvec2 |
int |
位元封裝整數,其中第 0 位(最低位)對應 x 分量。 例如,(bx, by) 的 bvec2 可以這樣建立: bvec2_input: int = (int(bx)) | (int(by) << 1)
|
bvec3 |
int |
位元封裝整數,其中第 0 位(最低位)對應 x 分量。 |
bvec4 |
int |
位元封裝整數,其中第 0 位(最低位)對應 x 分量。 |
int |
int |
|
ivec2 |
Vector2i |
|
ivec3 |
Vector3i |
|
ivec4 |
Vector4i |
|
uint |
int |
|
uvec2 |
Vector2i |
|
uvec3 |
Vector3i |
|
uvec4 |
Vector4i |
|
float |
float |
|
vec2 |
Vector2 |
|
vec3 |
Vector3、Color |
若型別為 Color,將會被解讀為 (r, g, b)。 |
vec4 |
Vector4、Color、Rect2、Plane、Quaternion |
若型別為 Color,將會被解讀為 (r, g, b, a)。 若型別為 Rect2,將會被解讀為 (position.x, position.y, size.x, size.y)。 若型別為 Plane,將會被解讀為 (normal.x, normal.y, normal.z, d)。 |
mat2 |
Transform2D |
|
mat3 |
Basis |
|
mat4 |
Projection、Transform3D |
若型別為 Transform3D,w 向量會自動設為單位(identity)。 |
sampler2D |
Texture2D |
|
isampler2D |
Texture2D |
|
usampler2D |
Texture2D |
|
sampler2DArray |
Texture2DArray |
|
isampler2DArray |
Texture2DArray |
|
usampler2DArray |
Texture2DArray |
|
sampler3D |
Texture3D |
|
isampler3D |
Texture3D |
|
usampler3D |
Texture3D |
|
samplerCube |
Cubemap |
關於在 Godot 匯入 cubemap 貼圖的說明,請參閱 更改匯入資源型別。 |
samplerCubeArray |
CubemapArray |
僅 Forward+ 與 Mobile 渲染支援,Compatibility 模式不支援。 |
samplerExternalOES |
ExternalTexture |
僅支援於 Compatibility/Android 平台。 |
備註
從 GDScript 設定 shader uniform 時請特別小心,若型別不符並不會報錯,而是產生未定義行為。特別是將 GDScript 的 int/float(64 位元)設給 Godot shader 的 int/float(32 位元)時,若需高精度時可能導致不可預期的結果。
Uniform 限制
每個著色器的 uniform 數量有上限。在大部分桌面平台上,限制為 65536 位元組(4096 個 vec4);在行動裝置上則為 16384 位元組(1024 個 vec4)。比 vec4 小的向量 uniform(如 vec2、vec3)會自動補齊成 vec4 大小。標量 uniform(如 int、float)不做補齊,而 bool 會補齊為 int 大小。
陣列 uniform 會以內容總大小計算。如果你需要的 uniform 陣列超過此上限,建議將資料包進紋理貼圖,因為紋理內容不計入 uniform 限制,只計取樣器(sampler uniform)本身。
內建變數
有大量內建變數可用,如 UV、COLOR、VERTEX 等。可用的變數取決於著色器類型(spatial、canvas_item、particle 等)與所使用的函式(vertex、fragment、light、start、process、sky 或 fog)。各類型可用的內建變數清單請見相對應頁面:
內建函式
Godot 支援大量內建函式,遵循 GLSL ES 3.0 標準。詳情請見 內建函式 頁面。
注釋
著色語言支援與 C# 和 C++ 相同的註解語法,單行註解使用
//,多行註解則使用/* */:此外,你可以使用說明註解,當游標懸停在著色器參數上時會顯示於屬性檢視器中。說明註解目前僅支援放在
uniform宣告的正上方。這些註解僅支援**多行**註解語法,且必須以**兩個**星號(/**)開頭,而不是一個(/*):根據 著色器風格指南,後續行的星號不是必要的,但建議加上。這些星號會自動被屬性檢視器去除,因此不會出現在提示中。