着色器简介
本页面会讲解什么是着色器,会为你综述其在 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 中,着色器由三个主要函数组成:vertex()
、fragment()
、light()
。
vertex()
函数运行在网格的所有顶点上,会设置它们的位置和一些其他与该顶点相关的变量。fragment()
函数运行在网格所覆盖的每一个像素上。它会使用vertex()
函数所输出的值,这些值会在顶点之间进行插值。light()
函数运行在每一个像素以及每一盏灯光上。他的变量是从fragment()
函数以及之前的运行中获取的。
警告
如果启用了 vertex_lighting
渲染模式,或者在项目设置中启用了 Rendering > Quality > Shading > Force Vertex Shading(渲染 > 质量 > 着色 > 强制顶点着色),则不会运行 light()
函数。在移动平台上默认启用。
着色器类型
你所编写的着色器必须指定类型(2D、3D、粒子),不存在所有场景都可以使用的通用配置。不同的类型支持不同的渲染模式、内置变量、处理函数。
在 Godot 中,所有的着色器都需要在第一行指定它们的类型,类似这样:
shader_type spatial;
有以下类型可用:
用于 3D 渲染的 spatial。
用于 2D 渲染的 canvas_item。
用于粒子系统的 particles。
渲染模式
可以在着色器的第二行,也就是在着色器类型之后,指定渲染模式,类似这样:
shader_type spatial;
render_mode unshaded, cull_disabled;
渲染模式会修改 Godot 应用着色器的方式。例如,unshaded
模式会让引擎跳过内置的光线处理器函数。
每种着色器类型都有不同的渲染模式。每种着色器类型的完整渲染模式列表请参阅参考手册。
处理器函数
根据着色器类型的不同,你可以覆盖不同的处理器函数。在 spatial
和 canvas_item
中,你可以使用 vertex()
、fragment()
、light()
。而在 particles
中则只能使用 vertex()
。
顶点处理器
在 spatial
和 canvas_item
着色器中,会为每一个顶点调用 vertex()
处理函数。在 particles
着色器中则会为每一个粒子调用一次。
你的世界中的几何体上,每一个顶点都有位置、颜色等属性。该函数会修改这些值,并将其传入片段函数。你也可以借助 varying 向片段着色器传递额外的数据。
默认情况下,Godot 会为你对顶点信息进行变换,这是将几何体投影到屏幕上所必须的。你可以使用渲染模式来自行变换数据;示例见 Spatial 着色器文档。
片段处理器
fragment()
处理函数的作用是设置每一个像素的 Godot 材质参数。这里的代码会在绘制的对象或图元的每一个可见像素上执行。只能在 spatial
和 canvas_item
着色器中使用。
片段函数的标准用途是设置用于计算光照的材质属性。例如,你可以为 ROUGHNESS
、RIM
、TRNASMISSION
等设置值,告诉光照函数光照应该如何处理对应的片段。这样就可以控制复杂的着色管线,而不必让用户编写过多的代码。如果你不需要这一内置功能,那么你可以忽略它,自行编写光照处理函数,Godot 会将其优化掉。例如,如果你没有向 RIM
写入任何值,那么 Godot 就不会计算边缘光照。编译时,Godot 会检查是否使用了 RIM
;如果没有,那么它就会把对应的代码删除。因此,你就不会在没有使用的效果上浪费算力。
光照处理器
light()
处理器也会在每一个像素上运行,并且同时还会在每一个影响该对象的灯光上运行。如果没有灯光影响该对象则不会运行。它会被用于 fragment()
处理器,一般会在 fragment()
函数中进行材质属性设置时执行。
light()
处理器在 2D 和 3D 中的工作方式不同;每种工作方式的详细描述请参阅它们对应的文档 CanvasItem 着色器 and Spatial 着色器。