着色语言
前言
Godot 使用类似于 GLSL ES 3.0 的着色语言。支持大多数数据类型和函数,并且可能会随着时间的推移添加剩余的几种类型和函数。
如果你已经熟悉 GLSL,Godot 着色器迁移指南是一个帮助你从常规 GLSL 转换到 Godot 着色语言的资源。
数据类型
支持大多数 GLSL ES 3.0 数据类型:
类型 |
描述 |
|---|---|
void |
Void 数据类型,只对不返回任何内容的函数有用。 |
bool |
布尔数据类型,只能包含 |
bvec2 |
布尔的两分量向量。 |
bvec3 |
布尔的三分量向量。 |
bvec4 |
布尔的四分量向量。 |
int |
32 bit signed scalar integer. |
ivec2 |
有符号整数的双分量向量。 |
ivec3 |
有符号整数的三分量向量。 |
ivec4 |
有符号整数的四分量向量。 |
uint |
无符号标量整数;不能包含负数。 |
uvec2 |
无符号整数的两分量向量。 |
uvec3 |
无符号整数的三分量向量。 |
uvec4 |
无符号整数的四分量向量。 |
float |
32 bit floating-point scalar. |
vec2 |
浮点值的两分量向量。 |
vec3 |
浮点值的三分量向量。 |
vec4 |
浮点值的四分量向量。 |
mat2 |
2x2 矩阵,按列主要顺序。 |
mat3 |
3x3 矩阵,按列主要顺序。 |
mat4 |
4x4 矩阵,按列主要顺序。 |
sampler2D |
用于绑定被读取为浮点数的 2D 纹理的采样器类型。 |
isampler2D |
用于绑定被读取为有符号整数的 2D 纹理的采样器类型。 |
usampler2D |
用于绑定被读取为无符号整数的 2D 纹理的采样器类型。 |
sampler2DArray |
用于绑定被读取为浮点数的 2D 纹理数组的采样器类型。 |
isampler2DArray |
用于绑定被读取为有符号整数的 2D 纹理数组的采样器类型。 |
usampler2DArray |
用于绑定被读取为无符号整数的 2D 纹理数组的采样器类型。 |
sampler3D |
用于绑定被读取为浮点数的 3D 纹理的采样器类型。 |
isampler3D |
用于绑定被读取为有符号整数的 3D 纹理的采样器类型。 |
usampler3D |
用于绑定被读取为无符号整数的 3D 纹理的采样器类型。 |
samplerCube |
用于绑定被读取为浮点数的立方体贴图的采样器类型。 |
samplerCubeArray |
用于绑定被读取为浮点数的立方体贴图数组的采样器类型。仅在 Forward+ 和移动中支持,兼容不支持。 |
samplerExternalOES |
外部采样器类型。仅在兼容/ Android 平台中支持。 |
These types can also be put inside arrays or structs, which are also usable as function parameters or return values. Arrays can be used as uniforms, but structs cannot.
警告
局部变量不会被初始化为像 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[column][row] 索引语法来访问每个标量,或使用 m[column] 按列索引来访问一个向量。例如,要从 4×4 变换矩阵中访问它的 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) 是一个单位矩阵。
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. 1. 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);
调换
只要结果是另一种向量类型(或标量),就可以按任意顺序获得组件的任意组合。百闻不如一见:
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() 函数来确保索引介于 0 和数组长度之间。务必仔细测试和检查你的代码。如果你传递一个常量表达式或数字,编辑器将检查其边界以防止这种崩溃。
全局数组
你可以在全局作用域中将数组声明为 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 关键字,可以使该变量不可变, 这意味着它不能被修改。除采样器外的所有基本类型都可以被声明为常量。访问和使用常量值的速度比使用 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 类型常量的初始化必须使用整数部分后的 . 符号或科学计数法。还支持可选的 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);
结构体
结构体是一种复合类型,可以对着色器代码进行更好的抽象。你可以在全局范围内声明它们,如下所示:
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 |
按位与 |
& |
9 |
按位异或 |
^ |
10 |
按位或 |
| |
11 |
逻辑与 |
&& |
12(最低) |
逻辑或 |
|| |
备注
大多数向量或矩阵的运算(乘法、除法等)都是按分量操作的,这意味着运算会先作用于每个向量的第一个值,然后是每个向量的第二个值,以此类推。以下是一些示例:
运算 |
等价标量运算 |
|---|---|
|
|
|
|
|
|
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 关键字。若使用该关键字,则丢弃当前片段且不写入任何数据。
请注意,使用 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:表示参数完全通过引用传递。
const:表示参数是常量且不能更改,可以与 in 限定符结合使用。
以下为示例:
void sum2(int a, int b, inout int result) {
result = a + b;
}
支持函数重载。你可以定义多个同名但参数不同的函数。需要注意的是,在重载函数调用中不允许 隐式类型转换 ,例如从 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。它们在顶点处理器中为每个图元顶点设置,并且该值对片段处理器中的每个像素进行插值。
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 关键字将数据从片段处理器发送到灯光处理器。在片段函数中赋值,然后在灯光函数中使用即可。
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 赋值,例如:
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 不可在着色器内部写入。除 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);
请注意,同时添加默认值和提示时,默认值应该写在提示的后面。
以下是完整的提示列表:
类型 |
提示 |
描述 |
|---|---|---|
vec3、vec4 |
source_color |
用作颜色。 |
int |
hint_enum("String1", "String2") |
在编辑器中以下拉菜单形式显示整数输入。 |
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 |
纹理是法线粗糙度纹理(仅在 Forward+ 中受支持)。 |
使用 hint_enum
你可以使用 hint_enum uniform,通过一个可读的下拉菜单来访问 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() 方法设置该值时,必须使用整数而非 String 名称。
使用 source_color
任何包含 sRGB 颜色数据的纹理都需要使用 source_color 提示才能被正确采样。这是因为 Godot 在线性色彩空间中进行渲染,但某些纹理包含的是 sRGB 颜色数据。如果未使用此提示,纹理将看起来颜色发白。
反照率和颜色纹理通常应具有 source_color 提示。法线、粗糙度、金属度和高度纹理通常不需要 source_color 提示。
Forward+ 和 Mobile 渲染器,以及启用了 HDR 2D 的 canvas_item 着色器,必须使用 source_color 提示。对于兼容渲染器,以及禁用了 HDR 2D 的 canvas_item 着色器, source_color 提示是可选的。然而,建议始终使用 source_color 提示,因为即使你更改了渲染器或禁用了 HDR 2D,它仍能正常工作。
Uniform 组
如果你需要在检查器中将多个 uniform 分组到某个特定的分类里,可以使用 group_uniform 关键字,就像这样:
group_uniforms MyGroup;
uniform sampler2D test;
结束分组的方法是:
group_uniforms;
这一语法还支持子分组(在此之前不需要声明基础分组):
group_uniforms MyGroup.MySubgroup;
全局 Uniform
有时你会想要统一修改很多不同着色器中的某个参数。使用普通的 uniform 就会很麻烦,因为你需要记录这些着色器,并且需要一个个地设 uniform。使用全局 uniform 就可以创建并更新所有着色器中均可以使用的 uniform,所有类型的着色器都适用(canvas_item、spatial、particles、sky、fog)。
全局 uniform 适用于能够影响场景中大量对象的环境效果,例如玩家在附近时的植被弯曲效果、物体随风移动的效果等。
备注
全局 uniform 与单个着色器的 全局作用域 并不相同。普通 uniform 定义在着色器函数之外,因此属于该着色器的全局作用域;而全局 uniform 则是整个项目中所有着色器共用的全局变量(但在每个着色器内部,它们同样处于全局作用域中)。
要创建全局 uniform,请打开项目设置,切换到着色器全局量选项卡。为 uniform 指定名称(区分大小写)和类型,然后点击对话框右上角的添加。点击 uniform 列表中的值即可编辑 uniform 的取值:
在“项目设置”的“着色器全局量”中添加全局 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 的取值。如果你需要在设值之后用脚本读取,请考虑创建一个自动加载,在设置需要查询的全局 uniform 的同时保存对应的值。
单实例 uniform
备注
单实例 uniform 在 canvas_item(2D)和 spatial(3D)着色器中均可使用。
有时,你希望使用材质修改每个节点上的某个参数。例如,在一个充满树木的森林中,你想让每棵树都有一个可以手动调整、略微不同的颜色。若不使用单实例 uniform,则需为每棵树创建单独的材质(每个材质具有略微不同的色相)。这使得材质管理变得复杂,并且由于场景需要更多单独的材质实例,还会产生性能开销。虽然可以使用顶点颜色来实现,但该方法需要为每种不同的颜色创建单独的网格副本,同样会带来性能开销。
单实例 uniform 设置在每个 GeometryInstance3D 上,而不是在每个材质实例上。在处理指定了多种材质的网格或多重网格设置时,请考虑这一点。
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 部分设置单实例 uniform 的值
单实例 uniform 的值也可在运行时通过调用继承自 GeometryInstance3D: 的节点上的 set_instance_shader_parameter 方法进行设置:
$MeshInstance3D.set_instance_shader_parameter("my_color", Color(0.3, 0.6, 1.0))
在使用单实例 uniform 时,你应该注意一些限制:
Per-instance uniforms do not support textures or arrays, only regular scalar and vector types. As a workaround, you can pass a texture array as a regular uniform, then pass the index of the texture to be drawn using a per-instance uniform.
备注
In GLSL versions before 4.0 (i.e. GLSL 3.3 and lower), you cannot directly index a texture array using a per-instance uniform, as sampler arrays can only be indexed by compile-time constant expressions. This affects shaders compiled with the Compatibility renderer.
If you are affected, use the
switchstatement to select the texture:
uniform sampler2D texture_array[2];
instance uniform int texture_index;
void fragment() {
vec4 color;
switch (texture_index) {
case 0:
color = texture(texture_array[0], UV);
break;
case 1:
color = texture(texture_array[1], UV);
break;
}
COLOR = color;
}
每个着色器最多可使用 16 个单实例 uniform。
如果你的网格使用了多个材质,则第一个被找到的网格材质中的参数将“优先生效”于后续材质中的参数,除非这些参数具有相同的名称、索引和类型。在这种情况下,所有参数将正确生效。
如果遇到上述情况,你可以通过使用
instance_index提示来手动指定单实例 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 的名称。它必须与着色器中的 uniform 的名称完全一致,否则将无法被识别。
GDScript 使用的变量类型与 GLSL 不同,所以当把变量从 GDScript 传递到着色器时,Godot 会自动转换类型。以下是相应类型的表格:
GLSL 类型 |
GDScript 类型 |
注意 |
|---|---|---|
bool |
bool |
|
bvec2 |
int |
按位打包整数,其中位 0 (LSB) 对应 x。 例如,值为 (bx, by) 的 bvec2 可以按以下方式创建: bvec2_input: int = (int(bx)) | (int(by) << 1)
|
bvec3 |
int |
按位打包整数,其中位 0 (LSB) 对应 x。 |
bvec4 |
int |
按位打包整数,其中位 0 (LSB) 对应 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 为单位向量。 |
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+ 和移动渲染器,不支持兼容渲染器。 |
samplerExternalOES |
ExternalTexture |
仅支持兼容渲染器(Compatibility)/ Android 平台。 |
备注
通过 GDScript 设置着色器 uniform 时务必谨慎,因为即使类型不匹配也不会抛出错误。着色器仅会出现未定义行为。特别需要注意的是,将 GDScript 的 int/float(64 位)设置到 Godot 着色器语言的 int/float(32 位)时,在需要高精度的场景中可能导致非预期结果。
Uniform 限制
在单个着色器中,你可以使用的着色器 uniform 的总大小是有限制的。在大多数桌面平台上,这个限制是 65536 字节或 4096 个 vec4 uniform。在移动平台上,这个限制通常是 16384 字节或 1024 个 vec4 uniform。小于 vec4 的向量 uniform(如 vec2 或 vec3)会补齐到 vec4 的大小。标量 uniform(如 int 或 float)不会进行补齐,而 bool 类型会补齐到 int 的大小。
数组的大小按其内容的总大小计算。如果你需要一个超过此限制的 uniform 数组,可考虑将数据打包到纹理中,因为纹理的内容不计入此限制,只有采样器 uniform 的大小会被计算。
内置变量
有大量内置变量可用,例如 UV、COLOR 和 VERTEX。具体有哪些可用的变量取决于着色器类型(spatial、canvas_item、particle 等)以及所使用的函数(vertex、fragment、light、start、process、sky 或 fog)。有关可用内置变量的完整列表,请参阅相应页面:
内置函数
支持大量符合 GLSL ES 3.0 标准的内置函数。详情请参阅 内置函数 页面。
注释
着色语言支持与 C# 和 C++ 相同的注释语法,使用
//进行单行注释和/* */进行多行注释:Additionally, you can use documentation comments that are displayed in the inspector when hovering a shader parameter. Documentation comments are currently only supported when placed immediately above a
uniformdeclaration. These documentation comments only support the multiline comment syntax (even if used on a single line) and must use two leading asterisks (/**) instead of just one (/*):后续行上的星号不是必需的,但根据《着色器风格指南》建议使用。这些星号会被检查器自动删除,因此它们不会出现在工具提示中。