你的第一个画布项着色器

简介

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

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

注解

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

场景布置

CanvasItem 着色器用于绘制Godot中的所有2D对象, 而 Spatial 着色器用于绘制所有3D对象.

要使用着色器, 它必须要被附加到一个 材质 上, 这个材质也必须附加在一个对象上. 材质是一种 资源. 若要使用同一材质绘制多个对象, 该材质必须附加到每个对象上.

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

首先, 创建一个精灵节点. 你其实可以用任意的画布项, 但是在这个教程中我们用精灵.

在属性面板里, 点击 "材质" 旁边写着 "[空]" 的地方然后选择 "读取", 接着选中 "Icon.png". 对于新的项目, 这个就是Godot的图标. 你现在就会在视区中看到这个图标.

接下来, 在属性面板下的画布项部分中, 点击 "材质" 并选择 "新着色器材质". 这会创建一个新的材质资源. 然后点击新出现的球体.Godot目前还不知道你是要写画布项着色器还是空间着色器, 它显示空间着色器的输出预览, 所以你看到的是默认的空间着色器的输出.

点击 "着色器" 并选中 "新着色器". 最后, 点击新创建的着色器资源, 着色器编辑器就会打开. 你现在就已经准备好开始写你的第一个着色器了.

你的第一个画布项着色器

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

shader_type canvas_item;

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

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

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

您的第一个片段函数

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

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

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

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

void fragment(){
  COLOR = vec4(0.4, 0.6, 0.9, 1.0);
}
../../../_images/blue-box.png

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

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

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

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

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

../../../_images/iconuv.png
void fragment() {
  COLOR = vec4(UV, 0.5, 1.0);
}
../../../_images/UV.png

使用内置变量 TEXTURE

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

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

默认的片段函数从纹理中读取并显示它. 当你覆盖了默认的片段函数, 你就失去了这个功能, 所以你必须自己实现它. 你使用 "纹理" 函数从纹理中读取. 某些节点, 比如精灵, 有一个专用的纹理变量, 可以在着色器中使用 "纹理" 访问. 使用它与 "UV" 和 "纹理" 一起绘制精灵.

void fragment(){
  COLOR = texture(TEXTURE, UV); //read from texture
  COLOR.b = 1.0; //set blue channel to 1.0
}
../../../_images/blue-tex.png

Uniform输入

Uniform输入是用来向着色器传递数据的, 这些数据在整个着色器中是相同的.

你可以通过在着色器顶部定义来使用Uniform值, 如:

uniform float size;

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

添加一个Uniform值来改变精灵中蓝色量.

uniform float blue = 1.0; // you can assign a default value to uniforms

void fragment(){
  COLOR = texture(TEXTURE, UV); //read from texture
  COLOR.b = blue;
}

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

代码与着色器的交互

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

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

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

你的第一个顶点函数

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

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

顶点函数中最重要的变量是顶点. 最初, 它指定模型中的顶点坐标, 但你也要写进去决定在哪里画那些顶点."顶点" 是一个 "二维向量", 它最初出现在局部空间中(即与摄像机, 视图或父节点无关).

您可以通过直接添加到 "顶点" 来偏移顶点.

void vertex() {
  VERTEX += vec2(10.0, 0.0);
}

与内置变量 "时间" 相结合, 可用于简单的动画.

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

总结

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

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