你的第一个画布项着色器

简介

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

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

注解

如果你在着色器方面有一定的经验,只是想知道着色器在Godot中是如何运作的,请参阅:ref:着色器参考 <toc-shading-reference>.

场景布置

:ref:画布项<doc_canvas_item_shader>`着色器用来绘制Godot中所有的2D对象,而:ref:`空间<doc_spatial_shader>`着色器用来绘制3D对象。

要使用着色器,它必须要被附加到一个:ref:材质<class_material>`上,这个材质也必须附加在一个对象上。材质是一种:ref:`资源<doc_resources>。若要使用同一材质绘制多个对象,该材质必须附加到每个对象上。

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

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

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

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

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

你的第一个画布项着色器

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

shader_type canvas_item;

Because we are writing a CanvasItem shader, we specify canvas_item in the first line. All our code will go beneath this declaration.

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

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

您的第一个片段函数

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

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

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

我们向内置变量``COLOR``中写入一个``vec4``来做到这点。``vec4``是创建一个四维向量的简写形式。若想进一步了解向量,请参阅:ref:向量数学教程<doc_vector_math>``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 float size;

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

添加一个统一值来改变精灵中蓝色的数量。

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;
}

现在,您可以从编辑器中更改精灵中的蓝色数量。回顾一下创建着色器的属性面板。您可以看到一个名为“着色器参数”的部分。展开这个部分,你会看到你刚刚声明的统一值。如果在编辑器中更改该值,它将覆盖着色器中提供的默认值。

代码与着色器的交互

可以使用函数“set_shader_param()”从代码中更改统一值,该函数在节点的材质资源上调用。使用精灵节点,以下代码可用于设置“蓝色”统一值。

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

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

你的第一个顶点函数

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

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

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

您可以通过直接添加到“顶点”来偏移顶点。

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着色器之书