你的第一个画布项着色器

简介

着色器是在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 input

统一值输入用于将数据传递到整个着色器中相同的着色器中。

你可以使用统一值定义他们在你的着色器顶部这样:

uniform float size;

For more information about usage see the Shading Language doc.

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

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

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

Interacting with shaders from code

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

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

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

Your first vertex function

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

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

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

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

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

总结

At their core, shaders do what you have seen so far, they compute VERTEX and COLOR. It is up to you to dream up more complex mathematical strategies for assigning values to those variables.

For inspiration, take a look at some of the more advanced shader tutorials, and look at other sites like Shadertoy and The Book of Shaders.