Esfog

快乐学习,快乐编程,快乐生活Forever!!

博客园 首页 新随笔 联系 订阅 管理

  有段日子没出这个系列的新文章了,今天就拿一个比较常见也比较基础的利用改变Shader来改变不断调整UV实现播放逐帧动画的小功能。很久没写了就当练练手了。在新版本的Unity中早就已经集成了Sprite2D的功能,而且可以编辑不规则的图形,不过了解一下它的原理,也是蛮好的!


逐帧动画


 

  帧动画大家应该都不陌生,经常会看到把一个动画几帧的的状态按一定顺序整合在同一张图片上,如下图:

      

 

  从上图中我们可以看出,这个图片动画一共有20帧,从左到右,从上到下依次排布(基本上都是这个规律).为了展示效果我们需要一个平面来作为模型,Unity里面自带的Panel就可以,不过最终的效果很可能是这个动画是倒着的,因为不清楚Unity自带Panel的顶点UV情况,只能是大家自己手动转一转调到合适的角度观察(如果你会建模的话自己做个平面导到Unity吧)。我们都知道一个贴图具体如何被映射到模型的表面是根据模型定点的UV值来决定的。假设不修改的话,一个平面的左下角的UV是(0,0),右上角是(1,1),那么你直接把这个贴图贴上去的话看到的效果就和上图一样,这显然不是我们想要的。在之前的教程中我们都是直接用模型自带的UV,UV是多少就按照它去到贴图上取对应颜色,这次我们就需要修改了,比如顶点的UV是(1,1)我们也不去贴图的(1,1)位置取。而是根据时间计算出当前播放到第几帧然后算出贴图上相应Sprite(也叫精灵,就是图片上一块一块具体的小图素)的UV位置.按照这个位置取贴图中取出对应颜色即可。千万不要以为模型顶点本身的UV信息变了,其实只是我们把他传递给我们的UV信息加工了一下获取我们想要的位置上的像素。

 

原理就是这样的,下面我们进行实例代码的分析:

 1 Shader "Esfog/SpriteUV" 
 2 {
 3     Properties 
 4     {
 5         _SpriteTex ("SpriteTexture (RGB)", 2D) = "white" {}
 6         _SpriteRowCount ("RowCounts",float) = 0
 7         _SpriteColumnCount ("ColumnCounts",float) = 0
 8         _Speed ("AnimationSpeed",Range(0.01,10)) = 4
 9     }
10     SubShader 
11     {
12         Pass
13         {
14             Tags { "RenderType"="Opaque" }
15             
16             CGPROGRAM
17             #pragma vertex vert
18             #pragma fragment frag
19             #include "UnityCG.cginc"
20             
21             uniform sampler2D _SpriteTex;
22             uniform float _SpriteRowCount;
23             uniform float _SpriteColumnCount;
24             uniform float _Speed;
25             
26             struct VertexOutput
27             {
28                 float4 pos:SV_POSITION;
29                 float2 uv:TEXCOORD0;
30             };
31 
32             VertexOutput vert(appdata_base input)
33             {
34                 VertexOutput o;
35                 o.pos = mul(UNITY_MATRIX_MVP,input.vertex);
36                 o.uv = input.texcoord.xy;
37                 return o;
38             }
39             
40             float4 frag(VertexOutput input):COLOR
41             {
42                 float totalSpriteCount = _SpriteRowCount * _SpriteColumnCount;
43                 float rowAvgPercent = 1 / _SpriteColumnCount;
44                 float columnAvgPercent = 1 / _SpriteRowCount;        
45                 float SpriteIndex = fmod(_Time.y * _Speed,totalSpriteCount);        
46                 SpriteIndex = floor(SpriteIndex);        
47                 float columnIndex = fmod(SpriteIndex,_SpriteColumnCount);
48                 float rowIndex = SpriteIndex / _SpriteColumnCount;
49                 rowIndex = _SpriteRowCount - 1 - floor(rowIndex);
50                 float2 spriteUV = input.uv;
51                 spriteUV.x = (spriteUV.x + columnIndex) * rowAvgPercent;
52                 spriteUV.y = (spriteUV.y + rowIndex) * columnAvgPercent;            
53                 float4 col = tex2D(_SpriteTex,spriteUV);
54                 return col;
55             }
56             
57             ENDCG
58         }
59     } 
60     FallBack "Diffuse"
61 }

  和之前一样,有一部分内容在之前的教程中已经提过了,不再赘述。

    第5~8行,在Shader的属性部分,我们定义了用来存放动画图片的_SpriteTex.用来存放这个动画图片上一共有几行图素的_SpriteRowCount.存放图片上一共有几列图素的_SpriteColumnCount.以及控制动画播放速度的_Speed.其中_Speed应用了一种新的属性类型Range(x,y).这个属性会在材质球的Inspector面板上创建一共Slider滑块,他会返回一共滑块所处位置的float值.最左端为x,最右端为y.用户可以直接拖动,十分方便。

  第42~44行,大家从代码可以看到VertexOutput和vert函数都很简单,因为我们的内容都在frag函数里呢,totalSpriteCount用来存放动画图片上精灵图素的总数量(一共有多少帧).rowAvgPercent用来存放在一行中一个精灵图素在横向上占得比例,因为一张图片的左下角是uv的(0,0),右上角是(1,1)。所以也可以认为是横向上每一个图素占得uv比例。从现在开始大家要注意代码中的row和column.有时候是代表横纵有的有时候却是代表纵横.不要被我搞晕了.我也觉得很别扭啊。这里的_SpriteColumnCount是我们之前定义的一共有几列,所以1/_SpriteColumnCount就是我们要的一行中每个图素占得比例了。columnAvgPercent同理。

  第45~46行,因为我们是逐帧动画,就是一帧一帧的播放,那么必须记录当前播放在第几帧了,SpriteIndex就是用来干这个的。后面有个看上去比较陌生的表达式,其中_Time是一个Unity为我么提供的float4变量,_Time.y分量代表的就是游戏开始以后流逝的时间(s).关于_Time的更多解释可以查看unity的帮助文档中关于Shader内置变量的内容。而后面乘以一个_Speed是我们用来调节播放速度的,如果_Speed = 1那么我们就是1秒1桢的速度播放动画,如果_Speed = 2就是1秒2帧的速度播放。而整个fmod(_Time.y * _Speed,totalSpriteCount)其中fmod(x,y)是x对y取余。因为我们的动画是循环播放的,而且序号是从0开始计算的,所以整个取余操作可以根据当前的时间和播放速度来确定播放到第几帧了,并且是循环播放。因为是两个浮点数取余,余数可能含有小数位。而我们具体播放到第几帧是不可能为小数的.所以我们用floor函数对结果进行下取整,为什么不用上取整呢,大家自己思考一下吧。

  第47~49行,这一步中,我们要去根据播放到第几帧去计算出相应图素在整个图素矩阵中的第几列第几行。这个我想大家上大学的时候一定见过很多了。其中SpriteIndex / _SpriteColumnCount用当前第几帧除以总列数就可以得到当前播放的图素处在第几行(从0开始算).而fmod(SpriteIndex,_SpriteColumnCount)用当前帧数对总列数取余得到当前图素处在第几列,为什么这次我们不需要对fmod结果进行取余呢,大家再思考一下吧。最后我们又对得到的rowIndex先进行了下取整,然后又用(总行数-1)去减去他得到结果,其中取整是因为第48行中的rowIndex是由两个浮点数相除而来,结果可能包含小数位。而用(_SpriteRowCount-1)去减它,是因为图片的UV原点是在左下角的那么我们算出来的第几行实际上是从下往上数的,这显然与我们期望的相反,而又由于行数是从0开始算的所以我们用(总行数-1)减去原来的rowIndex就能得到我们想要的图素在第几行了(从下往上数)。

  第50行~53行,创建一个spriteUV并赋予原始的uv信息,然后我们开始对整个原始uv进行加工了,先对uv信息的x方向值进行加工,(spriteUV.x + columnIndex) * rowAvgPercent可以拆除spriteUV.x*rowAvgPercent + columnIndex*rowAvgPercent.加号左边是相当于我们把原来的uv.x(0~1)缩放到(0~rowAvgPercent),也就是一行图素中第一个图素所占的横向上的uv比例。实际上现在这个位置就相当于每一行中的第一个图素,而加号右面的值实际上是我们相对于把刚才计算出来的uv当做一个基地址然后加上这个偏移,一个图素在横向上占rowAvgPercent这么多的uv比例,那么他处在第columnIndex列所以就是说他相对于加号左面的基础位置偏移了(columnIndex*rowAvgPercent)这么多的uv比例。可能比较绕,大家好好想一想就明白了。后面spriteUV.y同理。最后用我们加工后的spriteUV作为新的UV值去贴图中取出相应的颜色返回就可以了。

  

  (~ o ~)~系列教程的第六篇到此结束了,这篇的内容比较基础,就是解释起来比较绕,我也有段时间没写了,有些生疏了。还望大家见谅。日后会再接再厉,多多分享。现在都凌晨一点了,明天还要上班,希望大家有所收获吧。由于这个效果是动态的为了展示效果本人能力不足,只能用截图软件一帧一帧截屏然后合成成gif展示一下了.由于截图大小参差不齐,所以播放起来晃来晃去的。大家将就着看吧。洗洗睡了~~

  

 

  尊重他人智慧成果,欢迎转载,请注明作者esfog,原文地址 http://www.cnblogs.com/Esfog/p/4088597.html 

posted on 2014-11-11 01:36  Esfog  阅读(2889)  评论(0编辑  收藏  举报