Unity内置渲染管线体积渲染Shader问题求助
体积大气Shader问题修复方案
问题1:渲染出椭圆而非球体
原因:Blit用的屏幕对齐四边形worldPos会随屏幕宽高比拉伸,导致计算出的射线方向变形,无法正确对应视锥体射线。
修复:通过相机的逆投影、逆视图矩阵计算标准化的世界空间射线,确保射线方向不受屏幕分辨率影响。
问题2:背面查看球体消失
原因:原raySphere函数未处理相机在球体内部的情况,此时交点计算会返回无效值,导致球体不显示。
修复:调整交点判断逻辑,无论相机在球内还是球外,都正确计算射线的进出距离。
问题3:球体位置和半径与设置不符
原因:
- SphereCollider的
radius是本地空间值,未考虑物体缩放; - 原射线方向计算错误,导致位置判断偏移。
修复:在C#中计算世界空间半径,Shader中使用正确的射线生成方式。
修改后的C#代码
using UnityEngine; [ExecuteInEditMode, ImageEffectAllowedInSceneView] public class ImageEffectManager : MonoBehaviour { public Shader effectShader; public Transform volume; private Material _mat; private void OnRenderImage(RenderTexture src, RenderTexture dest) { if (_mat == null && effectShader != null) { _mat = new Material(effectShader); _mat.hideFlags = HideFlags.HideAndDontSave; } if (_mat == null || volume == null) { Graphics.Blit(src, dest); return; } // 计算世界空间半径:适配物体缩放(非均匀缩放取最大分量) SphereCollider collider = volume.GetComponent<SphereCollider>(); float worldRadius = collider.radius * Mathf.Max(volume.localScale.x, volume.localScale.y, volume.localScale.z); _mat.SetVector("_SphereCenter", volume.position); _mat.SetFloat("_SphereRadius", worldRadius); // 传递相机矩阵用于射线计算 _mat.SetMatrix("_InvProjMatrix", Camera.main.projectionMatrix.inverse); _mat.SetMatrix("_InvViewMatrix", Camera.main.worldToCameraMatrix.inverse); Graphics.Blit(src, dest, _mat); } }
修改后的Shader代码
Shader "Unlit/VolumetricAtmosphere" { Properties { _MainTex ("Texture", 2D) = "white" {} _SphereCenter ("Sphere Center", Vector) = (0, 0, 0, 0) _SphereRadius ("Sphere Radius", Float) = 1 } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } Blend One Zero Cull Off Lighting Off ZWrite Off LOD 100 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; float3 rayDir : TEXCOORD1; // 世界空间射线方向 }; sampler2D _MainTex; float4 _MainTex_ST; float3 _SphereCenter; float _SphereRadius; float4x4 _InvProjMatrix; float4x4 _InvViewMatrix; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 从NDC坐标转换为世界空间射线方向 float4 clipPos = float4(v.uv * 2 - 1, 0, 1); float4 viewPos = mul(_InvProjMatrix, clipPos); viewPos /= viewPos.w; o.rayDir = normalize(mul(_InvViewMatrix, viewPos).xyz); return o; } float2 raySphere(float3 sphereCenter, float sphereRadius, float3 rayOrigin, float3 rayDir) { float3 offset = rayOrigin - sphereCenter; float a = dot(rayDir, rayDir); float b = 2 * dot(offset, rayDir); float c = dot(offset, offset) - sphereRadius * sphereRadius; float discriminant = b * b - 4 * a * c; if (discriminant < 0) return float2(-1, -1); float sqrtDiscrim = sqrt(discriminant); float t0 = (-b - sqrtDiscrim) / (2 * a); float t1 = (-b + sqrtDiscrim) / (2 * a); // 确保t0 <= t1 if (t0 > t1) { float temp = t0; t0 = t1; t1 = temp; } // 处理相机在球内的情况 float enterDist = max(t0, 0.0); float exitDist = t1; if (exitDist < 0) return float2(-1, -1); return float2(enterDist, exitDist - enterDist); } float4 frag (v2f i) : SV_Target { float4 col = tex2D(_MainTex, i.uv); float3 rayOrigin = _WorldSpaceCameraPos; float2 hitInfo = raySphere(_SphereCenter, _SphereRadius, rayOrigin, i.rayDir); if (hitInfo.y < 0) return col; // 叠加大气颜色(可替换为更复杂的散射计算) float density = hitInfo.y / (_SphereRadius * 2); col.rgb += density * float3(0.2, 0.4, 0.8); return col; } ENDCG } } }
关键修改说明
- 射线生成:通过NDC坐标转换生成标准化世界射线,彻底解决宽高比导致的椭圆问题;
- 交点逻辑:修复
raySphere函数,支持相机在球内的场景,解决背面不可见问题; - 半径计算:在C#中转换为世界空间半径,确保球体大小与场景中物体一致;
- 渲染标签:调整
RenderType为Transparent,确保渲染队列正确。
内容的提问来源于stack exchange,提问作者minitruf
相关产品推荐
相关产品推荐

