Up to date

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

着色器简介

本页面会讲解什么是着色器,会为你综述其在 Godot 中的使用方法。引擎中着色语言的详细参考见 着色语言

着色器(Shader)是一种在图形处理单元(GPU)上运行的特殊程序。他们最初使用来为 3D 场景着色的,不过现在能做的事情就更多了。你可以用它们来控制引擎在屏幕上绘制几何体以及像素的方式,可以用来实现各种特效。

类似 Godot 的现代渲染引擎都会用着色器来执行所有绘制操作:图形卡可以并行执行成千上万条指令,可以达到惊人的渲染速度。

因为天生就是并行的,所以着色器处理信息的方式与普通的程序有所不同。着色器代码是单独针对顶点或像素执行的。你也无法在帧与帧之间存储数据。因此,使用着色器时,你需要使用与其他编程语言不同的编码和思考方式。

假设你想要把纹理中的所有像素点都设置成某个给定的颜色。使用 GDScript,你的代码会用 for 循环:

for x in range(width):
  for y in range(height):
    set_color(x, y, some_color)

在着色器中,你的代码已经是循环的一部分了,所以对应的代码应该类似这样。

void fragment() {
  COLOR = some_color;
}

备注

图形卡会为需要绘制的每一个像素调用若干次 fragment() 函数。后面会详细说明。

Godot 中的着色器

Godot 所提供的着色语言是基于流行的 OpenGL 着色语言(GLSL)的简化。引擎会为你处理一些底层的初始化工作,让编写复杂着色器更为简单。

在 Godot 中,着色器由若干主函数组成,这些函数被称为“处理器函数”。处理器函数是着色器程序的入口。有七种不同的处理器函数。

  1. vertex() 函数会为网格中的所有顶点各运行一次,用来设置顶点的位置和其他与顶点相关的变量。在 canvas_item 着色器空间着色器中使用。

  2. fragment() 函数会为网格所覆盖的所有像素各运行一次。这个函数会用到 vertex() 函数输出的值,这些值会在顶点之间进行插值。在 canvas_item 着色器空间着色器中使用。

  3. light() 函数会为每个像素和每个灯光各运行一次。这个函数会用到 fragment() 函数以及前几次运行中的变量。在 canvas_item 着色器空间着色器中使用。

  4. start() 函数会在粒子系统中的每个粒子出生时各运行一次。在粒子着色器中使用。

  5. start() 函数会为粒子系统中的每个粒子每帧时各运行一次。在粒子着色器中使用。

  6. sky() 函数会在辐射度立方体贴图需要更新时为辐射度立方体贴图中的每个像素各运行一次,也会为当前屏幕上的每个像素运行一次。在天空着色器中使用。

  7. fog() 函数会为体积雾片段体素缓冲中与 FogVolume 相交的每个片段体素运行一次。在雾着色器中使用。

警告

如果启用了 vertex_lighting 渲染模式,或者在项目设置中启用了 Rendering > Quality > Shading > Force Vertex Shading(渲染 > 质量 > 着色 > 强制顶点着色),则不会运行 light() 函数。在移动平台上默认启用。

备注

Godot 还为用户编写完全自定义的 GLSL 着色器暴露了 API。详见 使用计算着色器

着色器类型

你所编写的着色器必须指定类型(2D、3D、粒子、天空、雾),不存在所有场景都可以使用的通用配置。不同的类型支持不同的渲染模式、内置变量、处理函数。

在 Godot 中,所有的着色器都需要在第一行指定它们的类型,类似这样:

shader_type spatial;

有以下类型可用:

渲染模式

可以在着色器的第二行,也就是在着色器类型之后,指定渲染模式,类似这样:

shader_type spatial;
render_mode unshaded, cull_disabled;

渲染模式会修改 Godot 应用着色器的方式。例如,unshaded 模式会让引擎跳过内置的光线处理器函数。

每种着色器类型都有不同的渲染模式。每种着色器类型的完整渲染模式列表请参阅参考手册。

顶点处理器

spatialcanvas_item 着色器中,会为每一个顶点调用 vertex() 处理函数。在 particles 着色器中则会为每一个粒子调用一次。

你的世界中的几何体上,每一个顶点都有位置、颜色等属性。该函数会修改这些值,并将其传入片段函数。你也可以借助 varying 向片段着色器传递额外的数据。

默认情况下,Godot 会为你对顶点信息进行变换,这是将几何体投影到屏幕上所必须的。你可以使用渲染模式来自行变换数据;示例见 Spatial 着色器文档

片段处理器

fragment() 处理函数的作用是设置每一个像素的 Godot 材质参数。这里的代码会在绘制的对象或图元的每一个可见像素上执行。只能在 spatialcanvas_itemsky 着色器中使用。

片段函数的标准用途是设置用于计算光照的材质属性。例如,你可以为 ROUGHNESSRIMTRNASMISSION 等设置值,告诉光照函数光照应该如何处理对应的片段。这样就可以控制复杂的着色管线,而不必让用户编写过多的代码。如果你不需要这一内置功能,那么你可以忽略它,自行编写光照处理函数,Godot 会将其优化掉。例如,如果你没有向 RIM 写入任何值,那么 Godot 就不会计算边缘光照。编译时,Godot 会检查是否使用了 RIM;如果没有,那么它就会把对应的代码删除。因此,你就不会在没有使用的效果上浪费算力。

光照处理器

light() 处理器也会在每一个像素上运行,并且同时还会在每一个影响该对象的灯光上运行。如果没有灯光影响该对象则不会运行。它会被用于 fragment() 处理器,一般会在 fragment() 函数中进行材质属性设置时执行。

light() 处理器在 2D 和 3D 中的工作方式不同;每种工作方式的详细描述请参阅它们对应的文档 CanvasItem 着色器 and Spatial 着色器