Unity快速實現平行光體積光(URP)


體積光的光源可以是平行光、探照燈、點光源等,我們今天先來看看平行光如何制作體積光。

體積光的原理網上已經有很多了,這里就不贅述了。着重快速實現:

Shader "Unlit/VolumetricLightingShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Intensity("Intensity",float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define MAIN_LIGHT_CALCULATE_SHADOWS
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"
            #define STEP_TIME 64
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos:TEXCOORD1;
                float4 screenPos :TEXCOORD2;
            };

            TEXTURE2D_X_FLOAT(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture);
            TEXTURE2D(_CameraOpaqueTexture); SAMPLER(sampler_CameraOpaqueTexture);
            TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
            float _Intensity;
            v2f vert (appdata v)
            {
                v2f o;
                
                o.vertex = TransformObjectToHClip(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.screenPos = ComputeScreenPos(o.vertex);
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                half2 screenPos = i.screenPos.xy / i.screenPos.w;
                //rebuild world position according to depth
                float depth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture,sampler_CameraDepthTexture, screenPos).r;
                depth = Linear01Depth(depth, _ZBufferParams);
                float2 positionNDC = screenPos * 2 - 1;
                float3 farPosNDC = float3(positionNDC.xy,1)*_ProjectionParams.z;
                float4 viewPos = mul(unity_CameraInvProjection,farPosNDC.xyzz);
                viewPos.xyz *= depth;
                float4 worldPos = mul(UNITY_MATRIX_I_V,viewPos);
                
                float noise = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, screenPos*3).r;
                float3 startPos = i.worldPos;
                float3 dir = normalize(worldPos - startPos);
                startPos += dir * noise;
                worldPos.xyz += dir * noise;
                float len = length(worldPos - startPos);
                float3 stepLen = dir * len / STEP_TIME;
                half3 color = 0;

                half3 sceneColor = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, screenPos).rgb;
                
                UNITY_LOOP
                for (int i = 0; i < STEP_TIME; i++)
                {
                    startPos += stepLen;
                    float4 shadowPos = TransformWorldToShadowCoord(startPos);
                    float intensity = MainLightRealtimeShadow(shadowPos)*_Intensity;
                    color += intensity*_MainLightColor.rgb;
                }
                
                color /= STEP_TIME;
                color += sceneColor;
                return half4(color.xyz,1);
            }
            ENDHLSL
        }
    }
}

代碼如上,可以看到體積光的基礎實現非常簡單,在片元着色器步進采樣shadowmap,看當前步進的位置是否處於陰影區域,如果不處於陰影區域,就疊加強度,最后形成散射效果。

此shader直接掛在一個quad片上,能把相機遮住就OK,這種方式適合快速實現各種屏幕空間的效果,等效果覺得OK了,然后再慢慢的轉移到URP的RenderFeature中。

用quad片直接放在相機前面制作上有以下好處:

  1.不需要寫任何c#代碼,一個shader文件全搞定

  2.不需要計算屏幕空間近裁切面的世界坐標的位置,直接取片的position就可以,非常的方便

  3.快,主要就是快,這里指的快是寫代碼敲得快,哈哈哈~

Shader有兩個參數,第一個參數是傳入一張紋理,這里需要傳入噪聲紋理,最好是blue noise,這個紋理是用來dither步進距離特別大的時候產生的分層效果,使得分層感不那么明顯。通過調整“#define STEP_TIME 64”中的數值來控制步進次數。這里可以看一下沒有dither(上圖)和有dither(下圖)的效果對比:

 

 

 可以看到分層感的改善還是非常明顯的。需要注意的是這里的實現只是簡單的步進疊加強度,沒有進行任何的散射算法實現,常用的散射算法有米氏散射瑞利散射,各位童鞋可自行查看,套用公式即可,這里給出這個框架,公式隨便套~

如果代碼看不懂,這里給出幾個關鍵詞可供百度,百度完自然就能看懂了:

1.Raymarching

2.基於深度還原世界坐標

3.ShadowMap的實現原理

如果看完還有問題,歡迎博客討論區留言~  或者加qq群:104794354


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM