【URP】[平面阴影]原理与实现
【URP】[平面阴影]原理与实现一、为什么需要平面阴影在游戏开发中阴影是提升画面真实感的关键元素。传统的实时阴影如阴影贴图虽然效果出色但计算开销较大尤其在移动平台或性能有限的设备上。平面阴影Projected Planar Shadow是一种轻量级的阴影技术它通过将物体投影到一个平面上来模拟阴影效果具有性能高、实现简单的特点特别适合用于2D游戏或需要简单阴影效果的场景。在Unity的通用渲染管线URP中我们可以通过自定义Shader和C#脚本实现高效的平面阴影。本文将循序渐进地讲解平面阴影的原理与实现方法。## 二、基础概念阴影投影数学原理平面阴影的核心思想是将3D物体沿着光线方向投影到指定平面上。假设我们有一个点光源位于位置L物体上的顶点P以及一个平面由法线N和平面到原点的距离D定义。阴影点S可以通过以下公式计算S P - (dot(P - L, N) * (P - L)) / dot(P - L, N)这个公式的几何意义是从光源L到顶点P的射线与平面相交交点即为阴影位置。## 三、在URP中实现平面阴影Shader首先我们创建一个简单的平面阴影Shader。这个Shader会接收物体的顶点位置并计算其在平面上的投影。hlsl// 文件: PlanarShadow.shaderShader Custom/PlanarShadow{ Properties { _ShadowColor (Shadow Color, Color) (0, 0, 0, 0.5) _ShadowFade (Shadow Fade, Range(0, 1)) 0.5 } SubShader { Tags { RenderTypeTransparent QueueTransparent } LOD 100 // 使用混合模式实现半透明阴影 Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; float alpha : TEXCOORD0; }; float4 _ShadowColor; float _ShadowFade; // 平面定义假设平面在y0处法线向上 #define PLANE_Y 0.0 #define PLANE_NORMAL float3(0, 1, 0) // 光源位置从世界空间获取 float3 _LightPosition; v2f vert (appdata v) { v2f o; // 获取顶点世界坐标 float3 worldPos mul(unity_ObjectToWorld, v.vertex).xyz; // 计算光线方向从光源到顶点 float3 lightDir worldPos - _LightPosition; // 计算光线与平面的交点 float t - (dot(worldPos, PLANE_NORMAL) - PLANE_Y) / dot(lightDir, PLANE_NORMAL); float3 shadowPos worldPos t * lightDir; // 将阴影位置转换到裁剪空间 o.vertex mul(UNITY_MATRIX_VP, float4(shadowPos, 1.0)); // 根据高度计算阴影透明度 float height worldPos.y - PLANE_Y; o.alpha saturate(1.0 - height * _ShadowFade); return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(_ShadowColor.rgb, _ShadowColor.a * i.alpha); } ENDCG } }}## 四、创建C#控制脚本管理阴影有了Shader后我们需要一个脚本来动态创建和管理阴影对象。这个脚本会实时更新光源位置并将阴影渲染到平面上。csharpusing UnityEngine;// 文件: PlanarShadowController.cspublic class PlanarShadowController : MonoBehaviour{ [Header(阴影设置)] public GameObject shadowCaster; // 投射阴影的物体 public Material shadowMaterial; // 阴影材质 public Transform lightSource; // 光源位置可以是点光源或空对象 public float shadowHeight 0.01f; // 阴影与平面的偏移防止Z-fighting private GameObject shadowObject; private MeshFilter shadowMeshFilter; private MeshRenderer shadowMeshRenderer; void Start() { // 创建阴影物体 shadowObject new GameObject(PlanarShadow); shadowObject.transform.parent shadowCaster.transform; // 添加Mesh组件 shadowMeshFilter shadowObject.AddComponentMeshFilter(); shadowMeshRenderer shadowObject.AddComponentMeshRenderer(); // 复制源物体的网格 Mesh sourceMesh shadowCaster.GetComponentMeshFilter().sharedMesh; shadowMeshFilter.sharedMesh sourceMesh; // 应用阴影材质 shadowMeshRenderer.sharedMaterial shadowMaterial; shadowMeshRenderer.shadowCastingMode UnityEngine.Rendering.ShadowCastingMode.Off; shadowMeshRenderer.receiveShadows false; } void Update() { if (lightSource null || shadowMaterial null) return; // 更新阴影材质中的光源位置 shadowMaterial.SetVector(_LightPosition, lightSource.position); // 将阴影物体定位到投影平面上方一点点防止Z-fighting shadowObject.transform.position new Vector3( shadowCaster.transform.position.x, shadowHeight, shadowCaster.transform.position.z ); // 同步旋转和缩放阴影需要继承物体的旋转和缩放 shadowObject.transform.rotation shadowCaster.transform.rotation; shadowObject.transform.localScale shadowCaster.transform.localScale; }}## 五、进阶技巧处理复杂形状和性能优化### 5.1 处理非均匀缩放当物体有非均匀缩放时阴影会变形。我们可以通过修改Shader来考虑模型的缩放矩阵hlsl// 在vert函数中修改float3 worldPos mul(unity_ObjectToWorld, v.vertex).xyz;// 实际上上面的代码已经自动处理了缩放但为了更精确// 我们可以使用float4x4矩阵进行变换### 5.2 性能优化建议1.减少网格顶点数量使用低多边形模型作为阴影投射源2.使用LOD远处的阴影可以使用更简单的网格3.阴影更新频率对于静止物体可以每帧更新改为每几帧更新一次4.批处理使用GPU Instancing来渲染多个阴影## 六、完整示例添加平面阴影到场景以下是一个完整的场景设置示例csharp// 文件: SceneSetup.csusing UnityEngine;public class SceneSetup : MonoBehaviour{ void Start() { // 1. 创建地面平面接收阴影 GameObject ground GameObject.CreatePrimitive(PrimitiveType.Plane); ground.transform.position Vector3.zero; ground.transform.localScale Vector3.one * 10; // 2. 创建阴影投射物体例如一个立方体 GameObject cube GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.position new Vector3(0, 2, 0); cube.transform.localScale new Vector3(1, 2, 1); // 3. 添加阴影控制器 PlanarShadowController controller cube.AddComponentPlanarShadowController(); controller.shadowCaster cube; controller.shadowMaterial Resources.LoadMaterial(Materials/PlanarShadowMat); // 4. 创建光源使用空对象模拟点光源 GameObject lightObj new GameObject(ShadowLight); lightObj.transform.position new Vector3(3, 5, -2); controller.lightSource lightObj.transform; // 5. 添加方向光用于环境照明 GameObject dirLight new GameObject(DirectionalLight); dirLight.AddComponentLight().type LightType.Directional; dirLight.transform.rotation Quaternion.Euler(50, -30, 0); }}## 七、总结平面阴影是一种简单高效的阴影技术特别适合性能敏感的场景。通过本文的学习我们掌握了1.数学原理理解了光线投影到平面的几何计算方法2.Shader实现学会了在URP中编写平面阴影Shader包括顶点变换和透明度处理3.C#控制掌握了如何动态创建和管理阴影对象4.性能优化了解了实际应用中的优化技巧这种阴影技术的优势在于- 计算量极小适合移动平台- 实现简单易于理解和调试- 可以灵活控制阴影颜色和透明度但需要注意它的局限性- 只能投射到单一平面上- 无法处理自阴影和复杂的光照情况- 对于弯曲表面效果不佳在实际游戏开发中平面阴影可以作为高性能的替代方案或者与其他阴影技术结合使用以达到最佳的性能与效果平衡。