从瀑布流到旋转法阵:手把手带你用Unity Shader玩转UV动画,附极坐标实战代码
从瀑布流到旋转法阵:手把手带你用Unity Shader玩转UV动画,附极坐标实战代码
在游戏开发中,视觉效果往往是吸引玩家的第一要素。而UV动画作为Shader编程中最基础也最强大的工具之一,能够为静态贴图注入生命力。本文将带你从简单的平移动画开始,逐步深入到极坐标变换,最终实现一个环绕角色的动态法阵特效。无论你是想制作流动的河水、飘动的旗帜,还是酷炫的技能特效,掌握这些技巧都能让你的游戏视觉效果更上一层楼。
1. UV动画基础:让贴图动起来
UV动画的核心思想是通过改变UV坐标来改变贴图的采样位置。在Unity中,我们可以利用内置的_Time变量来实现这一效果。这个变量会随着游戏运行不断增长,为我们提供了天然的动画驱动源。
1.1 平移动画实现
最简单的UV动画就是让贴图沿着某个方向平移。下面是一个基础Shader代码示例:
Shader "Custom/UVScroll" { Properties { _MainTex ("Texture", 2D) = "white" {} _ScrollSpeed ("Scroll Speed", Vector) = (0.1, 0, 0, 0) } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float2 _ScrollSpeed; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { float2 scrollUV = i.uv + _ScrollSpeed * _Time.y; fixed4 col = tex2D(_MainTex, scrollUV); return col; } ENDCG } } }提示:
_Time.y表示游戏运行的总秒数,使用它可以让动画速度不受帧率影响。如果想实现循环动画,可以使用frac(_Time.y)函数。
1.2 常见应用场景
这种基础平移动画可以用于多种游戏场景:
- 环境效果:流动的河水、飘动的云层、移动的背景
- 材质动画:传送门效果、能量屏障
- 特殊效果:扫描线、雷达波
通过调整_ScrollSpeed参数,你可以控制动画的方向和速度。例如,设置(0, 0.1)可以让贴图向上移动,模拟瀑布效果。
2. UV帧动画:序列帧播放技巧
除了连续动画,我们还可以用UV变换来实现序列帧动画。这种方法比传统的SpriteRenderer更高效,因为所有帧都存储在一张贴图中,只需通过Shader控制显示哪一帧。
2.1 基础帧动画实现
假设我们有一张包含4x4帧动画的贴图,下面是实现序列帧播放的Shader代码:
Shader "Custom/FrameAnimation" { Properties { _MainTex ("Sprite Sheet", 2D) = "white" {} _Rows ("Rows", Int) = 4 _Columns ("Columns", Int) = 4 _Speed ("Speed", Float) = 10 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; int _Rows; int _Columns; float _Speed; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { // 计算总帧数 float frameCount = _Rows * _Columns; // 计算当前帧索引 float frameIndex = floor(_Time.y * _Speed % frameCount); // 计算当前帧所在的行列 float row = floor(frameIndex / _Columns); float column = frameIndex % _Columns; // 计算UV偏移 float2 frameUV = i.uv / float2(_Columns, _Rows); frameUV += float2(column / _Columns, 1 - (row + 1) / _Rows); fixed4 col = tex2D(_MainTex, frameUV); return col; } ENDCG } } }2.2 多动画控制技巧
在实际项目中,我们可能需要在一张贴图中存储多个动画序列。这时可以通过调整UV的缩放和偏移来实现:
- 缩放UV:将UV缩小到只覆盖一个动画序列的区域
- 偏移UV:移动到特定动画序列的起始位置
- 播放控制:使用脚本控制Shader参数来切换不同动画
这种方法特别适合角色动画、特效动画等需要多个动画序列的场景。
3. 极坐标变换:打造环绕法阵特效
极坐标变换是UV动画中的高级技巧,它可以将直角坐标系转换为极坐标系,非常适合创建圆形、放射状的特效。
3.1 极坐标基础原理
极坐标用半径(r)和角度(θ)来表示位置,与直角坐标(x,y)的转换关系如下:
| 转换方向 | 公式 |
|---|---|
| 直角→极坐标 | r = √(x² + y²) θ = atan2(y, x) |
| 极坐标→直角 | x = r * cos(θ) y = r * sin(θ) |
在Shader中实现极坐标变换的关键步骤:
- 将UV原点移动到中心(从[0,1]映射到[-1,1])
- 计算极坐标(r, θ)
- 将极坐标重新映射到[0,1]范围用于采样贴图
3.2 动态法阵特效实现
下面是一个完整的极坐标法阵特效Shader实现:
Shader "Custom/PolarCoordinate" { Properties { _MainTex ("Pattern Texture", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) _Speed ("Rotation Speed", Float) = 1 _Radius ("Radius", Float) = 0.5 _Intensity ("Intensity", Float) = 1 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _Color; float _Speed; float _Radius; float _Intensity; // 直角坐标转极坐标 float2 RectToPolar(float2 uv, float2 center) { uv -= center; float theta = atan2(uv.y, uv.x); float r = length(uv); return float2(theta, r); } v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { // 将UV原点移动到中心 float2 centerUV = i.uv - 0.5; // 转换为极坐标 float2 polar = RectToPolar(i.uv, float2(0.5, 0.5)); // 创建动态UV float2 animUV = float2( polar.x / UNITY_PI * 0.5 + 0.5 + _Time.y * _Speed, // 角度映射到[0,1]并添加旋转 polar.y / _Radius + frac(_Time.y * 0.5) // 半径缩放并添加流动效果 ); // 采样纹理 fixed4 tex = tex2D(_MainTex, animUV); // 边缘衰减 float edge = 1 - smoothstep(_Radius * 0.8, _Radius, polar.y); // 最终颜色 fixed4 col = _Color * tex; col.a *= edge * _Intensity; return col; } ENDCG } } }注意:这个Shader使用了透明混合(Blend),适合用于特效渲染。如果用于不透明物体,需要移除Blend指令并调整渲染队列。
3.3 法阵特效优化技巧
要让法阵特效更加炫酷,可以尝试以下优化:
- 多层叠加:使用多个不同速度旋转的法阵叠加,增加层次感
- 动态半径:通过脚本控制半径参数,实现法阵展开/收缩动画
- 颜色变化:根据时间或游戏事件改变法阵颜色
- 扭曲效果:在极坐标转换前对UV进行扭曲处理,创造更复杂的图案
4. 实战调试技巧与性能优化
掌握了UV动画的基本原理后,如何高效调试和优化这些效果同样重要。
4.1 常见问题排查
以下是UV动画开发中常见的问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 贴图闪烁 | UV坐标超出范围 | 使用frac()函数确保UV在[0,1]范围内 |
| 动画卡顿 | 使用_Time.x而非_Time.y | 改用_Time.y确保时间不受帧率影响 |
| 边缘撕裂 | Wrap Mode设置不当 | 检查贴图导入设置,确保Wrap Mode为Repeat |
| 效果不符预期 | 运算顺序错误 | 检查UV变换顺序,通常是先缩放后平移 |
4.2 Shader Graph实现
对于偏好可视化编程的开发者,Unity的Shader Graph同样可以实现上述效果。以下是关键节点设置:
平移动画:
- 使用Time节点驱动Vector2参数
- 通过Add节点将UV与时间偏移相加
极坐标变换:
- 使用Polar Coordinate节点转换UV
- 通过Multiply和Add节点调整极坐标范围
- 使用Time节点添加旋转动画
帧动画:
- 使用Fraction和Floor节点计算当前帧
- 通过Divide和Multiply节点定位到具体帧
4.3 性能优化建议
UV动画虽然强大,但也需要注意性能影响:
- 批处理:确保使用相同Shader和贴图的物体能够合批
- 实例化:对大量相同动画物体使用GPU Instancing
- LOD:根据距离简化远处物体的动画效果
- 贴图优化:使用适当大小的贴图,避免过度浪费内存
在实际项目中,我发现极坐标特效特别适合用于角色技能指示器。通过动态调整半径参数,可以清晰地展示技能范围,而旋转动画则能增强视觉效果。一个实用的技巧是将法阵分为内外两层,以不同速度反向旋转,这样即使使用简单的贴图也能创造出复杂的视觉效果。
