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...
CanvasItem 着色器
画布组件着色器用于绘制Godot中的所有二维元素. 这包括从画布组件继承的所有节点, 以及所有图形用户界面元素.
与 Spatial shaders (3D/空间着色器)相比,CanvasItem(画布项/2D)着色器包含的内置变量和功能要少一些,但它们依然保持着相同的基本结构,都包含顶点(vertex)、片段(fragment)和光照(light)处理函数。
渲染模式
渲染模式 |
描述 |
|---|---|
blend_mix |
混合混合模式(Alpha 为透明度),默认。 |
blend_add |
添加混合模式。 |
blend_sub |
减法混合模式。 |
blend_mul |
乘法混合模式。 |
blend_premul_alpha |
预乘 Alpha 混合模式。 |
blend_disabled |
禁用混合,值(包括 Alpha)会按原样写入。 |
unshaded |
结果只使用反照率。材质中不会发生照明/阴影。 |
light_only |
仅在光照通道(light pass)中进行绘制。 |
skip_vertex_transform |
需要在 |
world_vertex_coords |
|
内置
标记为 in (输入)的值是只读的。标记为 out (输出)的值可以被写入(赋值),但它们不一定包含有意义的初始值。标记为 inout (输入输出)的值会提供一个合理的默认值,并且可以被选择性地写入。采样器(Samplers)不能被写入,所以没有进行任何标记。
并非所有的内置变量在所有处理函数中都是可用的。如果你想从 fragment() 函数中访问顶点的内置变量,可以使用插值变量。同样的道理也适用于从 light() 函数中访问内置变量。
全局内置变量
全局的内置在所有地方均可用,包括自定义函数中。
内置 |
描述 |
|---|---|
in float TIME |
自引擎启动以来的全局时间,以秒为单位。它每经过 |
in float PI |
常量 |
in float TAU |
常量 |
in float E |
常量 |
顶点内置
顶点数据(VERTEX)使用局部空间呈现(像素坐标,相对于 Node2D 的原点)。如果不写入,这些值就不会被修改,会原样传下去。
用户可以禁用内置的‘从模型空间到世界空间’的转换(不过后续的世界到屏幕空间以及投影转换依然会照常进行),并通过以下代码来手动完成这个转换:
shader_type canvas_item;
render_mode skip_vertex_transform;
void vertex() {
VERTEX = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}
其他内置变量(比如 UV 和 COLOR )如果没有被修改过,也会直接原封不动地传递到 fragment() (片段函数)中。
对于实例化(Instancing), INSTANCE_CUSTOM 变量包含了实例的自定义数据。当使用粒子系统时,这些信息通常是:
x:旋转角度,单位为弧度。
y:生命周期的阶段(
0.0到1.0)。z:动画帧。
内置 |
描述 |
|---|---|
in mat4 MODEL_MATRIX |
从局部空间到世界空间的转换矩阵。世界空间,其实就是你在编辑器里平时操作时用到的那些坐标。 |
in mat4 CANVAS_MATRIX |
从世界空间到画布空间的转换矩阵。在画布空间中,屏幕的左上角是坐标原点 (0, 0),坐标范围从 |
in mat4 SCREEN_MATRIX |
从画布空间(Canvas space)到裁剪空间(Clip space)的变换。在裁剪空间中,坐标的取值范围是从 |
in int INSTANCE_ID |
实例化的实例 ID。 |
in vec4 INSTANCE_CUSTOM |
实例自定义数据. |
in bool AT_LIGHT_PASS |
始终为 |
in vec2 TEXTURE_PIXEL_SIZE |
默认 2D 纹理的归一化像素尺寸。对于一个纹理尺寸为 64x32 像素的 Sprite2D 来说, |
inout vec2 VERTEX |
顶点位置,使用局部空间。 |
in int VERTEX_ID |
顶点缓冲区中当前顶点的索引。 |
inout vec2 UV |
归一化的纹理坐标。范围从 |
inout vec4 COLOR |
来自顶点图元的颜色,乘以 CanvasItem 的 modulate (调制色),再乘以 CanvasItem 的 self_modulate (自身调制色)。 |
inout float POINT_SIZE |
点绘图的点大小. |
in vec4 CUSTOM0 |
来自顶点图元的自定义值。 |
in vec4 CUSTOM1 |
来自顶点图元的自定义值。 |
片段内置
COLOR 与 TEXTURE
内置变量 COLOR 用于以下几种情况:
在
vertex()(顶点函数)中,COLOR包含了来自顶点图元的颜色,乘以 CanvasItem 的 modulate (调制色),再乘以 CanvasItem 的 self_modulate (自身调制色)。在
fragment()(片段函数)中,输入值COLOR其实就是同一个值(指顶点的原始颜色)乘以默认TEXTURE(默认纹理/贴图)的颜色(如果该纹理存在的话)。在
fragment()(片段函数)中,COLOR同时也是最终的输出结果。
某些节点(例如 Sprite2D )默认就会显示一张纹理,比如 texture (纹理属性)。当你使用了自定义的 fragment() (片段函数)时,你可以有几种不同的方式来采样(读取)这张纹理。
如果只想读取默认纹理(贴图)原本的内容,而完全忽略掉顶点 COLOR (颜色)的影响:
void fragment() {
COLOR = texture(TEXTURE, UV);
}
如果想要读取默认纹理(贴图)与顶点 COLOR (颜色)相乘后的内容:
void fragment() {
// Equivalent to an empty fragment() function, since COLOR is also the output variable.
COLOR = COLOR;
}
如果想在 fragment() (片段函数)中只读取顶点的 COLOR (颜色),而忽略主纹理(贴图)的话,你必须把 COLOR 作为一个 varying(变化量/中间变量)传递过去,然后再在 fragment() 里读取它:
varying vec4 vertex_color;
void vertex() {
vertex_color = COLOR;
}
void fragment() {
COLOR = vertex_color;
}
法线
同样地,如果在 CanvasTexture 中使用了法线贴图,Godot 默认会直接启用它,并将其值赋给内置的 NORMAL 变量。但如果你使用的是一张原本为 3D 准备的法线贴图,它显示出来的效果会是反的(倒置的)。为了在着色器中正确使用它,你必须把这张贴图赋给 NORMAL_MAP 属性。这样一来,Godot 就会自动帮你把它转换成适用于 2D 的形式,并覆盖掉 NORMAL 的值。
NORMAL_MAP = texture(NORMAL_TEXTURE, UV).rgb;
内置 |
描述 |
|---|---|
in vec4 FRAGCOORD |
像素中心的坐标。在屏幕空间中。 |
in vec2 SCREEN_PIXEL_SIZE |
单个像素的尺寸。等于分辨率的倒数。 |
输入区域矩形(in vec4 REGION_RECT) |
精灵(Sprite)可见区域,格式为 |
in vec2 POINT_COORD |
用于绘制点的坐标,取值范围在 0.0 到 1.0 之间。 |
sampler2D TEXTURE |
默认的2D纹理. |
in vec2 TEXTURE_PIXEL_SIZE |
默认 2D 纹理的归一化像素尺寸。对于一个纹理尺寸为 64x32 像素的 Sprite2D 来说, |
in bool AT_LIGHT_PASS |
始终为 |
sampler2D SPECULAR_SHININESS_TEXTURE |
该物体的镜面高光光泽度贴图。 |
in vec4 SPECULAR_SHININESS |
从纹理中采样得到的镜面高光光泽度颜色。 |
in vec2 UV |
来自 |
in vec2 SCREEN_UV |
当前像素的屏幕 UV 坐标。 |
sampler2D SCREEN_TEXTURE |
在 Godot 4 中移除。请改用 |
inout vec3 NORMAL |
从 |
sampler2D NORMAL_TEXTURE |
默认 2D 法线纹理。 |
out vec3 NORMAL_MAP |
配置原本用于 3D 的法线贴图,使其能在 2D 中使用。如果启用了该选项,它会直接覆盖掉 |
out float NORMAL_MAP_DEPTH |
用于缩放的法线贴图深度。 |
inout vec2 VERTEX |
屏幕空间中的像素位置。 |
inout vec2 SHADOW_VERTEX |
与 |
inout vec3 LIGHT_VERTEX |
与 |
inout vec4 COLOR |
来自 |
内置灯光
光照处理函数在 Godot 4.x 中的工作方式与 Godot 3.x 有所不同。在 Godot 4.x 中,所有的光照计算都是在常规的绘制阶段(regular draw pass)中完成的。换句话说,Godot 不再需要为每一个灯光把物体重新绘制一遍。
如果你不想让 light() 函数运行,可以使用 unshaded (无阴影/不受光照)渲染模式。如果你只想看到光照对物体产生的影响,则可以使用 light_only (仅光照)渲染模式;当你希望物体只在被光照覆盖的地方才可见时,这个模式会非常有用。
如果你定义了一个 light() 函数,它就会完全替换掉引擎内置的光照函数,哪怕你写的这个函数里面是空的(什么都没写)。
下面是一个光照着色器(light shader)的示例,它会将 CanvasItem(画布项)的法线贴图(normal map)也纳入计算:
void light() {
float cNdotL = max(0.0, dot(NORMAL, LIGHT_DIRECTION));
LIGHT = vec4(LIGHT_COLOR.rgb * COLOR.rgb * LIGHT_ENERGY * cNdotL, LIGHT_COLOR.a);
}
内置 |
描述 |
|---|---|
in vec4 FRAGCOORD |
像素中心的坐标。在屏幕空间中。 |
in vec3 NORMAL |
输入法线。 |
in vec4 COLOR |
输入颜色。这就是 |
in vec2 UV |
来自 |
sampler2D TEXTURE |
CanvasItem(画布项)当前正在使用的纹理。 |
in vec2 TEXTURE_PIXEL_SIZE |
|
in vec2 SCREEN_UV |
当前像素的屏幕 UV 坐标。 |
in vec2 POINT_COORD |
点精灵的UV. |
in vec4 LIGHT_COLOR |
该 Light2D 的 Color 。如果这盏灯是 PointLight2D ,则该颜色会与灯光的 texture 进行相乘。 |
in float LIGHT_ENERGY |
|
in vec3 LIGHT_POSITION |
Light2D 在屏幕空间中的位置。如果使用的是 DirectionalLight2D ,则该值始终为 |
in vec3 LIGHT_DIRECTION |
Light2D 在屏幕空间中的方向。 |
in bool LIGHT_IS_DIRECTIONAL |
如果当前这个渲染通道(pass)属于 DirectionalLight2D ,则为 |
in vec3 LIGHT_VERTEX |
像素位置,处于屏幕空间中(经过了 |
inout vec4 LIGHT |
该 Light2D 的输出颜色。 |
in vec4 SPECULAR_SHININESS |
镜面反射光泽度,由对象的纹理设置。 |
out vec4 SHADOW_MODULATE |
用这种颜色去乘以(叠加)投射在该点上的阴影。 |
SDF 函数
另外还实现了一些额外的函数,用于对自动生成的‘有向距离场(Signed Distance Field)’纹理进行采样。这些函数可以在 CanvasItem 着色器的 fragment() 和 light() 函数中使用。只要是从这些受支持的函数中被调用的,自定义函数同样可以使用它们。
有向距离场(SDF)是根据场景中存在的、且启用了 SDF 碰撞( SDF Collision ) 属性的 LightOccluder2D 节点生成的(默认情况下该属性即为开启状态)。更多详细信息,请参阅 2D lights and shadows 的相关文档。
函数 |
描述 |
|---|---|
float texture_sdf (vec2 sdf_pos) |
执行2D纹理读取。 |
vec2 texture_sdf_normal (vec2 sdf_pos) |
根据 SDF(有向距离场)纹理来计算法线。 |
vec2 sdf_to_screen_uv (vec2 sdf_pos) |
将屏幕 UV 转换为 SDF(有向距离场)。 |
vec2 screen_uv_to_sdf (vec2 uv) |
将屏幕 UV 转换为 SDF(有向距离场)。 |