本系列主要参考《Unity Shaders and Effects
Cookbook》
一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================

题外话

好久未更新,一些习惯还是应该要好好坚持啊。最近发现Android上一个很好的应用,希望每天可以坚持看一个视频,了解下最新的科技动态和演讲,也推荐给大家使用!

这次内容主要讲如何使用sprite sheets制作2D动画。在Unity中,实际上已经有很多插件可以完成这个工作,例如2dTookit,新版的Unity中支持2D应该也会提供类似的功能了。虽然如此,我还是希望通过这篇文章可以更深入地理解2D动画的原理。说到动画的原理,先要提到一个名词,sprite atlas,也可以称为image sequence,实际上就是一张大图中包含了很多张小图,如下:

当我们按照一定速率滚动这张图时,就会发现图片动起来了,这我们应该都有经验,以前看小人书快速翻动时就发现小人在动。这里实际上也是这个原理。

准备工作


  1. 准备一张sprite sheet,你可以自己画一张也可以从网上下载一张,或是直接使用本书资源。这张图不需要太复杂,只需要包含一组图片序列来供滚动浏览就行。下面是本书中的实例图片(5084_Code/Unity assets/5084_02_UnityAssets/Textures/Chapter02_SpriteSheet005.png):

  2. 创建一个新的Shader和一个新的Material,名称分别为AnimateSprites;
  3. 新建一个场景,名为AnimateSprites_Scene,在场景中添加平行光,创建一个新的平面,并把上一步中的Material设为其材质,将第一步中的图片拖到Material的图片上。最后如下所示:

实现


  1. 打开Shader编辑器,向Properties 区域添加三个新的Properties:

    1. Properties {
    2. _MainTex ("Base (RGB)", 2D) = "white" {}
    3.  
    4. // Create the properties below
    5. _TexWidth ("Sheet Width", float) = 0.0
    6. _CellAmount ("Cell Amount", float) = 0.0
    7. _Speed ("Speed", Range(0.01, 32)) = 12
    8. }
  2. 在SubShader区域为每个新增的Properties增加对应的引用,以便在代码中使用它们的值:
    1. CGPROGRAM
    2. #pragma surface surf Lambert
    3.  
    4. sampler2D _MainTex;
    5.  
    6. //Create the connection to the properties inside of the
    7. //CG program
    8. float _TexWidth;
    9. float _CellAmount;
    10. float _Speed;

    下面更改surf函数。

  3. 将_MainTex的UV坐标先存储到单独的变量中:
    1. //Lets store our UVs in a seperate variable
    2. float2 spriteUV = IN.uv_MainTex;

    之后,我们将使用该变量计算新的UV坐标。

  4. 下面,我们需要计算每个小图的宽度,最后计算得到每个小图宽度占整体的百分比。

    原书中代码如下:

    1. //Lets calculate the width of a singe cell in our
    2. //sprite sheet and get a uv percentage that each cel takes up.
    3. float cellPixelWidth = _TexWidth/_CellAmount;
    4. float cellUVPercentage = cellPixelWidth/_TexWidth;

    但通过观察代码可以发现,缩短为一行即可:

    1. //Lets calculate the width of a singe cell in our
    2. //sprite sheet and get a uv percentage that each cel takes up.
    3. float cellUVPercentage = 1.0/_CellAmount;

    对于示例图片,其宽度为512,包含了9张小图,Inspector中配置如下:

  5. 下面,通过得到系统时间来计算需要在原图上的偏移量,来得到不同的小图:
    1. //Lets get a stair step value out of time so we can increment
    2. //the uv offset
    3. float timeVal = fmod(_Time.y * _Speed, _CellAmount);
    4. timeVal = ceil(timeVal);
  6. 最后,计算在X方向上的最终偏移量。

    原书中代码如下:

    1. //Animate the uv's forward by the width precentage of
    2. //each cell
    3. float xValue = spriteUV.x;
    4. xValue += cellUVPercentage * timeVal * _CellAmount;
    5. xValue *= cellUVPercentage;

    同样,观察cellUVPercentage的计算式,可以简化上述代码如下:

    1. //Animate the uv's forward by the width precentage of
    2. //each cell
    3. float xValue = spriteUV.x;
    4. xValue += timeVal;
    5. xValue *= cellUVPercentage;
  7. 应用最终偏移量,显示到plane上:
    1. spriteUV = float2(xValue, spriteUV.y);
    2.  
    3. half4 c = tex2D (_MainTex, spriteUV);
    4. o.Albedo = c.rgb;
    5. o.Alpha = c.a;
最终Shader代码如下:
  1. Shader "Custom/AnimateSprites" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4.  
  5. // Create the properties below
  6. _TexWidth ("Sheet Width", float) = 0.0
  7. _CellAmount ("Cell Amount", float) = 0.0
  8. _Speed ("Speed", Range(0.01, 32)) = 12
  9. }
  10. SubShader {
  11. Tags { "RenderType"="Opaque" }
  12. LOD 200
  13.  
  14. CGPROGRAM
  15. #pragma surface surf Lambert
  16.  
  17. sampler2D _MainTex;
  18.  
  19. //Create the connection to the properties inside of the
  20. //CG program
  21. float _TexWidth;
  22. float _CellAmount;
  23. float _Speed;
  24.  
  25. struct Input {
  26. float2 uv_MainTex;
  27. };
  28.  
  29. void surf (Input IN, inout SurfaceOutput o) {
  30. //Lets store our UVs in a seperate variable
  31. float2 spriteUV = IN.uv_MainTex;
  32.  
  33. //Lets calculate the width of a singe cell in our
  34. //sprite sheet and get a uv percentage that each cel takes up.
  35. float cellUVPercentage = 1.0/_CellAmount;
  36.  
  37. //Lets get a stair step value out of time so we can increment
  38. //the uv offset
  39. float timeVal = fmod(_Time.y * _Speed, _CellAmount);
  40. timeVal = ceil(timeVal);
  41.  
  42. //Animate the uv's forward by the width precentage of
  43. //each cell
  44. float xValue = spriteUV.x;
  45. xValue += timeVal;
  46. xValue *= cellUVPercentage;
  47.  
  48. spriteUV = float2(xValue, spriteUV.y);
  49.  
  50. half4 c = tex2D (_MainTex, spriteUV);
  51. o.Albedo = c.rgb;
  52. o.Alpha = c.a;
  53. }
  54. ENDCG
  55. }
  56. FallBack "Diffuse"
  57. }

保存后,点击运行即可看到动画效果。

解释


为了每刻只显示一张小图,我们需要将sprite atlas进行放缩,这就需要计算放缩比例cellUVPercentage。
  1. float cellUVPercentage = 1.0/_CellAmount;
事例中_CellAmount为9,则cellUVPercentage为0.11。

下面根据时间计算偏移量,这些偏移量随时间增大而且为整数,直到增大为小图的数目大小。
  1. float timeVal = fmod(_Time.y * _Speed, _CellAmount);
  2. timeVal = ceil(timeVal);
这需要使用CGFX的内置函数fmod。

如上所示,fmod函数将返回x除以y的余数。示例中,fmod函数将循环返回范围为0至9之间的小数。为了得到整数,再使用ceil函数向上取整。


下面这部分代码最难理解:
  1. float xValue = spriteUV.x;
  2. xValue += timeVal;
  3. xValue *= cellUVPercentage;

第一行首先声明一个新的变量xValue,用于存储用于图片采样的x坐标。它首先被初始为surf函数的输入参数In的横坐标。类型为Input的输入参数In代表输入的texture的UV坐标,范围为0到1。第二行向原值加上小图的整数偏移量,最后为了只显示一张小图,我们还需将x值乘以小图所占百分比cellUVPercentage。

为了理解,我们举例来说。假设此时timeVal值为0,即显示第一张小图,则此时xValue的范围将会是 (0 + 0) * 0.11 = 0至(1 + 0) * 0.11 = 0.11,即第一张小图对应的贴图范围。时间推进,当timeVal值增大为1,即需要显示第二张小图时,xValue值范围将是 (0
+ 1) * 0.11 = 0.11至(1 + 1) * 0.11 = 0.22,即第二张小图对应的贴图范围。这样就按照顺序显示小图,从而让整个画面动起来了。

扩展


在上例中,我们只需要偏移x值即可。但有时,x和y方向都需要进行偏移来移动小图。如下图:

这时你只需要像移动x方向那样移动y即可。虽然这样可以达到动画要求,但是这张在Shader端开发的方法会添加过多的Shader指令,从而造成程序性能下降。
为了改善这一情况,我们可以将帧偏移选择的代码转移到C#脚本中,从而让CPU来分担GPU的部分负担。这种同时使用CPU和GPU,让其配合相互工作的方法在优化性能方面非常有用。按照这种想法我们改写上述Shader代码,并创建一个新的C#代码,SpriteAnimator.cs,并将其拖拽到plane上面。
更改后的Shader代码如下:
  1. Shader "Custom/AnimateSprites" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4.  
  5. // Create the properties below
  6. _CellAmount ("Cell Amount", float) = 0.0
  7. _TimeValue ("Time Value", float) = 0.0
  8. }
  9. SubShader {
  10. Tags { "RenderType"="Opaque" }
  11. LOD 200
  12.  
  13. CGPROGRAM
  14. #pragma surface surf Lambert
  15.  
  16. sampler2D _MainTex;
  17.  
  18. //Create the connection to the properties inside of the
  19. //CG program
  20. float _CellAmount;
  21. float _TimeValue;
  22.  
  23. struct Input {
  24. float2 uv_MainTex;
  25. };
  26.  
  27. void surf (Input IN, inout SurfaceOutput o) {
  28. //Lets store our UVs in a seperate variable
  29. float2 spriteUV = IN.uv_MainTex;
  30.  
  31. //Lets calculate the width of a singe cell in our
  32. //sprite sheet and get a uv percentage that each cel takes up.
  33. float cellUVPercentage = 1.0/_CellAmount;
  34.  
  35. //Animate the uv's forward by the width precentage of
  36. //each cell
  37. float xValue = spriteUV.x;
  38. xValue += _TimeValue;
  39. xValue *= cellUVPercentage;
  40.  
  41. spriteUV = float2(xValue, spriteUV.y);
  42.  
  43. half4 c = tex2D (_MainTex, spriteUV);
  44. o.Albedo = c.rgb;
  45. o.Alpha = c.a;
  46. }
  47. ENDCG
  48. }
  49. FallBack "Diffuse"
  50. }

C#代码如下:

  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. public class SpriteAnimator : MonoBehaviour
  5. {
  6.  
  7. public float speed = 5.0f;
  8. public int cellAmount = 0;
  9.  
  10. float timeValue = 0.0f;
  11.  
  12. void Start ()
  13. {
  14. transform.renderer.material.SetFloat("_CellAmount", cellAmount);
  15. }
  16.  
  17. // Update is called once per frame
  18. void FixedUpdate ()
  19. {
  20. timeValue = Mathf.Ceil(Time.time * speed % 9);
  21. transform.renderer.material.SetFloat("_TimeValue", timeValue);
  22. }
  23. }

在Inspector界面上,我们只需要调整C#代码的两个变量即可,而不需要更改Material的变量。这样,我们将计算整数偏移量的工作转移到了C#代码中。



当然,如果你不想自己实现一个功能完全的2D动画系统,你可以到Asset Store上下载一些插件,这些插件可以帮你完成大部分工作。这些插件有:
如果你需要寻找一些应用来制作sprites,可以使用下列应用:
这刀刀都在肉上,当然我相信大家总会有办法的。

【Unity Shaders】Using Textures for Effects——让sprite sheets动起来的更多相关文章

  1. 【Unity Shaders】Using Textures for Effects介绍

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  2. 【Unity Shaders】Using Textures for Effects —— 实现Photoshop的色阶效果

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  3. 【Unity Shaders】Using Textures for Effects——打包和混合textures

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  4. 【Unity Shaders】Using Textures for Effects——通过修改UV坐标来滚动textures

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  5. 【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...

  6. 【Unity Shaders】《Unity Shaders and Effects Cookbook》总结篇

    我的唠叨 不知不觉,从发表第一篇关于<Unity Shaders and Effects Cookbook>已经快十个月了.一开始的初衷就是学习笔记,毕竟将来回过头去看的时候,再看英文难免 ...

  7. 【Unity Shaders】使用Unity Render Textures实现画面特效——建立画面特效脚本系统

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  8. 【Unity Shaders】Lighting Models —— 衣服着色器

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  9. 【Unity Shaders】Reflecting Your World —— Unity3D中的遮罩反射(Masking Reflections)

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

随机推荐

  1. linux的简单命令 网络配置

    1.1.1 ls命令 l ls(list)功能:列出目录内容 l 格式:ls [参数] [文件或目录] -a或--all   下所有文件和目录.注意隐藏文件.特殊目录.. 和 .. -l   使用详细 ...

  2. HTML Parsing Error: Unable to modify the parent container element before the child element is closed (KB927917)

    IE8报错误: 用户代理: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .N ...

  3. vue开发中v-for在Eslint的规则检查下出现:Elements in iteration expect to have 'v-bind:key' directives

    在使用VScode编辑器vue开发过程中,v-for在Eslint的规则检查下出现报错:Elements in iteration expect to have 'v-bind:key' direct ...

  4. Openstack:Instance cannot ping by domain name

    Issue: When you created an instance inside Openstack, you may find that you cannot ping address by d ...

  5. MPAndroidChart的K线图上添加均线

    MPAndroidChart的K线图上添加均线 效果图 均线计算方法: 通常说的5日均线,10日均线,其实就是根据当前K线节点的时间维度来说的,当前每个节点代表一天,那么上面的均线就叫做日均线(几日均 ...

  6. Spark技术内幕:Shuffle Map Task运算结果的处理

    Shuffle Map Task运算结果的处理 这个结果的处理,分为两部分,一个是在Executor端是如何直接处理Task的结果的:还有就是Driver端,如果在接到Task运行结束的消息时,如何对 ...

  7. WINDOWS系统注册表取得管理权限研究

    有的时候开发我们需要取得系统管理员权限,可以通过修改注册表实现,研究网上的各种方法,整理得一下脚本实现取得管理员权限 脚本如下 Windows Registry Editor Version 5.00 ...

  8. VS2008界面语言设置

    在卸载某个软件的时候,不知道什么原因导致vs2008的界面变成中文的了,但是菜单还是英文的,很不美观. 几经查找,最后可以在如下的地方设置界面语言 Tools -> Options 如果设置成 ...

  9. 你知道RxJava也可以实现AsyncTask吗?

    使用RxJava实现异步操作(AsyncTask) 常见的异步操作我们可以联想到AsyncTask或者handler,其实google创造出的目的也就是为了让代码更加清晰明了,让代码更加简洁. 而Rx ...

  10. iOS下JS与原生OC互相调用(总结)

    这是去年总结的一篇文章,也一并先放到这个目录下好了. iOS开发免不了要与UIWebView打交道,然后就要涉及到JS与原生OC交互,今天总结一下JS与原生OC交互的两种方式. JS调用原生OC篇 方 ...