11.1 Unity Shader 中的内置变量

11.2 纹理动画
纹理动画在游戏中应用非常广泛。尤其在各种资源紧张的移动平台上,往往会使用纹理动画来代替复杂的粒子系统来模拟各种动效
11.2.1 序列帧动画
- 优点:灵活性强,效果细腻
- 缺点:美术工作量大
实现步骤:
- 建材质,建 Shader,将 Shader 赋给材质
- 场景中创建四边形(Quad),将它正对摄像机,将材质赋给它
- Shader 代码:(源码)
-
设置 Pass 相关状态,以渲染透明效果
jsSubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" } Pass { Tags {"LightMode"="ForwardBase" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
- 序列帧图像包含了透明通道,因此可被当成一个半透明对象
- Queue 和 RenderType 都设置成 Transparent
- IgnoreProjector 设置为 True
- 在 Pass 中使用 Blend 命令来开启并设置混合模式
- ZWrite Off:关闭深度写入
- 序列帧图像包含了透明通道,因此可被当成一个半透明对象
-
片元着色器
jsfixed4 frag (v2f i) : SV_Target { // 根据时间和速度计算出帧图像的行列索引 float time = floor(_Time.y * _Speed); float row = floor(time / _HorizontalAmount); float column = time - row * _HorizontalAmount; // -------- 使用行列索引值来构建真正的采样坐标 -------- // 得出帧图像"坐标"------(x: 列索引,y: 行索引) // 对垂直方向的坐标偏移使用减法,因为 Unity 纹理坐标正方向与序列帧纹理的正方向相反 half2 uv = i.uv + half2(column, -row); // 除以行/列帧图像数,得到真正的采样坐标 uv.x /= _HorizontalAmount; uv.y /= _VerticalAmount; fixed4 col = tex2D(_MainTex, uv); col.rgb *= _Color; return col; }
-
设置 Fallback
-
- 将序列帧纹理赋给材质中的 Image Sequence,并将 Horizontal Amount 和 Vertical Amount 设置为正确的值
11.2.2 滚动的背景
步骤:
-
去掉天空盒,将摄像机的投影模式改为"正交"
-
建材质,建 Shader,将 Shader 赋给材质
-
场景中创建四边形(Quad),调整位置与大小,让它充满摄像机的视野范围。将第 2 步的材质赋给它
-
Shader 代码:(源码)
-
属性声明
jsProperties { // 底层(较远)纹理 _MainTex ("Base Layer", 2D) = "white" {} // 表层(较近)纹理 _DetailTex ("2nd Layer", 2D) = "white" {} // 底层纹理滚动速度 _ScrollX ("Base layer Scroll Speed", Float) = 1.0 // 表层纹理滚动速度 _Scroll2X ("2nd layer Scroll Speed", Float) = 1.0 // 控制纹理整体亮度 _Multiplier ("Layer Multiplier", Float) = 1.0 }
-
顶点着色器
jsv2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); // 利用内置的 _Time.y 变量在水平方向上对纹理坐标进行偏移 // 把两纹理坐标存储在同一变量 o.uv 中,以减少占用的插值寄存器空间 o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y); o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y); return o; }
-
片元着色器
jsfixed4 frag (v2f i) : SV_Target { // 分别用 i.uv.xy 和 i.uv.zw 对两张纹理进行采样 fixed4 firstLayer = tex2D(_MainTex, i.uv.xy); fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw); // 使用第二层纹理的透明通道来混合两张纹理 fixed4 col = lerp(firstLayer, secondLayer, secondLayer.a); // 调整整体亮度 col.rgb *= _Multiplier; return col; }
-
-
回到编辑器,将底层纹理和表层纹理赋好值,调整好滚动速度即可
效果:

11.3 顶点动画
11.3.1 流动的河流
步骤:
- 将摄像机投影类型改为"正交"
- 建 3 个材质(因为要模拟 3 层水波),建 Shader,赋给 3 个材质
- 创建 3 个四边形(Quad),调整位置、大小和方向,将上步创建的 3 个材质分别赋给它们
- Shader 代码:(源码)
-
设置 SubShader 标签
jsSubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Opaque" "DisableBatching"="True" }
- DisableBatching:包含了模型空间的顶点动画的 Shader,使用 Unity 的批处理功能时会出现问题。这是因为批处理会合并所有相关的模型,令它们各自的模型空间丢失,从而无法进行模型空间的顶点动画。这里就需要取消批处理
-
设置 Pass 的渲染状态
jsPass { Tags { "LightMode"="ForwardBase" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Cull Off
- 为了让每层水流都能显示
-
11.3.2 广告牌
Shader 代码:(源码)
11.3.3 注意事项
- 在模型空间下进行顶点动画,往往要关掉批处理,但这样会带来一定的性能下降,增加 Draw Call
- 应尽量避免使用模型空间下的一些绝对位置和方向来进行计算
- 广告牌例子中,可利用顶点颜色来存储每个顶点到锚点的距离值
- 顶点动画的物体,若想正确地往其他物体上投影,需要提供一个自定义的带顶点动画的 ShadowCaster Pass