1. 程式人生 > 實用技巧 >Unity快速實現平行光體積光(URP)

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的實現原理

如果看完還有問題,歡迎部落格討論區留言~