You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
        }
    }
}

关键修改说明

  1. 射线生成:通过NDC坐标转换生成标准化世界射线,彻底解决宽高比导致的椭圆问题;
  2. 交点逻辑:修复raySphere函数,支持相机在球内的场景,解决背面不可见问题;
  3. 半径计算:在C#中转换为世界空间半径,确保球体大小与场景中物体一致;
  4. 渲染标签:调整RenderType为Transparent,确保渲染队列正确。

内容的提问来源于stack exchange,提问作者minitruf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 03:18:24