【译】如何使用Shader动态交换精灵的颜色

发表于2016-01-07
评论1 1.8k浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏程序行业精英群

711501594

     在本教程中,我们将创建一个简单的交换Shader的颜色来重新着色在飞的精灵。Shader使添加多样化到游戏中变得更容易,允许玩家来定制他们的角色,还能被用来添加特效在精灵上,比如当角色受到伤害时使他们闪烁。

尽管我们在这使用Unity来演示和写源代码,但基本原则将运用在许多游戏引擎和编程语言中。

 

演示

你可以查看Unity演示,或者WebGL版本(25MB+),在运行中看到最后的结果。用颜色选择器来重新着色顶级角色。(尽管其他角色都是用相同的精灵,但也要同样地被重新着色。)单击Hit Effect键来使角色都短暂地闪白。

理解理论

我们将要用下面这个例子的结构来演示Shader

在这个结构中有相当多的颜色。看起来就像调色板:

现在,让我们想想我们能够如何在Shader里交换这些颜色。

每个颜色都有一个独一无二的RGB值与它相关联,所以编写Shader代码是很容易的,“如果结构颜色与这个RGB值相等,就用RGB值替代它”然而,这并不能衡量许多颜色,那是一个十分昂贵的操作。事实上,我们当然想要完全地避免任何条件语句。

相反,我们将用一个额外的结构,它将包含被代替的颜色,我们称之为交换结构。

最大的问题是,我们如何把精灵结构中的颜色连接到交换结构中?答案是,我们将用RGB颜色中的R组件来索引交换结构,这意味着交换结构将需要256像素宽,因为R组件可以设置许多不同的值。

让我们来看一下这个例子中的所有颜色。这是精灵面板颜色中红色颜色的值:

假设我们想要用蓝色来取代这个轮廓精灵眼睛的颜色(黑色)。这个轮廓的颜色是面板上的最后一个---红色值为25的那个。如果我们想要交换这个颜色,再交换结构中我们需要设置像素为指数25来得到我们想要的轮廓颜色:蓝色。

现在,当Shader遇到一个红色值为25的颜色,他将用交换结构中的蓝色来替代它:

注意,如果精灵结构中两个或者两个以上的颜色分享相同的红色值这或许不会像预期的那样工作!当使用这个方法时,保持精灵结构颜色的红色值不同是很重要的。

还要注意的是,在演示中你能够看到,将一个透明的像素放到交换结构的指数中将会导致没有颜色来交换那个对应指数的颜色。

 

实现Shader

我们将通过修改现有的精灵Shader来实现这个想法。自从演示项目在Unity做好以后,我们将使用Unity默认的精灵Shader

所有的默认Shader(与本教程相关的)从主要的结构图集抽取颜色和通过一个顶点颜色改变色调来增加那个颜色。产生的颜色乘以α,在一个较低的不透明度下使精灵更黑暗。

我们需要做的第一件事就是添加额外的结构到Shader中:

正如你所看到的,我们现在已经得到两个结构。第一个,_MianTex是精灵结构;第二个,_SwapTex是交换结构。

我们还需要为第二个结构定义一个取样器,所以我们可以使用它。我们将使用2D结构取样器,因为Unity不支持1D取样器:

现在我们终于可以编辑片段Shader:

这有些与默认的片段Shader相关的代码。正如你看到的,c是取样于主要结构的颜色;这是乘以顶点颜色来给它一个色调。还有,Shader会用较低的不透明度使精灵变暗。

采样主要的颜色后,我们同样也要采样交换颜色但在我们这么做之前,我们要移除用色调颜色乘以它的部分,所以我们将取样使用结构真正的红色值,而不是它色调的红色值。

正如你所看到的,采样颜色的指数是与主要颜色的红色值相等的。

现在我们来计算我们最终的颜色:

要做到这一点,我们需要用被交换颜色的α插入到主要颜色和被交换颜色之间来作为一步。这样,如果被交换的颜色是透明的,最后的颜色将会和主要颜色相同;但如果被交换的颜色是完全不透明的,那么最后的颜色将会和被交换的颜色相同。

我们不要忘记最后的颜色需要乘以色调:

现在我们需要考虑如果我们想要在不是完全不透明的主要结构中交换颜色那会怎么样。例如,如果我们有一个蓝色的,半透明的鬼精灵,并且想要用紫色来交换它的颜色,我们不希望被交换颜色的鬼精灵成为不透明的,我们想要保留原来的透明度。让我们来做:

最终的颜色透明度应该和主要的结构颜色透明度相同。

最后,因为原始的Shader是用颜色的α值乘以RGB值,我们应该也那么做,为了保持着色的一致:

Shader现在是完整的;我们能创建一个交换颜色的结构,用不同的颜色像素填满它,看看精灵是否会完全的改变颜色。

当然,如果我们必须要一直手动创建交换结构那这个方法不会非常有用的!我们将希望以程序的方式生产和修改它们…

 

设置一个例子演示

我们知道我们需要一个交换结构能够利用我们的Shader。此外,如果我们想要用不同的面板在相同的时间对于相同的精灵增加特征,每一个特征都将需要它们自己的交换结构。

这将是最好的,如果我们简单的创建这些动态的交换结构,正如我们创建对象。

首先,我们定义一个交换结构和数组来追踪所有被交换的颜色:

接下来,我们创建一个函数来初始化结构。我们将使用RGB32格式并设置过滤模式到Point

1

2

3

4

5

public void InitColorSwapTex()

{

Texture2D colorSwapTex = new Texture2D(256, 1, TextureFormat.RGBA32, false, false);

colorSwapTex.filterMode = FilterMode.Point;

}

现在让我们通过清除所有的像素和应用改变来确保所有结构的像素是透明的:

我们还需要设置材料的交换结构到新创建的交换结构中:

最后,我们保存参考到结构中并为颜色创建数组:

完整的函数如下:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

public void InitColorSwapTex()

{

    Texture2D colorSwapTex = new Texture2D(256, 1, TextureFormat.RGBA32, false, false);

    colorSwapTex.filterMode = FilterMode.Point;

 

    for (int i = 0; i < colorSwapTex.width; ++i)

        colorSwapTex.SetPixel(i, 0, new Color(0.0f, 0.0f, 0.0f, 0.0f));

 

    colorSwapTex.Apply();

 

    mSpriteRenderer.material.SetTexture("_SwapTex", colorSwapTex);

 

    mSpriteColors = new Color[colorSwapTex.width];

    mColorSwapTex = colorSwapTex;

}

注意,没有必要为每个对象使用一个单独的256x1px结构,我们能够做一个更大的覆盖所有对象的结构。如果我们需要32个角色,我们应该使用尺寸为256x32px的结构,并确保每个角色在结构中仅仅只使用一个特定行。然而,每次我们需要改变成为更大的结构,我们必须要传更多的数据到GPU中,这可能会降低效率。

也没有必要为每个精灵使用一个单独的交换结构。例如,如果角色有一个武器装备,武器是一个单独的精灵,那么与角色共享这个交换结构是很容易的(只要武器的精灵结构没有使用与角色精灵相同红色值的颜色)。

知道特定精灵部分的红色值是什么是非常有用的,所以让我们来创建一个enum来保存这个数据:

这些都是被用在角色例子中所有的颜色。

现在我们需要做的所有事情就是创建一个函数来交换颜色:

正如你看到的,这没什么特别的;我们只是要在我们对象的颜色数组中设置颜色,并且以一个适当的指数来设置结构的像素。

注意事实上我们每次调用这个函数时我们都不是真正想要改变这个结构;一旦我们交换所有我们想要的像素我们将会应用它们。

让我们来看看这个函数的使用例子:

正如你看到的,很容易理解这些函数调用所做的仅仅只是浏览他们:在这个情况下,他们正在改变彼此的皮肤颜色,衬衫颜色,和裤子的颜色。

 

     添加一个冲击效果到演示中

我们接下来看看我们如何使用Shader来为我们的精灵创建一个冲击效果。这个效果将交换所有的精灵颜色为白色,这样保持一个短暂的时期,然后回到原来的颜色。整体的效果将是精灵闪白。

首先,让我们创建一个函数来交换所有的颜色,但事实上并不覆盖来自对象的数组的颜色。毕竟当我们想要关掉这个冲击效果时我们将需要这些颜色。

我们仅仅遍历枚举,但是遍历整个结构将确保颜色被交换即使这个特定的颜色不在SwapIndex中定义。

现在颜色被交换,我们需要等待一些时间,并返回到先前的颜色。

首先,让我们来创建一个重置颜色的函数:

现在让我们来定义一个定时器和常数:

让我们来创建一个开启冲击效果的函数:

更新功能,让我们来看下还剩下多少时间,减少每个滴答声,当时间到了时调用重置:

就是现在,当StartHitEffect被调用时,精灵会闪白一段时间然后返回到先前的颜色。

 

     总结

这标志着本教程的结束!我希望你能找到可以接受的方法和有用的Shader。这是真正简单的一个,但是不使用许多颜色的像素艺术精灵工作得很好。

如果我们想立即交换这个组的颜色,这个方法需要改变一点,这当然需要一个更复杂和昂贵的Shader。在我自己的游戏中,因为我使用非常少的颜色,所以这项技术非常适合。

 

翻译自:< How to Use a Shader to Dynamically Swap a Sprite's Colors /http://gamedevelopment.tutsplus.com/tutorials/how-to-use-a-shader-to-dynamically-swap-a-sprites-colors--cms-25129>

文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

游戏学院公众号二维码
腾讯游戏学院
微信公众号

提供更专业的游戏知识学习平台