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.

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

需要在 vertex() (顶点函数)中手动对 VERTEX (顶点)进行坐标转换。

world_vertex_coords

VERTEX (顶点)是在世界坐标系中被修改的,而不是局部坐标系。

内置

标记为 in (输入)的值是只读的。标记为 out (输出)的值可以被写入(赋值),但它们不一定包含有意义的初始值。标记为 inout (输入输出)的值会提供一个合理的默认值,并且可以被选择性地写入。采样器(Samplers)不能被写入,所以没有进行任何标记。

并非所有的内置变量在所有处理函数中都是可用的。如果你想从 fragment() 函数中访问顶点的内置变量,可以使用插值变量。同样的道理也适用于从 light() 函数中访问内置变量。

全局内置变量

全局的内置在所有地方均可用,包括自定义函数中。

内置

描述

in float TIME

自引擎启动以来的全局时间,以秒为单位。它每经过 3,600 秒就会重置循环一次(这个时长可以通过 rollover 设置来修改)。它受 time_scale (时间缩放/游戏速度)的影响,但不受暂停(pausing)的影响。如果你需要一个不受 time_scale 影响的 TIME 变量,可以添加你自己的 global shader uniform ,并在每一帧更新它。

in float PI

常量 PI (值为 3.141592 )。它是圆的周长与直径的比值,也是转半圈(180度)所包含的弧度数。

in float TAU

常量 TAU (值为 6.283185 )。它等同于 PI * 2 ,也就是一个完整圆周(整圈)所包含的弧度数。

in float E

常量 E (值为 2.718281 )。即欧拉数,自然对数的底数。

顶点内置

顶点数据(VERTEX)使用局部空间呈现(像素坐标,相对于 Node2D 的原点)。如果不写入,这些值就不会被修改,会原样传下去。

用户可以禁用内置的‘从模型空间到世界空间’的转换(不过后续的世界到屏幕空间以及投影转换依然会照常进行),并通过以下代码来手动完成这个转换:

shader_type canvas_item;
render_mode skip_vertex_transform;

void vertex() {

    VERTEX = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}

其他内置变量(比如 UVCOLOR )如果没有被修改过,也会直接原封不动地传递到 fragment() (片段函数)中。

对于实例化(Instancing), INSTANCE_CUSTOM 变量包含了实例的自定义数据。当使用粒子系统时,这些信息通常是:

  • x:旋转角度,单位为弧度。

  • y:生命周期的阶段(0.01.0)。

  • z:动画帧。

内置

描述

in mat4 MODEL_MATRIX

从局部空间到世界空间的转换矩阵。世界空间,其实就是你在编辑器里平时操作时用到的那些坐标。

in mat4 CANVAS_MATRIX

从世界空间到画布空间的转换矩阵。在画布空间中,屏幕的左上角是坐标原点 (0, 0),坐标范围从 (0.0, 0.0) 一直延伸到视口的大小。

in mat4 SCREEN_MATRIX

从画布空间(Canvas space)到裁剪空间(Clip space)的变换。在裁剪空间中,坐标的取值范围是从 (-1.0, -1.0)(1.0, 1.0).

in int INSTANCE_ID

实例化的实例 ID。

in vec4 INSTANCE_CUSTOM

实例自定义数据.

in bool AT_LIGHT_PASS

始终为 false

in vec2 TEXTURE_PIXEL_SIZE

默认 2D 纹理的归一化像素尺寸。对于一个纹理尺寸为 64x32 像素的 Sprite2D 来说,TEXTURE_PIXEL_SIZE = vec2(1.0/64.0, 1.0/32.0)

inout vec2 VERTEX

顶点位置,使用局部空间。

in int VERTEX_ID

顶点缓冲区中当前顶点的索引。

inout vec2 UV

归一化的纹理坐标。范围从 0.01.0

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

像素中心的坐标。在屏幕空间中。xy 指定视口中的位置。视口的左上角是原点 (0.0, 0.0) 。视口的右下角是 (1.0, 1.0)

in vec2 SCREEN_PIXEL_SIZE

单个像素的尺寸。等于分辨率的倒数。

输入区域矩形(in vec4 REGION_RECT

精灵(Sprite)可见区域,格式为 (x, y, width, height) 。该数值会根据 Sprite2D 的 region_enabled (启用区域)属性发生变化。数值是归一化的;举个例子,如果一张 1000×800 的纹理上有一个 600×400 的区域,并且带有 100×100 的偏移量,那么对应的数值就是 vec4(0.1, 0.125, 0.6, 0.5) 。如果 X/Y 的偏移量为负数,或者区域尺寸超过了纹理本身的尺寸,那么数值可能会超出 0.0 到 1.0 的范围。

in vec2 POINT_COORD

用于绘制点的坐标,取值范围在 0.0 到 1.0 之间。

sampler2D TEXTURE

默认的2D纹理.

in vec2 TEXTURE_PIXEL_SIZE

默认 2D 纹理的归一化像素尺寸。对于一个纹理尺寸为 64x32 像素的 Sprite2D 来说,TEXTURE_PIXEL_SIZE = vec2(1.0/64.0, 1.0/32.0)

in bool AT_LIGHT_PASS

始终为 false

sampler2D SPECULAR_SHININESS_TEXTURE

该物体的镜面高光光泽度贴图。

in vec4 SPECULAR_SHININESS

从纹理中采样得到的镜面高光光泽度颜色。

in vec2 UV

来自 vertex() (顶点函数)的 UV 坐标。对于启用了‘区域’(region)功能的 Sprite2D 来说,使用它会采样整张完整的纹理。如果想只采样 Sprite2D 属性中定义的那个区域,请改用 REGION_RECT

in vec2 SCREEN_UV

当前像素的屏幕 UV 坐标。

sampler2D SCREEN_TEXTURE

在 Godot 4 中移除。请改用 sampler2Dhint_screen_texture

inout vec3 NORMAL

NORMAL_TEXTURE (法线贴图)中读取到的法线。它是可写的(支持修改)。

sampler2D NORMAL_TEXTURE

默认 2D 法线纹理。

out vec3 NORMAL_MAP

配置原本用于 3D 的法线贴图,使其能在 2D 中使用。如果启用了该选项,它会直接覆盖掉 NORMAL (法线)的值。

out float NORMAL_MAP_DEPTH

用于缩放的法线贴图深度。

inout vec2 VERTEX

屏幕空间中的像素位置。

inout vec2 SHADOW_VERTEX

VERTEX 相同,但是可以通过写入来修改阴影。

inout vec3 LIGHT_VERTEX

VERTEX 相同,但是可以通过写入来修改灯光。Z 分量代表高度。

inout vec4 COLOR

来自 vertex() (顶点函数)的 COLORTEXTURE (纹理)颜色相乘后的结果。同时,它也是最终输出的颜色值。

内置灯光

光照处理函数在 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

像素中心的坐标。在屏幕空间中。xy 指定视口中的位置。视口的左上角是原点 (0.0, 0.0) 。视口的右下角是 (1.0, 1.0)

in vec3 NORMAL

输入法线。

in vec4 COLOR

输入颜色。这就是 fragment() (片段函数)的输出结果。

in vec2 UV

来自 vertex() (顶点函数)的 UV 坐标,它等同于 fragment() (片段函数)中的 UV。

sampler2D TEXTURE

CanvasItem(画布项)当前正在使用的纹理。

in vec2 TEXTURE_PIXEL_SIZE

TEXTURE 的归一化像素尺寸。例如,对于一个 纹理 尺寸为 64×32 像素的 Sprite2D,TEXTURE_PIXEL_SIZE = vec2(1.0 / 64.0, 1.0 / 32.0)

in vec2 SCREEN_UV

当前像素的屏幕 UV 坐标。

in vec2 POINT_COORD

点精灵的UV.

in vec4 LIGHT_COLOR

Light2DColor 。如果这盏灯是 PointLight2D ,则该颜色会与灯光的 texture 进行相乘。

in float LIGHT_ENERGY

Light2DEnergy multiplier

in vec3 LIGHT_POSITION

Light2D 在屏幕空间中的位置。如果使用的是 DirectionalLight2D ,则该值始终为 (0.0, 0.0, 0.0)

in vec3 LIGHT_DIRECTION

Light2D 在屏幕空间中的方向。

in bool LIGHT_IS_DIRECTIONAL

如果当前这个渲染通道(pass)属于 DirectionalLight2D ,则为 true

in vec3 LIGHT_VERTEX

像素位置,处于屏幕空间中(经过了 fragment() 函数的修改)。

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(有向距离场)。