着色器简介

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

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

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

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

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

  1. for x in range(width):
  2. for y in range(height):
  3. set_color(x, y, some_color)

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

  1. void fragment() {
  2. COLOR = some_color;
  3. }

备注

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

Godot 中的着色器

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

在 Godot 中,着色器由三个主要函数组成:vertex()fragment()light()

  1. vertex() 函数运行在网格的所有顶点上,会设置它们的位置和一些其他与该顶点相关的变量。

  2. fragment() 函数运行在网格所覆盖的每一个像素上。它会使用 vertex() 函数所输出的值,这些值会在顶点之间进行插值。

  3. light() 函数运行在每一个像素以及每一盏灯光上。他的变量是从 fragment() 函数以及之前的运行中获取的。

警告

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

着色器类型

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

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

  1. shader_type spatial;

有以下类型可用:

渲染模式

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

  1. shader_type spatial;
  2. render_mode unshaded, cull_disabled;

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

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

处理器函数

根据着色器类型的不同,你可以覆盖不同的处理器函数。在 spatialcanvas_item 中,你可以使用 vertex()fragment()light()。而在 particles 中则只能使用 vertex()

顶点处理器

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

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

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

片段处理器

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

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

光照处理器

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

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