进阶Unity Shader:打造可旋转与镜像的2D动态阴影

发布时间:2026/7/14 12:19:31
进阶Unity Shader:打造可旋转与镜像的2D动态阴影 1. 为什么需要动态2D阴影在2D游戏开发中阴影效果往往是最容易被忽视的部分。很多开发者会直接使用静态阴影贴图或者干脆省略阴影效果。但实测下来一个能随角色动作变化的动态阴影能让游戏画面质感提升至少30%。我最近在开发一款2D平台游戏时就深有体会。最初用的是固定阴影贴图结果测试时玩家反馈角色像飘在空中。后来改用动态阴影后不仅解决了漂浮感问题连场景的光影层次都丰富了起来。特别是当角色左右转身时阴影能实时跟随翻转那种真实感是静态阴影无法比拟的。动态阴影的核心在于三个关键参数的控制旋转根据光源角度调整阴影方向偏移模拟真实阴影的近大远小效果镜像配合角色朝向实现自然翻转2. Shader实现基础框架2.1 双Pass渲染架构要实现动态阴影我们需要使用双Pass的Shader结构。第一个Pass专门渲染阴影第二个Pass渲染角色本身。这种分离渲染的方式有三大优势可以单独控制阴影的渲染参数避免角色纹理影响阴影效果便于后期添加更多特效下面是最基础的Shader框架代码Shader Custom/2DShadow { Properties { _MainTex (Texture, 2D) white {} } SubShader { Tags { QueueTransparent } // 阴影Pass Pass { // 阴影渲染代码 } // 角色Pass Pass { // 角色渲染代码 } } }2.2 透明队列设置特别注意Tags中的QueueTransparent设置。这确保了阴影能正确显示在角色下方。如果没有这个设置可能会出现阴影覆盖角色或者完全不显示的情况。我在项目中就踩过这个坑。当时阴影怎么调都不显示最后发现是渲染队列设置成了Geometry。改成Transparent后立即正常。这里有个小技巧如果遇到阴影显示异常第一个要检查的就是渲染队列。3. 矩阵变换控制阴影3.1 构建变换矩阵动态阴影的核心就是通过矩阵变换控制阴影形态。我们需要构建一个包含旋转和偏移参数的变换矩阵float4x4 transMatrix { 1, _Rotation.x, 0, _Offset.x, _Rotation.y, 1, 0, _Offset.y, 0, 0, 1, 0, 0, 0, 0, 1 };这个矩阵的四个关键参数_Rotation.x控制X轴方向的倾斜_Rotation.y控制Y轴方向的倾斜_Offset.xX轴位移_Offset.yY轴位移3.2 顶点变换实现在顶点着色器中应用这个矩阵v2f vert(appdata v) { v2f o; v.vertex mul(transMatrix, v.vertex); o.vertex UnityObjectToClipPos(v.vertex); o.uv v.uv; return o; }实测发现直接修改vertex比后续处理更高效。我在移动端测试时这种方式能节省约15%的GPU时间。4. 镜像翻转功能实现4.1 UV坐标镜像原理要实现阴影随角色转身而翻转最简单的办法就是镜像UV坐标。当角色向左时我们把UV的y坐标反转if (_IsMirror) { o.uv.y 1 - o.uv.y; }这个技巧不仅适用于阴影任何需要镜像的效果都可以这样实现。比如水中的倒影、对称的特殊效果等。4.2 动态切换控制通常我们会用一个布尔值_IsMirror来控制是否启用镜像。这个值可以通过脚本根据角色朝向动态设置material.SetInt(_IsMirror, isFacingLeft ? 1 : 0);建议在角色控制器中处理这个逻辑确保阴影翻转与角色动画同步。我在项目中是这样实现的void Update() { float move Input.GetAxis(Horizontal); if(move 0 isFacingLeft) { shadowMaterial.SetInt(_IsMirror, 0); isFacingLeft false; } else if(move 0 !isFacingLeft) { shadowMaterial.SetInt(_IsMirror, 1); isFacingLeft true; } }5. 阴影视觉效果优化5.1 颜色与透明度好的阴影不应该只是简单的黑色。在片段着色器中我们可以这样处理fixed4 frag(v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); if(col.a 0.1) { col.rgb float3(0,0,0); // 纯黑阴影 col.a _ShadowAlpha; // 可控透明度 } return col; }建议将阴影透明度_ShadowAlpha设置在0.2-0.5之间。太浅会看不清太深又会显得不自然。5.2 边缘柔化技巧硬边阴影在2D游戏中会显得很假。可以通过以下方法实现边缘柔化在Photoshop中为角色纹理添加1-2像素的羽化边缘在Shader中添加边缘透明度渐变float edge smoothstep(0.1, 0.2, col.a); col.a * edge;6. 完整Shader代码以下是整合了所有功能的完整Shader代码Shader Custom/Advanced2DShadow { Properties { _MainTex (Texture, 2D) white {} _ShadowParams (Rotation(XY) Offset(ZW), Vector) (-0.3,0,0.1,0) _ShadowAlpha (Shadow Alpha, Range(0,1)) 0.4 [Toggle]_IsMirror (Mirror Shadow, Int) 0 } SubShader { Tags { RenderTypeTransparent QueueTransparent } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off 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 _ShadowParams; float _ShadowAlpha; int _IsMirror; v2f vert(appdata v) { v2f o; float4x4 transMatrix { 1, _ShadowParams.x, 0, _ShadowParams.z, _ShadowParams.y, 1, 0, _ShadowParams.w, 0, 0, 1, 0, 0, 0, 0, 1 }; v.vertex mul(transMatrix, v.vertex); o.vertex UnityObjectToClipPos(v.vertex); o.uv v.uv; if(_IsMirror) { o.uv.y 1 - o.uv.y; } return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); if(col.a 0.1) { col.rgb float3(0,0,0); col.a _ShadowAlpha; // 边缘柔化 float edge smoothstep(0.1, 0.2, col.a); col.a * edge; } return col; } ENDCG } 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; v2f vert(appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv v.uv; return o; } fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex, i.uv); } ENDCG } } }7. 实战应用技巧7.1 光源方向模拟要让阴影更真实可以根据虚拟光源位置动态调整阴影参数。比如模拟太阳位置void UpdateShadow() { Vector2 sunDir (sunTransform.position - character.position).normalized; material.SetVector(_ShadowParams, new Vector4( sunDir.x * 0.3f, // X旋转 sunDir.y * 0.2f, // Y旋转 sunDir.x * 0.1f, // X偏移 sunDir.y * 0.1f // Y偏移 )); }7.2 性能优化建议尽量复用材质实例而非每次创建新材质将阴影渲染限制在必要场合如角色移动时在低端设备上可以降低阴影更新频率我在移动端项目中使用了这样的优化策略IEnumerator ShadowUpdateRoutine() { while(true) { UpdateShadow(); yield return new WaitForSeconds( QualitySettings.GetQualityLevel() 1 ? 0.02f : 0.1f ); } }8. 常见问题排查8.1 阴影不显示可能原因及解决方案渲染队列设置错误 → 检查Tags中的Queue混合模式未启用 → 确认Blend设置纹理Alpha通道问题 → 检查纹理导入设置8.2 镜像效果异常典型表现镜像方向错误 → 检查UV修改逻辑镜像后纹理错位 → 确认纹理的Wrap Mode为Clamp8.3 性能问题如果发现帧率下降检查是否有多余的材质实例减少实时更新的频率考虑使用对象池管理阴影对象