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

原文地址:https://www.cnblogs.com/shenyibo/p/14185656.html