Unity中的混合光照

Unity支持三种混合光照模式,分别是Baked Indirect,Subtractive,Shadowmask。Shadowmask模式又分为两种,Shadowmask和Distance Shadowmask。这次我们来研究一下这三种光照模式,对光照,静态/动态物体,阴影,以及前向/延迟渲染的影响。首先三种模式的设置放在Window/Rendering/Lighting Settings下:

在这里插入图片描述

Shadowmask模式的设置现在放到了Edit/Project Settings下,位于Quality选项:

在这里插入图片描述

其次,要开启混合光照,需要将光源的模式设置为mixed:

在这里插入图片描述

下面让我们对这几个光照模式逐一进行分析。

Baked Indirect

如图所示的场景,我们只启用一个平行光源并设置为mixed,将光照模式调置baked indirect:

在这里插入图片描述

该模式只会将光源间接光照的部分烘焙到lightmap和light probe中,实时光照则不受影响。由于它只烘焙间接光照,因此lightmap看起来会比较暗:

在这里插入图片描述

lightmap用于为标记为静态static的物体添加间接光照的信息,动态dynamic的物体是使用light probe来获取间接光照。baked indirect模式天然对多个光源支持友好,例如我们再开启一个平行光源:

在这里插入图片描述

此时看一下烘焙的lightmap,对比发现变亮了一些,这是因为多了一个光源的间接光照:

在这里插入图片描述

baked indirect模式下前向渲染的流程与实时光照基本相同,由一个base pass加上多个光源的add pass组成:

在这里插入图片描述
在这里插入图片描述

由于lightmap中已经包含所有光源的间接光照,因此只要在forward base pass中对lightmap采样一次即可,避免forward add pass多次采样。延迟渲染的流程也基本不变,几个光源就有几个light pass:

在这里插入图片描述

不过这里注意到light pass中并没有开启LIGHTMAP_ON宏。LIGHTMAP_ON宏是在geometry pass中生效的:

在这里插入图片描述

这就意味着我们需要在geometry pass中对lightmap进行采样,将间接光照的信息写入G-Buffer。最后来看下阴影,baked indirect模式下前向渲染的阴影都是实时渲染的,和实时光源的流程基本一致:

在这里插入图片描述

场景中有两个平行光源,因此会做两次阴影收集。但是注意在base pass中渲染阴影时,要避免使用unity内置的UNITY_LIGHT_ATTENUATION宏,此时shadow fade会失效:

在这里插入图片描述
在这里插入图片描述

两张截图都是shadow distance设置为10的效果。第一张截图是自定义材质,第二张截图是使用standard shader的默认材质,对比可以发现第二张截图shadow fade的效果更明显。那么为什么此时shadow fade会失效呢?

首先我们打开frame debug,观察到base pass下渲染静态物体时定义了LIGHTMAP_ONSHADOWS_SCREEN这两个宏:

在这里插入图片描述

在这种情况下,unity在UnityShadowLibrary.cginc中还会定义另外一个宏:

#if defined( SHADOWS_SCREEN ) && defined( LIGHTMAP_ON )
    #define HANDLE_SHADOWS_BLENDING_IN_GI 1
#endif

而如果定义了这个宏,unity对shadow的采样函数就会发生变化:

#if defined(HANDLE_SHADOWS_BLENDING_IN_GI) // handles shadows in the depths of the GI function for performance reasons
#   define UNITY_SHADOW_COORDS(idx1) SHADOW_COORDS(idx1)
#   define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
#   define UNITY_SHADOW_ATTENUATION(a, worldPos) SHADOW_ATTENUATION(a)
#else
    ...
#endif

在没有定义HANDLE_SHADOWS_BLENDING_IN_GI这个宏的情况下,UNITY_SHADOW_ATTENUATION宏都会走到UnityComputeForwardShadows这个函数中:

// -----------------------------
//  Shadow helpers (5.6+ version)
// -----------------------------
// This version depends on having worldPos available in the fragment shader and using that to compute light coordinates.
// if also supports ShadowMask (separately baked shadows for lightmapped objects)

half UnityComputeForwardShadows(float2 lightmapUV, float3 worldPos, float4 screenPos)
{
    //fade value
    float zDist = dot(_WorldSpaceCameraPos - worldPos, UNITY_MATRIX_V[2].xyz);
    float fadeDist = UnityComputeShadowFadeDistance(worldPos, zDist);
    half  realtimeToBakedShadowFade = UnityComputeShadowFade(fadeDist);
    ...
}

可以看到此时调用了unity内置的计算shadow fade的函数。而SHADOW_ATTENUATION这个宏并不会:

#if defined (SHADOWS_SCREEN)
    #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif
        
#if defined (SHADOWS_DEPTH) && defined (SPOT)
	#define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)
#endif
        
#if defined (SHADOWS_CUBE)
	#define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)
#endif
        
#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
	#define SHADOW_ATTENUATION(a) 1.0
#endif

unitySampleShadowUnitySampleShadowmap这两个接口只会对shadowmap进行采样,返回平滑过的采样结果,并不会考虑shadow fade的情况,因此我们需要在定义HANDLE_SHADOWS_BLENDING_IN_GI这个宏的条件下,在调用完UNITY_SHADOW_ATTENUATION之后手动补上shadow fade的处理,处理方式可以参考UnityComputeForwardShadows这个函数。处理之后效果如下:

在这里插入图片描述

可以看出此时已经有了shadow fade的效果。另外值得一提的是,设置shadow distance,会在渲染shadowmap时对超出distance范围的物体进行剔除,使得最后渲染出的shadow map本身就不包含超出shadow distance物体的投射阴影信息:

在这里插入图片描述

在shadow distance设置为10时,RenderShadowMap只用了17个pass,而设置为200时,需要41个pass:

在这里插入图片描述

那么,如果是延迟渲染路径的话,shadow fade会失效吗?答案是不会的。因为在延迟渲染中,采样lightmap的时机和绘制阴影的时机是分开的,一个在geometry pass,一个在light pass:

在这里插入图片描述
在这里插入图片描述

Shadowmask

baked indirect模式开销比较昂贵,它实际上是直接光使用实时光照,间接光使用lightmap,所有的阴影也都是实时渲染的。shadowmask模式除了烘焙间接光之外,还会烘焙阴影。阴影不存储在lightmap中,而是存储在另外一张名为shadowmask的map中:

在这里插入图片描述
在这里插入图片描述

可以发现,shadowmask看上去是红色的,这是因为Unity将烘焙的阴影信息保存在不同的通道上。这里我们只用了一个平行光源,因此信息保存到了R通道上。如果我们使用两个平行光源,此时shadowmask如图所示:

在这里插入图片描述

shadowmask模式只烘焙静态物体所投射的阴影,动态物体不受影响。我们来看下前向渲染的流程,也是由一个base pass加上其他光源的add pass组成:

在这里插入图片描述
在这里插入图片描述

另外我们可以发现,RenderShadowMap的pass数量大量减少,shadowmask模式不再为static物体渲染实时阴影了。与baked indirect模式类似地,base pass渲染静态物体会同时定义LIGHTMAP_ONSHADOWS_SCREEN这两个宏,这会使得HANDLE_SHADOWS_BLENDING_IN_GI这个宏生效,因此shadowmask模式下我们也需要处理base pass的shadow fade。

我们可以使用unity内置的APIUnitySampleBakedOcclusion对shadowmask进行采样:

// ------------------------------------------------------------------
// Used by the forward rendering path
fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos)
{
    #if defined (SHADOWS_SHADOWMASK)
        #if defined(LIGHTMAP_ON)
            fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
        #else
            fixed4 rawOcclusionMask = fixed4(1.0, 1.0, 1.0, 1.0);
            #if UNITY_LIGHT_PROBE_PROXY_VOLUME
    		   ...
            #else
                rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
            #endif
        #endif
        return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector));
    #else
    ...
    #endif
}

unity_OcclusionMaskSelector是一个四维的向量,用于根据当前是哪个光源提取shadowmask对应的通道值:

在这里插入图片描述
在这里插入图片描述

shadowmask模式中物体接收到的阴影,既可能来自于静态物体,也可能来自于动态物体,而静态物体投射的阴影是烘焙在shadowmask和light probe中,动态物体投射的阴影是实时渲染在shadowmap中的,因此shadowmask模式需要对这两种阴影进行混合。Unity提供了内置的API,UnityMixRealtimeAndBakedShadows来做这件事情:

// ------------------------------------------------------------------
// Used by both the forward and the deferred rendering path
half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
    // -- Static objects --
    // FWD BASE PASS
    // ShadowMask mode          = LIGHTMAP_ON + SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
    // Distance shadowmask mode = LIGHTMAP_ON + SHADOWS_SHADOWMASK
    // Subtractive mode         = LIGHTMAP_ON + LIGHTMAP_SHADOW_MIXING
    // Pure realtime direct lit = LIGHTMAP_ON

    // FWD ADD PASS
    // ShadowMask mode          = SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
    // Distance shadowmask mode = SHADOWS_SHADOWMASK
    // Pure realtime direct lit = LIGHTMAP_ON

    // DEFERRED LIGHTING PASS
    // ShadowMask mode          = LIGHTMAP_ON + SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
    // Distance shadowmask mode = LIGHTMAP_ON + SHADOWS_SHADOWMASK
    // Pure realtime direct lit = LIGHTMAP_ON

    // -- Dynamic objects --
    // FWD BASE PASS + FWD ADD ASS
    // ShadowMask mode          = LIGHTMAP_SHADOW_MIXING
    // Distance shadowmask mode = N/A
    // Subtractive mode         = LIGHTMAP_SHADOW_MIXING (only matter for LPPV. Light probes occlusion being done on CPU)
    // Pure realtime direct lit = N/A

    // DEFERRED LIGHTING PASS
    // ShadowMask mode          = SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING
    // Distance shadowmask mode = SHADOWS_SHADOWMASK
    // Pure realtime direct lit = N/A

    #if !defined(SHADOWS_DEPTH) && !defined(SHADOWS_SCREEN) && !defined(SHADOWS_CUBE)
        #if defined(LIGHTMAP_ON) && defined (LIGHTMAP_SHADOW_MIXING) && !defined (SHADOWS_SHADOWMASK)
            //In subtractive mode when there is no shadow we kill the light contribution as direct as been baked in the lightmap.
            return 0.0;
        #else
            return bakedShadowAttenuation;
        #endif
    #endif

    #if (SHADER_TARGET <= 20) || UNITY_STANDARD_SIMPLE
        //no fading nor blending on SM 2.0 because of instruction count limit.
        #if defined(SHADOWS_SHADOWMASK) || defined(LIGHTMAP_SHADOW_MIXING)
            return min(realtimeShadowAttenuation, bakedShadowAttenuation);
        #else
            return realtimeShadowAttenuation;
        #endif
    #endif

    #if defined(LIGHTMAP_SHADOW_MIXING)
        //Subtractive or shadowmask mode
        realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
        return min(realtimeShadowAttenuation, bakedShadowAttenuation);
    #endif

    //In distance shadowmask or realtime shadow fadeout we lerp toward the baked shadows (bakedShadowAttenuation will be 1 if no baked shadows)
    return lerp(realtimeShadowAttenuation, bakedShadowAttenuation, fade);
}

函数看起来很复杂,不过这里我们只需要考虑shadowmask模式下前向渲染路径的情况。主要可以细分为以下几种类型:

(1)forward base pass下的静态物体

在这里插入图片描述

可以看到此时有LIGHTMAP_ONLIGHTMAP_SHADOW_MIXINGSHADOWS_SCREENSHADOWS_SHADOWMASK这几个宏生效了。LIGHTMAP_ON表明我们需要对lightmap进行采样;SHADOWS_SCREEN表明我们需要用到shadowmap采样实时阴影,这里特指平行光用到的screen space shadowmap;SHADOWS_SHADOWMASK表明我们需要用到shadowmask采样烘焙阴影;由于实时和烘焙的阴影都需要用到,LIGHTMAP_SHADOW_MIXING表明需要对这两种阴影进行混合。综合这些宏再去看上面这个函数,会简化成这样:

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
        return min(realtimeShadowAttenuation, bakedShadowAttenuation);
}

(2)forward base pass下的动态物体

在这里插入图片描述

可以看到此时有LIGHTMAP_SHADOW_MIXINGLIGHTPROBE_SHSHADOWS_SCREEN这几个宏生效。因为是动态物体所以没有用到lightmap和shadowmask采样。LIGHTPROBE_SH表示动态物体接收到了静态物体产生的烘焙阴影,需要通过light probe获得,因此此时LIGHTMAP_SHADOW_MIXING宏也会生效,函数也会走到相同的逻辑上:

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
        return min(realtimeShadowAttenuation, bakedShadowAttenuation);
}

(3)forward base pass下没有实时阴影的静态物体

有些静态物体不会接收到动态物体产生的阴影,进而也不需要对shadowmap采样:

在这里插入图片描述

可以看到此时SHADOWS_SCREEN 宏不生效,那么实际上也不需要进行阴影混合:

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        return bakedShadowAttenuation;
}

(4)forward base pass下没有实时阴影的动态物体

与情形(3)类似,此时也不需要对shadowmap采样,阴影全部来自light probe,因而只有LIGHTPROBE_SH宏生效:

在这里插入图片描述

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        return bakedShadowAttenuation;
}

(5)forward add pass下的静态物体

如前文所述,add pass主要少了对lightmap的采样,add pass不会定义LIGHTMAP_ON宏:

在这里插入图片描述

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
        return min(realtimeShadowAttenuation, bakedShadowAttenuation);
}

(6)forward add pass下的动态物体

add pass也不会对light probe进行处理,因此不会定义LIGHTPROBE_SH宏:

在这里插入图片描述

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
        return min(realtimeShadowAttenuation, bakedShadowAttenuation);
}

(7)forward add pass下没有实时阴影的静态物体

在这里插入图片描述

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        return bakedShadowAttenuation;
}

(8)forward add pass下没有实时阴影的动态物体

在这里插入图片描述

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        return bakedShadowAttenuation;
}

虽然用到了大量不同的宏,但是总结一下就是如果存在需要混合实时阴影和烘焙阴影的情况,无论静态物体还是动态物体,无论是base pass还是add pass,都会走到取min值的逻辑;如果只有烘焙阴影,则直接返回它的值;如果只有实时阴影,则会走到最后lerp插值的逻辑。

对于延迟渲染路径来说,我们需要在geometry pass阶段,单独采样shadowmask然后保存到新的G-Buffer中:

struct FragmentOutput {
	#if defined(DEFERRED_PASS)
		float4 gBuffer0 : SV_Target0;
		float4 gBuffer1 : SV_Target1;
		float4 gBuffer2 : SV_Target2;
		float4 gBuffer3 : SV_Target3;

		#if defined(SHADOWS_SHADOWMASK)
			float4 gBuffer4 : SV_Target4;
		#endif
	#else
		float4 color : SV_Target;
	#endif
};
    
#if defined(DEFERRED_PASS)
    #if defined(SHADOWS_SHADOWMASK)
    	output.gBuffer4 = UnityGetRawBakedOcclusions(shadowUV, i.worldPos.xyz);
    #endif
#endif

UnityGetRawBakedOcclusions也是Unity提供的内置API,专门用于延迟渲染路径:

// ------------------------------------------------------------------
// Used by the deferred rendering path (in the gbuffer pass)
fixed4 UnityGetRawBakedOcclusions(float2 lightmapUV, float3 worldPos)
{
    #if defined (SHADOWS_SHADOWMASK)
        #if defined(LIGHTMAP_ON)
            return UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
        #else
            half4 probeOcclusion = unity_ProbesOcclusion;

            #if UNITY_LIGHT_PROBE_PROXY_VOLUME
                ...
            #endif

            return probeOcclusion;
        #endif
    #else
        return fixed4(1.0, 1.0, 1.0, 1.0);
    #endif
}

静态物体就是直接从shadowmask中采样:

在这里插入图片描述

动态物体返回unity_ProbesOcclusion这个默认值:

在这里插入图片描述

在light pass阶段,需要对刚才保存shadowmask的G-Buffer进行采样,然后进行阴影混合,这与前向渲染路径类似:

half UnityDeferredComputeShadow(float3 vec, float fadeDist, float2 uv)
{

    half fade                      = UnityComputeShadowFade(fadeDist);
    half shadowMaskAttenuation     = UnityDeferredSampleShadowMask(uv);
    half realtimeShadowAttenuation = UnityDeferredSampleRealtimeShadow(fade, vec, uv);

    return UnityMixRealtimeAndBakedShadows(realtimeShadowAttenuation, shadowMaskAttenuation, fade);
}

//Note :
// SHADOWS_SHADOWMASK + LIGHTMAP_SHADOW_MIXING -> ShadowMask mode
// SHADOWS_SHADOWMASK only -> Distance shadowmask mode

// --------------------------------------------------------
half UnityDeferredSampleShadowMask(float2 uv)
{
    half shadowMaskAttenuation = 1.0f;

    #if defined (SHADOWS_SHADOWMASK)
        half4 shadowMask = tex2D(_CameraGBufferTexture4, uv);
        shadowMaskAttenuation = saturate(dot(shadowMask, unity_OcclusionMaskSelector));
    #endif

    return shadowMaskAttenuation;
}

unity_OcclusionMaskSelector变量的含义与前向渲染相同,用来筛选当前光源对应的通道。

在这里插入图片描述

Distance Shadowmask

在distance shadowmask模式中,静态物体投射的阴影会发生变化。在shadow distance范围内,静态物体投射的阴影也变成了实时阴影,只有shadow distance范围外才使用shadowmask。distance shadowmask模式的设置位于Edit/Project Settings/Quality中:

在这里插入图片描述

与shadowmask模式类似,我们依旧可以使用内置APIUnityMixRealtimeAndBakedShadows计算最后的阴影值。由于这里物体接收到的阴影要么全部来自实时阴影,要么全部来自shadowmask/light probe,因此并不存在阴影混合的情况,也即LIGHTMAP_SHADOW_MIXING宏不生效:

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
        #if !defined(SHADOWS_DEPTH) && !defined(SHADOWS_SCREEN) && !defined(SHADOWS_CUBE)
            return bakedShadowAttenuation;
        #endif

        //In distance shadowmask or realtime shadow fadeout we lerp toward the baked shadows (bakedShadowAttenuation will be 1 if no baked shadows)
    	return lerp(realtimeShadowAttenuation, bakedShadowAttenuation, fade);
}

如果在超出shadow distance范围的情况下,会直接返回bakedShadowAttenuation,否则就是根据shadow fade的值做一个线性插值。

Subtractive

最后来看一下subtractive模式。该模式相对来说最简单,渲染效率也最高,它把直接光照,间接光照,阴影信息都烘焙到了一张lightmap中:

在这里插入图片描述

subtractive模式下的前向渲染路径也是由一个forward base pass加上多个光源的add pass组成。此时静态物体投射的阴影全部来自于lightmap,因此没有渲染到shadowmap的过程;并且静态物体的直接光照也被烘焙到了lightmap,所以add pass中也没有渲染静态物体的过程:

在这里插入图片描述
在这里插入图片描述

此时,动态物体投射的阴影来自实时光源,而这个阴影与lightmap混合,还需要从lightmap中减去光照信息,才能得到相对正确的效果,也就是说实际上subtractive模式只支持一个平行光源的情况。由于阴影一部分来自lightmap,一部分来自shadow map,所以LIGHTMAP_SHADOW_MIXING宏开启。

由于直接光照不用实时计算,所以我们需要将其屏蔽掉:

#if defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
	#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK)
		#define SUBTRACTIVE_LIGHTING 1
	#endif
#endif
    
UnityLight CreateLight (Interpolators i) {
	UnityLight light;

	#if SUBTRACTIVE_LIGHTING
		light.dir = float3(0, 1, 0);
		light.color = 0;
	#else
		...
	#endif

	return light;
}

接下来就是要在间接光照的处理中把光照和阴影分开来。首先还是使用UNITY_LIGHT_ATTENUATION来计算光照的衰减值,采样烘焙阴影的逻辑在UnitySampleBakedOcclusion中,但此时已经没有了shadowmask,只会走到以下的逻辑:

// ------------------------------------------------------------------
// Used by the forward rendering path
fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos)
{
        fixed atten = 1.0f;
        return atten;
}

相应地,用于混合实时阴影和烘焙阴影的函数UnityMixRealtimeAndBakedShadows会简化成这样:

half UnityMixRealtimeAndBakedShadows(half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade)
{
    #if !defined(SHADOWS_DEPTH) && !defined(SHADOWS_SCREEN) && !defined(SHADOWS_CUBE)
        #if defined(LIGHTMAP_ON) && defined (LIGHTMAP_SHADOW_MIXING) && !defined (SHADOWS_SHADOWMASK)
            //In subtractive mode when there is no shadow we kill the light contribution as direct as been baked in the lightmap.
            return 0.0;
        #endif
    #endif
    
    #if defined(LIGHTMAP_SHADOW_MIXING)
        //Subtractive or shadowmask mode
        realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
        return min(realtimeShadowAttenuation, bakedShadowAttenuation);
    #endif
}

在subtractive模式下,如果没有实时阴影,则直接返回attenuation为0,它表示阴影信息全部来自lightmap;否则,由于这里bakedShadowAttenuation为1,返回的就是实时阴影的衰减值。

前面提到过,subtractive模式下,光照和阴影都烘焙到lightmap中了:

在这里插入图片描述

但这里烘焙的只是静态物体投射的阴影,我们需要预估动态物体阴影带来的衰减影响,也就是要把这部分从lightmap采样中减去。由于lightmap烘焙的光照只包含diffuse,因此可以使用lambert diffuse计算公式来预估实时光照:

float ndotl = saturate(dot(i.normal, _WorldSpaceLightPos0.xyz));
float3 shadowedLightEstimate = ndotl * (1 - attenuation) * _LightColor0.rgb;

shadowedLightEstimate表示被实时阴影衰减掉的光照预估,而lightmap中只包含了被烘焙阴影衰减掉的光照,因此需要从中减掉:

float3 subtractedLight = indirectLight.diffuse - shadowedLightEstimate;
indirectLight.diffuse = min(subtractedLight, indirectLight.diffuse);

对比效果如下:

(a)未减去光照预估

在这里插入图片描述

(b)减去光照预估

在这里插入图片描述

不过,subtactive模式并不支持延迟渲染路径,就算开了延迟渲染,也会走到forward pass上:

在这里插入图片描述

最后,一张图来看这几种光照模式对静态/动态物体,和它们投射/接收阴影的影响:

在这里插入图片描述

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路
在这里插入图片描述

Reference

[1] Mixed Lighting

[2] 如何理解Unity中的MixedLighting

[3] SHADOWS_SHADOWMASK与LIGHTMAP_SHADOW_MIXING

[4] 浅析Unity中的Enlighten与混合光照

[5] 聊聊LightProbe原理实现以及对LightProbe数据的修改

[6] Unity shader的内置宏与变体(一)

猜你喜欢

转载自blog.csdn.net/weixin_45776473/article/details/123388585