【GMS2】Shader入门介绍

您所在的位置:网站首页 gamemakerstudio2编程语言 【GMS2】Shader入门介绍

【GMS2】Shader入门介绍

2024-02-05 20:08| 来源: 网络整理| 查看: 265

搬运自https://developer.amazon.com/zh/blogs/appstore/post/acefafad-29ba-4f31-8dae-00805fda3f58/intro-to-shaders-and-surfaces-with-gamemaker-studio-2

请支持原作者Guest Blogger

Shader通常在游戏中用于创建精美的图像特效,也是GameMaker Studio 2最先进的功能之一。但是我将尝试在本文中尽可能简单地解释所有内容。要想继续阅读,你不需要有关shader方面的任何基础,但是有必要对编程有基本的了解,并且熟悉GameMaker Studio 2的操作。那么,让我们开始吧。

什么是shader?

最初创建它们的目的是为光照提供阴影(因此得名),现在它们被用于产生各种各样的特效。shader代码与常规代码比较相似,但是(通常)由GPU而不是CPU执行。这种差异有它们自己的一套规则和限制,但往后我们再讲。

每个shader都是由两个分开的脚本组成:vertex shader和fragment shader(也叫做pixel shader)。当我刚开始学习shader时,我很难理解它们是如何运作的。不过我想出了一个比喻,让我更容易理解它们。

让我们从vertex shader开始。我们的每个贴图都由是一个矩形组成的,但是电脑更喜欢绘制三角形,所以这些矩形被分为了两个三角形。这样一来,每个贴图就会有六个顶点,但是其中两个是重叠的,所以我们只要管四个。现在,假设我们有一个遍历每个顶点并为每个顶点执行vertex shader内部代码的for循环。由于vertex shader较早执行,因此这使我们可以在将顶点坐标和颜色传递给fragment shader之前对其进行更改。

就像这样:

对于fragment shader,您可以想象与以前相同的循环,但这次覆盖了贴图中的每个像素,为你提供了像素的坐标和颜色之类的信息。在片fragment shader的代码当中,你可以执行操作和计算以确定该像素的颜色以获得你想要的特效。举个例子,如果您想用shader将精灵变成黑白的,就需要计算每个像素创建特效所需要的灰色阴影。

就像这样:

shader的代码通常由GPU执行是因为它的效率更高。现代CPU通常具有2到8个内核。每个内核一次只能执行一个任务,因此通过利用多个内核,我们可以同时执行多个任务。相反,现代GPU可以同时执行几千甚至一万个任务。这对于shader很有帮助,我们可以同时执行数千个像素的着shader代码。局限性在于我们只能访问贴图的初始状态,因此我们不知道它对其他像素所做的任何修改,因为我们不确定代码是否已在它上面运行。

GameMaker Studio 2允许用户以GLSL(OpenGL的shader语言),HLSL(高级shader语言,与DirectX配合使用)和GLSL ES(在移动设备中常见的GLSL的子集)编写shader。在本教程中,我将使用GLSL ES作为shader语言,因为它是跨系统最佳移植性的语言。三种语言之间的数学和技术应该都比较相似,语法上只会有一点差异。

Vertex shader和fragment shader

首先执行vertex shader,顾名思义,它处理顶点。 它用于计算坐标,法线和材质坐标。 这些shader在2D中并不是特别有用,因为每个贴图通常都是矩形的,但是可以用于倾斜,缩放等。它们在3D中对于照明计算和网格变形更加有用。 fragment shader更好玩,所以将被主要介绍。 我们可以获取有关材质的信息,也可以调整贴图中每个像素的最终颜色。

Shader中的变量限定符:attribute, varying和uniform

如果您在GameMaker Studio 2中创建了一个shader,你可能就会在shader的默认代码里找到这些词。 这些限定符可以帮助shader了解每个变量的目的和范围。

Attribute:OpenGL传递给vertex shader的变量。 它们可以更改每一个顶点,而且仅可读取。 这些信息包括顶点坐标,材质坐标,顶点颜色和顶点法线等信息。

Varying:用于在vertex shader和fragment shader之间传递数据的变量。 它们可以在vertex shader中编写,但是在fragment shader中仅可读取。

Uniform:随对象的更改而更改,并由用户传递给shader的变量。它们可以在vertex shader和fragment shader中都可以使用,但是仅可读取。

GLSL中的向量

使用shader时,向量非常重要。 这就是为什么在GLSL中将它们实现为基本类型。 如果你不懂,我告诉你。它们是一个数学术语,表示只有一列的矩阵。 在编程中,我们通常将它们表示为一个数组,分量的数量和维度相对应。 二维和三维的向量通常用于没有透明度路径的坐标,材质坐标或颜色,而四维向量则用于具有透明度路径的颜色。 我们还可以指定它们是否包含布尔值,整数或浮点值。 声明向量的语法是这样的:

要初始化它们,我们可以使用constructor创建向量。 您需要提供与向量长度相同数量的值,但是您可以混合并匹配标量和较小的向量以达到目标长度。 示例如下:

我们还可以为它们分配相同长度的另一个向量(或变换向量直到它的长度适当。在下一节中会进一步讲解):

重排向量

在GLSL中访问向量组件时,我们有一些选项。 最基本的方法是将向量视为数组,并用中括号访问分量,就像这样:

但是,还有一种访问分量的方法,使用如下语法:

这将使用向量中的分量名来访问它们。 您可以使用x,y,z或w分别获取第一个,第二个,第三个或第四个分量。 我们将这种方法称为“变换(swizzling)”,因为以下语法也会起作用:

如你所见,我们可以使用最多四个字母的任意组合来创建该长度的向量。 我们不能尝试访问超出范围的分量(例如,尝试访问secondVec或thirdVec中的w,因为它们没有第四个分量)。 此外,我们可以重复字母并以任何顺序使用,只要分配给它变量的大小与所用字母的数量相同即可。

变换同时也适用于l值:

很明显,在使用重组设置分量值的时候,你不能重复两次使用相同的分量。 例如,下面一段代码无效,因为它试图将同一分量同时设置为两个不同的值:

最后,我们一直在使用xyzw作为我们的变换遮罩,处理坐标通常都是这种情况。你可以使用另外两组遮罩:rgba(用于颜色)或stpq(用于材质坐标)。 它们之间没有区别,我们使用它们仅仅是为了使实例代码中向量所代表的内容更加清楚。而且,我们无法在同一操作中组合使用变换遮罩,因此下列代码是无效的:

这些是很多定义和信息,但是了解这些内容对于理解shader本身是必需的。 废话不多说,让我们开始我们的第一个shader。

解构默认的传递shader

当你在GameMaker Studio 2中创建shader的时候,它将为你打开两个文件:一个vertex shader(.vsh)和一个fragment shader(.fsh)。 这是你可以制作的最基本的shader,它可以使用一个贴图,读取它的材质,并用那个颜色为每个像素着色。 如果你在绘制的时候指定了顶点的颜色,这些颜色将和材质混合。

让我们从vertex shader开始研究代码并进行分析。

在main函数之外,我们看到了一些变量声明及其限定符。这些属性是由通用GM提供给我们的。varying的那个变量由用户创建,以将该信息传递到fragment shader里面。在main函数之内,我们可以进行计算以找到顶点在屏幕上的坐标。

首先,我们创建一个vec4并使用该坐标的分量对其进行初始化,然后添加一个作为第四个分量。在线性代数中,惯例是如果向量表示一个点,就添加第四个分量;如果它表示实际向量,则添加一个零。我们需要添加第四个分量,以将其乘以4x4的矩阵。MATRIX_WORLD_VIEW_PROJECTION。该乘法会将顶点的世界坐标投影到屏幕坐标中。然后,我们通过varying变量将顶点颜色和材质坐标传递给fragment shader。如果您不打算使用顶点坐标,就单独保留此shader。在本教程中,我将让它保持为默认代码,这是因为我们的特效是用fragment shader来创建的。

让我们来快速地看一下fragment shader:

如前文所讲,fragment shader背后的想法是返回当前像素的颜色。 将通过把变量gl_FragColor分配给最终颜色值来完成。 texture2D函数获取一个材质和一个带有你要在该材质中检查的UV坐标的vec2,这将返回带有颜色的vec4。在传递shader中,我们要做的就是在该像素的坐标中获取材质的颜色,然后将其乘以与它关联的顶点颜色。 

现在我们有了我们的第一个shader,测试它所要做的就是创建一个对象并为其指定一个贴图。在绘制事件中,你可以像这样设置shader:

我们在shader_set和shader_reset之间进行的每个绘制调用都将应用于shader。在这里,我们使用传递shader绘制对象贴图。

也许你已经猜到,我们没有做出任何改变。

现在让我们来尝试一些更有趣的事情。

颜色遮罩shader

创建一个新的shader,将vertex shader保持不变,并在fragment shader中,将gl_FragColor设置为红色。 代码如下:

和我们期盼的不大一样。我们需要记住的是,每个贴图最终都是一个正方形,所以,除非我们考虑透明度,这就是我们所得到的。 还记得那个texture2D函数吗? 我们将使用它来抓取我们正在处理的像素处的颜色。 返回值为vec4,其中分量依次为红,绿,蓝和alpha通道。我们可以通过在变量名称后加上点后跟上a或w来访问alpha通道。 这分别对应于RGBA和XYZW。

这是更新后的代码:

现在我们为gl_FragColor分配了一个新的vec4,其中红色通道已最大化,而绿色通道和蓝色通道为零,alpha通道和原始材质相同。 输出如下:

结果就是这样! 我们将每个像素的颜色替换为红色,并保持Alpha通道完整。

因为我们需要为每种颜色使用单独的shader,所以每次要使用其他颜色都不得不更改shader绝对不会是一个好主意。 相反,我们将使用统一的颜色信息传递给shader。 因此,首先我们需要获得指向uniform的指针。 我们通过添加下面一段代码在具有贴图的对象的创建事件中执行此操作:

我们要做的就是调用shader_get_uniform以获得指向uniform的指针。 我们需要传递的参数是shader名(我们要传递GameMaker为我们生成的ID,所以不加引号)和shader内部的uniform变量名,这次是字符串。 该名称需要与shader代码中的名称完全匹配才能奏效。 我还添加了一个颜色变量,以便我们可以在运行时更改它,并使它记住。

现在以传递uniform变量,我们的绘制事件中的代码将稍作更改。

它与以前的代码相同,但是在绘制任何东西之前,我们需要将所有uniform值传递给shader。在这种情况下,我们传递的颜色作为浮点数的数组。至于shader,我们将它改为包含并使用shader,所以它变成:

我们声明一个名称与创建着色器(u_color)中的名称相同的变量,并将其作为gl_FragColor向量的前三个分量进行传递,以充分利用变换的优势。 如果再次编译,我们将看到如下:

现在,shader变得更加有用和可重用。如果你需要在运行时设置颜色(使用_color变量),则可以添加更多功能。

让我们转到另一个shader,事实上它是我制作的第一个shader。

黑白shader

当我还在学习时,我想出了这个有趣的shader。 概念很简单:获取每个像素并分配灰色阴影。 当使用RGB,如果所有的三个分量的值都相同,我们将会得到灰色调。 我最初采用的简单方法是将所有三个颜色通道(红,绿,蓝)相加并除以三。 然后,我将它分配给所有的三个通道,从而产生灰色调。 fragment shader如下:

你可能已经注意到了,另一件事是,在gl_FragColor中,我将vec4乘以了v_vColour。这是vertex shader所传递的变量,它告诉我们与该像素关联的顶点的颜色。 最好将最终计算出的颜色与顶点颜色相乘。 在大多数情况下,它不会执行任何操作,但是如果您在GML中更改了顶点颜色,则会反映出这一点(通过使用draw_sprite_ext或draw_sprite_general之类的函数)。a

绘制事件很简单,毕竟我们没有uniform要传递:

让我们编译并看看我们得到了什么。

这已经看起来很好了,对吧? 后来,我发现了一个更“正确”的解决方案,因为我们没有将分量相加并除以三,而是将每个分量乘以黑白的标准NTSC值。 这是经过修改的fragment shader代码:

我们使用点积作为简写,将texColor的每个分量与正确的权重相乘,然后将它们相加。 如果您不了解点积,那么本质上就是这种情况:

到了最后,它看起来非常相似,但是严格来说更正确。

为了结束本文,我们将尝试再使用一个shader,该shader将使用到目前为止所学的所有内容。

彩虹shader

这是我最喜欢的shader之一,因此我在HackyZack中广泛将其用于菜单项。该shader是高度可变的,所以我将简单地开始并逐步添加功能。 由于涉及的内容很多,我可能还会快一些。因此,如果你没读懂,请随时重读上一部分。

我们要做的第一件事是根据像素的水平坐标为每个像素着色。实现的方法是将x坐标设置为色相,然后从HSV格式(色调,饱和度,亮度)转换为RGB格式(红,绿,蓝)。为此,我们需要在fragment shader当中编写一个辅助函数,该函数读取HSV值并返回RGB向量。 我将使用我在网上找到的函数来执行,无需任何if语句。记住要尽可能地避免在shader的代码中使用条件语句,因为分支会使shader变得非常慢。

这是shader在这一阶段的样子:

好的,这里提供了大量的信息,但其中的大多数我们已经完成。首先,我们有hsv2rgb函数,该函数使用带有HSV颜色的vec3,并通过RGB转换返回另一个vec3。 在main函数中,我们首先创建HSV颜色,色相是x坐标。然后将饱和度和亮度保留为1.0。接着,从材质中获取alpha,这样它就只会为我们的角色着色,而不是把整个贴图矩形都着色(就像之前那样)。 最后,我们将“fragment color”设置为HSV颜色,将其与alpha乘以顶点颜色转换为RGB(养成始终这么做的好习惯)。

至于我们的绘制代码,目前为止它非常少:

让我们看看我们所得到的:

我们已经接近我们所想要的,但是有一个问题:我们无法在动画的每个贴图中同时看到所有颜色,但是颜色似乎会发生改变。 原因是我们假设v_vTexcoord给了我们贴图的坐标,从左上角(0,0)开始到右下角(1,1),在shader中是标准的。原因是GameMaker在内部工作的原理。 为了进行优化,GameMaker可以将尽可能多的材质填充到材质页中。 默认纹理页为2048x2048,但是可以更改。

因此,这就是我们材质实际的样子:

如上文所述,v_vTexcoord为我们提供了整个材质页面中贴图的绝对坐标,但是我们想要的是0.0到1.0之间的值,该值仅覆盖当前的贴图。 此过程称为标准化(获取值并将其转换为0到1的范围)。 为了标准化我们的水平值,我们需要知道上图中的x0和x1值。 幸运的是,GameMaker有一个函数,可让我们获取材质页里的贴图中每个角的坐标。首先,我们需要转到创建事件并创建一个uniform变量,以将该数据传递给shader:

然后我们修改绘制事件以获取该值,然后将其传递给shader:

函数sprite_get_uvs接受一个贴图和一个指数,并返回包含大量信息的数组,例如每个角的坐标,裁剪多少像素以对其进行优化等。我们对其中两个值感兴趣:贴图的左侧坐标和右测坐标,他们分别存储在uv[0]和uv[2]中。 我们将把这两个值传递给shader并绘制本身。 在fragment shader当中,我们现在将使用这些值来计算标准化的水平坐标,如下:

记住要在文档顶部添加与我们在创建事件中使用的名称相同的uniform变量。 接下来,我们通过将当前的x坐标转换为原点(v_vTexcoord.x-u_uv [0]),然后将其除以贴图的宽度来计算范围为0到1的数(u_uv [1 ]-u_uv [0])。

结果便是:

这才像话! 正是我们想要的。我们可以看到贴图内光谱中的每种颜色。

让我们把这个shader变得更有趣一点吧。如果我们想要根据时间在颜色上添加偏移量以产生运动怎么办? 为此,我们将需要两个额外的变量:速度和时间。 我们还需要另外两个uniform,给每个新变量分配一个,所以将创建事件改为:

我们还需要每帧增加时间,因此在步事件中我们添加:

很好! 让我们转到绘制事件,发送以下uniforms:

现在让我们回到shader,以实际使用这些变量。 我们要做的是将速度乘以时间并将其添加到坐标,如下:

如果一切都没有问题,则应该会看到下图:

为了完成这个shader,我们将添加更多uniform变量以进一步完善它。前两个不是很重要,即饱和度和亮度。下一个我将它称为section,其功能是允许用户传递一个介于0和1之间的数字,以确定我们一眼看到的整个色谱的百分比。 最后,我将添加一个名叫mix的变量,以指定我们要将shader颜色与原始材质颜色混合的比例(1.0是全彩虹,0.0是全材质)。和往常一样,让我们从将变量添加到创建事件中开始:

我们的绘制事件改为包含以下uniforms:

至于shader,我们需要将饱和度和亮度传递给颜色,这将影响由我们的辅助函数生成的颜色。 该部分需要乘以我们的坐标以缩小范围。 我还抓取了整个材质的颜色,因此我可以通过将材质颜色与我们颜色的RGB转换混合来计算最终颜色。 混合函数的最后一个参数确定我们要添加多少第二种颜色。 这是最终的shader代码:

而我们的最终结果就是这样!

Demo

我做了一个demo,其中包含所有这些shader以及一些其他功能。 如果你想体验一下,或者需要检查为什么有些地方在你这出问题,请点击下面的链接。

https://drive.google.com/file/d/1x3v1_jrhIIvvepGfS1Sm6JvgKLpSh1Li/view?usp=drive_open

参考和有趣的地址

GameMaker Studio 2 Shader手册:https://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/shaders/index.html

OpenGL ES 1.0说明:https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf

Shadertoy:https://www.shadertoy.com/

结论

我希望这对你会有帮助!shader的主题非常广泛,仅仅一篇文章无法覆盖,但我希望对它的一瞥能激发您的好奇心,以了解更多有关shader的知识。

如果你对本文中涉及的素材或GMS2相关有任何问题,我永远愿意提供帮助。 您可以在twitter上联系我。(https://twitter.com/AleHitti)

Alejandro Hitti是委内瑞拉的电子游戏程序员和设计师。尽管他的背景是C++并使用定制的游戏引擎工作,但他的两个商业游戏INK和HackyZack是使用GameMaker Studio 1.4制作的。随着GameMaker Studio 2的发布,这便成为了他选择的引擎。GMS2的新颖,再加上他对先前版本的了解,便激发了他对制作针对该新引擎的教程的兴趣。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3