本节书摘来自华章出版社《unity着色器和屏幕特效开发秘笈》一 书中的第2章,第2.6节,作者:(美)kenny lammers,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
有时你为了创建更多不一样的效果,需要动态创建纹理或者在运行时修改它们的像素值,这种情况通常被称为程序性的纹理效果。不同于你在图像编辑软件中手动制作一些新纹理,你需要在一个二维空间中创建一组像素然后将其应用到一个新的纹理上。再将创建的新纹理传入到着色器中,使它们可以在着色器中进行计算。
这种技术在已有的纹理贴图上进行绘图是非常有用的,我们使用动态创建纹理贴图的方式可以制造一种玩家和游戏环境之间的互动效果。也可以用它来制作一些贴花的效果,或者创建一些可以在着色器函数中使用的程序化形状等。在很多情况下比如你可能想创建一个新纹理并且将它匹配到某些程序模式,另外你也可以将它应用到你的着色器中。
创建动态纹理的过程其实就是对你的纹理进行处理的过程,它肯定需要依赖于一个专门的脚本。但是你需要了解它是如何实现的,换句话说,你应该知道在你的着色器管线中需要哪些技术。让我们来看看如何通过创建一个脚本实现将动态创建的纹理赋给表面着色器。
你需要通过执行以下步骤来为本节做些准备:
1.在你的unity项目中创建一个新的c#脚本,并将其命名为proceduraltexture。
2.在场景中创建一个空的游戏对象,将其position(位置)置零,并将procedural-texture.cs脚本附加到该物体上。
3.接下来,创建一个新的着色器,一个新的材质,以及一个可以附加着色器和材质的对象。确定你的着色器和材料的命名,使你通过名字可以很容易地找到它们。
4.设置好了这一切,我们就可以创建一段代码实现一个抛物线型的像素值,将其应用至一个纹理并将该纹理赋给着色器。通过本节的学习,你将创建一个如下图所示的纹理贴图:

1.创建一个变量来控制纹理的高度和宽度,以及一个texture2d类型的变量来存储我们生成的纹理。我们还需要几个私有变量来存储脚本运行时涉及的一些变量值。
2.在脚本的start()函数中,我们首先需要检查对象,查看该对象的材质属性是否已经赋值。如果材质不为空,我们将调用自定义函数generateparabola(),并为我们返回一个texture2d类型的变量值:
3.然后,我们需要声明自定义函数generateparabola(),它将实现我们预期的效果:
4.最后,我们还要填充自定义函数,实现一个抛物线形纹理的算法。现在先不用关心这些内容的具体意义,我们将在本章的下一节详细地讲解每一行代码。
在脚本开始处,我们先简单地检查场景中的特定对象,看看是否有材质赋给该对象,这样我们才能给它指定一个纹理。如果该对象的材质不为空,我们会将自定义的currentmaterial变量值赋给transform.renderer.sharedmaterial的返回值,该返回值是一个材质类型变量。
然后,进入下一个if()语句,检查当前材质值是否为有效值。如果材质值有效,我们就调用generateparabola()函数,该函数将为我们返回一个texture2d类型的值。
当程序运行到generateparabola()函数时,它会先通过texture2d()构造函数创建一个新的纹理,并且传入我们的widthheight变量。完成这一步后我们将创建一个空的纹理,这样我们就可以递归widthheight的平方次为每个像素填充相应的颜色值了。
创建了新的纹理以后,我们计算中心像素的位置,并使用centerpixelposition变量来存储该值。
接下来,对于循环内当前像素值的位置vector2(x,y),我们使用vector2.distance()函数计算它与中心像素的距离,该函数将为我们返回一个浮点值。举例来说,如果在循环中的当前像素位置是vector2(32,32)而且我们创建的是一个512×512的纹理,那么我们得到的距离值等于316.78。也就是说,它与中心点的像素距离为(32,32)。
然后,我们需要将像素距离值重新映射至0到1的范围内,这样它才能作为一个颜色值被我们使用(unity使用的颜色值范围是从0到1的)。为了实现这种映射我们需要做的就是将距离值除以纹理的宽度或高度值的一半。因此,在这种情况下,我们需要把我们的距离值除以256,也就是512的一半。所以,如果我们有一个大小为316.78的距离值,正如我们在前面的例子中看到的一样,我们会得到一个大小为1.23的颜色值。
现在,我们还需要确保我们得到的任何值都不能高于1.0或低于0.0,因此我们使用mathf.clamp()函数,它可以让我们将值限定在你传递的参数范围内。我们给该函数传入0和1两个值,以确保我们得到的是一个归一化的值。
最后,我们用1减去当前值对颜色值进行取反,然后将最后的值传递给一个新的颜色变量的各个通道。如下图所示:
现在你已经看到了如何使用少量的矢量数学生成像素值,你也可以试着生成其他类型的数据并将它们存储到一个纹理当中。下面的代码演示了通过使用世界向量和图像中心到当前像素的向量方向两者的点积来生成其他类型数据。
1.下面就是使用数学公式创建一个环绕纹理中心的环形:
2.下面是使用数学公式计算像素方向上的点积,相对世界向量的vector3.up(vector3(0, 1, 0) y轴)和vector3.right(vector3(1, 0, 0) x轴):
3.下面是使用数学公式计算像素方向相对世界方向的角度:
生成像素使用不同的向量和角度计算会产生不同的计算结果,我们可以通过下图看出: