您的第一个 2D 着色器

前言

着色器是在 GPU 上运行,用来渲染图像的一种特殊程序。现代渲染都是通过着色器实现的。若想了解关于着色器更详细的说明,请查看着色器是什么

本教程将重点介绍实际编写着色器程序的各个方面, 引导您走过使用顶点和片段函数编写着色器的整个流程. 本教程面向着色器.

备注

如果你在着色器方面有一定的经验,只是想知道着色器在 Godot 中是如何运作的,请参阅着色器参考

场景布置

CanvasItem 着色器用于绘制 Godot 中的所有 2D 的

对象,而 Spatial 着色器则用于绘制所有 3D 对象。

要使用着色器,它必须要被附加到一个 Material(材质)上,而这个材质也必须附加在一个对象上。材质是一种 Resource。若要使用同一材质绘制多个对象,该材质必须附加到每个对象上。

所有继承自 CanvasItem 的对象都有一个材质属性, 这包含所有的 GUI 元素, 精灵, 方块地图, 2D 网格实例 等等. 它们同时也可以选择性地继承其父类的材质. 如果你有要使用同一材质的很多节点, 这个特性就可以派上用场.

首先,创建一个 Sprite 节点。你其实可以用任意的 CanvasItem,但是在这个教程中我们用 Sprite。

在“检查器”中,点击“Texture”旁边写着“[空]”的地方然后选择“加载”,再选择“Icon.png”。对于新的项目,这个就是 Godot 的图标。你现在就会在视口中看到这个图标。

接下来,在“检查器”下的 CanvasItem 部分中,在“Material”旁点击并选择“新建 ShaderMaterial”。这会创建一个新的材质资源。然后点击新出现的球体。Godot 目前还不知道你是要写 CanvasItem 着色器还是 Spatial 着色器,它显示 Spatial 着色器的输出预览,所以你看到的是默认的 Spatial 着色器的输出。

点击“Shader”旁边的位置并选择“新建 Shader”。最后,点击新创建的着色器资源,就会打开着色器编辑器。你现在就已经准备好开始写你的第一个着色器了。

你的第一个 CanvasItem 着色器

在Godot中, 所有的着色器第一行都是指定着色器类型的, 格式如下:

  1. shader_type canvas_item;

因为我们正在编写CanvasItem着色器, 所以我们在第一行中指定了 canvas_item. 我们所有的代码都会在这个声明下面.

这一行告诉游戏引擎要提供你哪些内置变量以及函数.

在Godot中, 你可以重写三个函数来控制着色器的运作, 它们是 vertex (顶点函数), fragment (片段函数)和 light (光照函数). 本教程会引导你写出一个包含顶点和片段函数的着色器. 因为光照函数比另外两个函数要复杂非常多, 所以在这里不会进行讲解.

您的第一个片段函数

片段函数对精灵中的每个像素进行操作, 并且决定这个像素应该是什么颜色的.

它们限制在精灵覆盖的那些像素中, 这也就意味着, 你无法用片段函数来实现例如在精灵周围加边框的事情.

最基础的片段函数仅仅给每个像素赋予一个颜色.

我们向内置变量 COLOR 中写入一个 vec4 来做到这点. vec4 是创建一个四维向量的简写形式. 若想进一步了解向量, 请参阅 向量数学教程. COLOR 变量既是片段函数的一个输入, 同时也是它的最终输出.

  1. void fragment(){
  2. COLOR = vec4(0.4, 0.6, 0.9, 1.0);
  3. }

../../../_images/blue-box.png

恭喜你!你成功在 Godot 中写出了你的第一个着色器。

接着, 我们来讨论更复杂的事情.

你可以使用片段函数中的很多输入来计算 COLOR, UV 就是其中的一个. 你的精灵指定了UV坐标(在你不知情的情况下!), 而它们告诉着色器, 对于网格的每个部分从纹理的何处读取信息.

在片段函数中你只能从 UV 中读取, 但是你可以在其他函数中使用, 或者直接对 COLOR 赋值.

UV 取值在0-1之间, 从左到右, 由上到下.

../../../_images/iconuv.png

  1. void fragment() {
  2. COLOR = vec4(UV, 0.5, 1.0);
  3. }

../../../_images/UV.png

使用内置变量 TEXTURE

当你想调整 Sprite 中的颜色时,你不能像下面的代码那样手动修改纹理中的颜色。

  1. void fragment(){
  2. //this shader will result in an all white rectangle
  3. COLOR.b = 1.0;
  4. }

默认的片段函数从纹理中读取并显示它。当你覆盖了默认的片段函数,你就失去了这个功能,所以你必须自己实现它。你使用 texture 函数从纹理中读取。Sprite 等节点有一个专用的纹理变量,可以在着色器中使用 TEXTURE 访问。可以用它与 UVtexture 一起绘制 Sprite。

  1. void fragment(){
  2. COLOR = texture(TEXTURE, UV); //read from texture
  3. COLOR.b = 1.0; //set blue channel to 1.0
  4. }

../../../_images/blue-tex.png

Uniform 输入

Uniform 输入是用来向着色器传递数据的,这些数据在整个着色器中都是一致的。

你可以像这样通过在着色器顶部定义来使用 Uniform 值:

  1. uniform float size;

用法的更多详情请参见着色语言文档

添加一个 Uniform 值来改变 Sprite 中蓝色量。

  1. uniform float blue = 1.0; // you can assign a default value to uniforms
  2. void fragment(){
  3. COLOR = texture(TEXTURE, UV); //read from texture
  4. COLOR.b = blue;
  5. }

现在你可以在编辑器中改变Sprite的蓝色量. 回头看看你创建着色器的地方的属性面板. 你应该看到一个叫做 “Shader Param” 的部分. 展开这个部分, 你会看到你刚刚声明的Uniform. 如果你在编辑器中改变这个值, 它将覆盖你在着色器中提供的默认值.

代码与着色器的交互

你可以使用在节点的材质资源上调用的函数 set_shader_param() , 从代码中改变Uniform. 对于一个Sprite节点, 可以使用下面的代码来设置 blue Uniform.

  1. var blue_value = 1.0
  2. material.set_shader_param("blue", blue_value)

注意,uniform值的名称是一个字符串. 字符串必须与它在着色器中的书写方式完全匹配, 包括拼写和大小写.

你的第一个顶点函数

现在我们有了一个片段函数, 我们再写一个顶点函数.

使用顶点函数计算屏幕上每个顶点的结束位置.

顶点函数中最重要的变量是 VERTEX。它最初指定的是模型中的顶点坐标,但你也会通过往里面写值来决定把这些顶点画到哪里。VERTEX 是一个 vec2,最初使用的是局部空间(即与摄像机、视口、父节点无关)。

您可以通过直接调整 VERTEX 来偏移顶点。

  1. void vertex() {
  2. VERTEX += vec2(10.0, 0.0);
  3. }

与内置变量 TIME 相结合,可用于简单的动画。

  1. void vertex() {
  2. // Animate Sprite moving in big circle around its location
  3. VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0);
  4. }

总结

着色器的核心, 如你所见, 是计算 VERTEXCOLOR. 你可以制定更复杂的数学策略来给这些变量赋值.

一些更高级的着色器教程可以给你启发, 如 Shadertoy着色器之书 .