你的第一个2D Shader(CanvasItem Shader)

tech2024-11-06  13

原文地址:Docs » Shading » Your first shader » Your first CanvasItem shader

导论

本教程将从实践的角度手把手教你写一个包含vertex函数和fragment函数的Shader。本教程完全面向初学者。

注意:如果你有编写Shader的经验或者仅仅是想Godot Shader的工作概要,请阅读《Shader手册》

准备工作

2D Shader(CanvasItem Shader)被用于绘制Godot中的所有2D对象,3D Shader(Spatial Shader)被用于绘制所有3D对象。

想要使用Shader,必须首先将一个材质(Material)挂载到对象上,然后再把Shader挂载到材质上。材质是一种资源(Resource)类型。如果想使用同一材质绘制多个对象,那么每个对象上都要挂载这个材质。

所有派生自CanvasItem的对象都拥有一个材质属性,包括所有的GUI 节点,Sprite节点,TileMap节点,MeshInstance2D节点等等。这些节点还有一个选项可以直接继承其父节点的材质。

首先,创建一个Sprite节点。实际上你可以创建任何的CanvasItem节点,但在这个教程里我们使用一个Sprite节点。

在检视面板中,点击Texture(此时它应该显示为空[empty]),选择load,然后选择Icon.png,对于新项目这个图片应当是Godot的icon。这时你应该可以在视口中看到这个icon。

接下来,看一下检视面板,在CanvasItem区域的下面,点击"Material"然后选择“New ShaderMaterial”,来创建一个新的材质资源。点击此时显示的球状区域,此时Godot并不知道你要写一个2D Shader还是3D Shader,所以它默认显示一个3D Shader。

点击“Shader”并且选择“New Shader”。最后,点击新的Shader资源,这时候Shader编辑器将会打开。此时,你已经准备好写你的第一个Shader啦。

你的第一个2D Shader(CanvasItem shader)

在Godot中,所有的Shader代码的第一行必须声明其着色器类型,格式如下:

shader_type canvas_item;

因为我们要写一个2D Shader,所以我们在第一行声明canvas_item。我们所有的代码都要写在这个声明的下方。

这一行将告诉引擎需要提供哪些内置属性和函数。

在Godot中,你可以重载vertex,fragment,light这三个函数来控制Shader执行。这个教程将手把手带你写一个包含vertex,fragment函数的Shader,light函数相对比较繁琐,所以本教程暂时不涉及light函数。

你的第一个片元函数(fragment function)

对于一个Sprite,片元函数会逐像素执行,并决定每一个像素的颜色。

片元函数的可绘制范围被严格约束在Sprite所覆盖的区域。这意味着,类似如下功能你将无法用片元函数实现,比如在一个精灵外面画一个轮廓。

最简单的片元函数仅仅是为每一个像素指定了一个单一的颜色。

我们定义一个vec4类型变量,并把它赋值给内置属性COLOR,vec4是构造4维向量的简写形式。获取更多关于向量的内容,请阅读《Vector math tutorial》 COLOR既是片元函数的输入值也是它存放最终结果的值。

void fragment(){ COLOR = vec4(0.4, 0.6, 0.9, 1.0); }

恭喜!你成功地完成了你的第一个Godot Shader。

接下来,让我们整点更复杂的。

在片元函数中有很多内置属性可供我们利用来计算COLOR,UV就是其中之一。一个Sprite的UV坐标在你不知不觉中就被指定了,它会告诉Shader网格(mesh)的某一个部分对应着纹理(texture)的哪个部分。

在片元函数中UV是只读的,但你可以在其它函数中使用它,也可用它直接给COLOR赋值。

UV的值域是介于0-1之间,并且是从左向右,从上向下的。

void fragment() { COLOR = vec4(UV, 0.5, 1.0); }

[图片上传失败…(image-6d9688-1561435363231)]

使用TEXTURE内置属性

如果你想手动修改一个Sprite的Texture颜色,你不能像下面这样做:

void fragment(){ //这个shader将产生一个全白的矩形。 COLOR.b = 1.0; }

默认的片元函数读取texture并将其显示。当你重写了默认的片元函数,你就失去了这个功能,所以你必须自己实现它。即使用texture函数手动读取texture。某些节点,比如Sprite有一个专用的texture变量,可以在Shader中用TEXTURE来访问。使用它加上UV以及texture函数,可以绘制Sprite。

void fragment(){ COLOR = texture(TEXTURE, UV); //读取纹理 COLOR.b = 1.0; //将蓝色通道设为1.0 }

使用Uniform变量

uniform变量用于在这个Shader中传递数据。(译者注:实际上有点类似于序列化的成员变量)

你可以在Shader的顶部按如下方式声明uniform变量:

uniform float size;

要获取更多用法请阅读Shading Language doc

添加一个uniform变量来给便Sprite中的blue值。

uniform float blue = 1.0; // 可以对uniform指定一个默认值 ​ void fragment(){ COLOR = texture(TEXTURE, UV); //读取纹理 COLOR.b = blue; }

现在,你可以在编辑器中修改Sprite的blue值了。在检视面板你创建的这个Shader下面,有一个Shader Param。展开这个折叠的部分,你将看到你刚刚声明的uniform变量。如果你修改这个值,他将覆盖掉你提供的默认值。

代码和Shader的交互

你可以在代码中通过节点的material资源来调用set_shader_param()以修改uniform变量的值。以Sprite节点为例,如下代码可以修改blue的值:

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

注意:uniform的名字是一个字符串。这个字符串必须和Shader中的uniform变量名完全一样,包括大小写。

你的第一个顶点函数(vertex function)

现在,我们已经有了一个片元函数,让我们写一个顶点函数吧。

使用顶点函数计算每个顶点应该绘制到屏幕的哪个区域。

VERTEX是顶点函数中最重要的内置属性。最初,它指定模型中顶点的坐标,但你也可以改写它,来决定最终在哪里绘制顶点。VERTEX是一个vec2类型变量,它最初以局部坐标表示(即,不依赖于摄像机,视口以及父节点)。

你可以直接给VERTEX增加偏移量来偏移顶点。

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

结合内置属性TIME,甚至还可以实现简单的动画。

void vertex() { // 让这个Sprite以自身位置为中心绕圈运动 VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0); }
总结

本质上,Shader就是做你目前看到的这些工作,它们计算VERTEX和COLOR。你可以设计出更复杂的数学策略来给那些变量赋值。

想要汲取灵感,请看一些关于Shader的高级教程,或者看一写其它的网站Shadertoy 以及 The Book of Shaders

最新回复(0)