当前位置: 首页 > news >正文

Custom SRP - Shadow Masks

截图展示的是:近处实时阴影,远处烘焙阴影

1 Baking Shadows

阴影让场景更具层次感和真实感,但是实时阴影渲染距离有限,超出阴影距离的世界由于没有阴影显得很“平”.烘焙的阴影不会受限于阴影距离,可以与实时阴影结合解决该问题:

  • 最大阴影距离之内使用实时阴影
  • 最大阴影距离之外用烘焙阴影

1.1 生成烘焙阴影

配置生成 shadow mask

  • Lighting/Mixed Lighting/Lighting Mode: 选择 ShadowMask

  • Project Settings/Quality/Shadows/ShadowMaskMode: 选择 DistanceShadowMask

  • 确保光源的 Mode 是 Mixed,只有该模式才会生成 shadow mask

Lighting/Baked Lightmaps 下的预览,不会显示 shadow mask,但是通过下方的概要信息,可以看到生成的 light map 是带有 shadow mask 的.

通过点击LightingData,在 Asset 窗口定位,可以看到该目录下有对应的 shadow mask 贴图.

Shadow Mask 将 shadow attenuation 存储到 G 通道中,所以看起来是红色的

lightmap

shadow mask map

1.2 判断是否启用 shadow mask

我们需要增加一个 shader keyword 来支持 shadow mask,因此在渲染时,需要知道场景渲染是否需要使用 shadow mask,以此为根据设置 keywords.当场景中存在任意一个光源,渲染了 shadow mask 时,就启用keywords.

  • 在 Shadow.cs 中定义 shader keyword: _SHADOW_MASK_DISTANCE 
  • 并定义 一个 bool 变量,跟踪该状态
  • 如果如果检测到使用了 shadow mask 则设置 keyword
    • 每个光源的 LightMap 信息存储在 LightBakingOutput 中,如果
      • lightMapBakeType = Mixed 并且
      • mixedLightingMode = ShadowMask 则表示要渲染 shadow mask,设置 bool 跟踪状态
// 烘焙阴影的 keywords
private static string[] shadowMaskKeywords = { "_SHADOW_MASK_DISTANCE" };// 跟踪是否使用 shadow mask
private bool useShadowMask = false;public void Setup(ScriptableRenderContext context, CullingResults cullingResults, ShadowSettings settings)
{...// 每次初始化,不使用烘焙阴影useShadowMask = false;
}/// <summary>
/// 缓存下需要绘制阴影的方向光
/// </summary>
/// <param name="light"></param>
/// <param name="visibleLightIndex"></param>
/// <returns></returns>
public Vector3 ReserveDirectionalShadows(Light light, int visibleLightIndex)
{...// 判断是否需要使用 shadow maskif (light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed &&light.bakingOutput.mixedLightingMode == MixedLightingMode.Shadowmask)useShadowMask = true;return new Vector3(light.shadowStrength,settings.directional.cascadeCount * directionalLightCount++,light.shadowNormalBias);}return Vector3.zero;
}public void Render()
{...// 不论是否渲染实时阴影,都要根据跟踪的状态启用 keyword,因此烘焙阴影不是实时的buffer.BeginSample(bufferName);SetKeywords(shadowMaskKeywords, useShadowMask ? 1 : 0);buffer.EndSample(bufferName);ExecuteBuffer();
}

在 shadow.hlsl 中定义相应的 shader keyword

#pragma multi_compile _ _SHADOW_MASK_DISTANCE
#pragma multi_compile _ LIGHTMAP_ON

1.3 采样 Shadow Mask

shadow mask 数据与 lightmap 一样采样得到,但是它毕竟是 shadow 数据,为了逻辑上的一致性,在 shadows.hlsl 中定义结构体 ShadowMask 来存储数据,并将其添加到 ShadowData 中,并在返回时进行初始化

struct ShadowMask
{bool distance;float4 shadows;
};struct ShadowData
{int cascadeIndex; // 级联索引,0-3float cascadeBlend; // 级联混合,用于在级联之间平滑过渡float strength; // 阴影强度,0表示没有阴影,1表示完全有阴影ShadowMask shadowMask;
};ShadowData GetShadowData(Surface surfaceWS)
{ShadowData shadowData;// 初始化 shadow mask 数据shadowData.shadowMask.distance = false;shadowData.shadowMask.shadows = 1.0;...
}

真正采样 shadow mask 上 GI 来完成的,因此在 GI.hlsl 中的 GI 结构体中,也增加 ShadowMask 成员.

unity 定义了 unity_ShadowMask 来提供采样贴图,我们只需要声明贴图及对应的采样器,然后定义一个采样函数来完成采样.

TEXTURE2D(unity_ShadowMask);
SAMPLER(samplerunity_ShadowMask);struct GI
{float3 diffuse;ShadowMask shadowMask;
};// 采样 shadow mask
float3 SampleBakedShadow(float2 uv)
{// 只有定义了 lightmap, unity_ShadowMask 才是可用的
#if defined(LIGHTMAP_ON)return SAMPLE_TEXTURE2D(unity_ShadowMask, samplerunity_ShadowMask, uv);// 否则直接返回 1
#elsereturn 1.0;
#endif
}GI GetGI (float2 lightMapUV, Surface surfaceWS) 
{GI gi;gi.diffuse = SampleLightMap(lightMapUV) + SampleLightProbe(surfaceWS);// 启用了 shadow mask distance 才采样
#if defined(_SHADOW_MASK_DISTANCE)gi.shadowMask.distance = true;gi.shadowMask.shadows = SampleBakedShadow(lightMapUV);
#elsegi.shadowMask.shadows = 1.0;gi.shadowMask.distance = false;
#endifreturn gi;
}

在 Lighting.hlsl 中,将 GI 中的 shadow mask 数据传递给 shadow data,以供后续的光照计算使用.

这里我们先直接返回 shadow mask 的颜色,在场景里看数据

float3 GetLighting(Surface surfaceWS, BRDF brdf, GI gi)
{ShadowData shadowData = GetShadowData(surfaceWS);shadowData.shadowMask = gi.shadowMask;// 临时返回以查看数据return gi.shadowMask.shadows.rgb;
}

同 lightmap, light probe 等 GI 数据一样,需要在 DrawingSettings 中配置,通知 unity 准备并向 GPU 提交这些数据.在 CameraRenderer.cs 中:

void DrawVisibleGeometry(bool useDynamicBatching, bool useGPUInstancing)
{// 渲染不透明物体var sortingSettings = new SortingSettings(camera){ criteria = SortingCriteria.CommonOpaque };var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings){ enableDynamicBatching = useDynamicBatching, enableInstancing = useGPUInstancing};// 索引是 1,因为索引为 0 的通过构造函数将 unlitShaderTagId 设置了drawingSettings.SetShaderPassName(1, litShaderTagId);drawingSettings.perObjectData = PerObjectData.Lightmaps | PerObjectData.LightProbe| PerObjectData.LightProbeProxyVolume| PerObjectData.ShadowMask;...

可以看到效果:

1.4 处理 Occlusion probes

unity 中动态物体 ShadowMask 需要用到 Light Probe.通过变量 unity_ProbesOcclusion 提供

  • 在 UnityInput.hlsl 中定义常量
    real4 unity_WorldTransformParams;
    float4 unity_ProbesOcclusion;   // 动态物体 shadow mask 数据
  • 如果对象没有用 lightmap,则直接返回 unity_ProbesOcclusion. 该数据在 shadow mask 中,其中红色通道中是 shadow,其它通道是 occlusion probe 数据
    // 采样 shadow mask
    float3 SampleBakedShadow(float2 uv)
    {// 只有定义了 lightmap, unity_ShadowMask 才是可用的
    #if defined(LIGHTMAP_ON)return SAMPLE_TEXTURE2D(unity_ShadowMask, samplerunity_ShadowMask, uv);// 否则返回 occlusion probe 数据
    #elsereturn unity_ProbesOcclusion;
    #endif
    }
  • 注意,unity_ProbesOcclusion 会导致 UnityInstancing 被打断,为了支持 instance,需要在 common.hlsl 中定义 SHADOWS_SHADOWMASK.需要在包含 UnityInstancing.hlsl 之前
    #if defined(_SHADOW_MASK_DISTANCE)#define SHADOWS_SHADOWMASK
    #endif#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
  • cs 代码中,DrawingSettings.PerObjectData 配置 OcclusionProbes
    drawingSettings.perObjectData = PerObjectData.Lightmaps | PerObjectData.LightProbe| PerObjectData.LightProbeProxyVolume| PerObjectData.ShadowMask| PerObjectData.OcclusionProbe;

    效果如图:阴影内的动态物体是青色,光照到的物体时白色

1.5 LPPVs

LPPV 也支持 shadow mask

  • DrawingSettings.PerObjectData 配置 OcclusionProbeProxyVolume
    drawingSettings.perObjectData = PerObjectData.Lightmaps | PerObjectData.LightProbe| PerObjectData.LightProbeProxyVolume| PerObjectData.ShadowMask| PerObjectData.OcclusionProbe| PerObjectData.OcclusionProbeProxyVolume;;
  • 跟lightmap 一样,当 unity_ProbeVolumeParams.x != 0 时,表示需要采样 shadow mask,用接口 SampleProbeOcclusion 进行采样,参数与 SampleProbeVolumeSH4 一样,只是不需要法线.
// 采样 shadow mask
float3 SampleBakedShadow(float2 uv, Surface surfaceWS)
{// 只有定义了 lightmap, unity_ShadowMask 才是可用的
#if defined(LIGHTMAP_ON)return SAMPLE_TEXTURE2D(unity_ShadowMask, samplerunity_ShadowMask, uv);
#else// unity_ProbeVolumeParams.x !=0 表示用 LPPVif(unity_ProbeVolumeParams.x)return SampleProbeOcclusion(TEXTURE3D_ARGS(unity_ProbeVolumeSH, samplerunity_ProbeVolumeSH),surfaceWS.position, unity_ProbeVolumeWorldToObject,unity_ProbeVolumeParams.y, unity_ProbeVolumeParams.z,unity_ProbeVolumeMin.xyz, unity_ProbeVolumeSizeInv.xyz);// 否则返回 occlusion probe 数据else return unity_ProbesOcclusion;
#endif
}
  • 修改调用 GetGI 的代码,传入 surface 参数
#if defined(_SHADOW_MASK_DISTANCE)gi.shadowMask.distance = true;gi.shadowMask.shadows = float4(SampleBakedShadow(lightMapUV, surfaceWS), 1.0);
#elsegi.shadowMask.shadows = 1.0;gi.shadowMask.distance = false;
#endif

如下图,车库中的长条对象,在阴影中的部分渲染成了青色

最后,移除 lighting.hlsl 中 GetLighting 函数中返回 gi shadow 的调试代码.

1.6 Mesh Balls

DrawInstanced ,在CPU中计算每个对象的 Occlusion Probe 数据,并拷贝到 MaterialPropertyBlock 完成提交.

// 生成 interpolated light probes
var lightProbes = new SphericalHarmonicsL2[1023];
var occlusionProbes = new Vector4[1023];
// 参数1: 位置熟知
// 参数2: 返回 SphericalHarmonicsL2 数据
// 参数3: 返回 Occlusion Probe 数据
LightProbes.CalculateInterpolatedLightAndOcclusionProbes(positions, lightProbes, occlusionProbes);
// 将数据拷贝到 material property block
matPropBlock.CopySHCoefficientArraysFrom(lightProbes); 
matPropBlock.CopyProbeOcclusionArrayFrom(occlusionProbes);

2 Mixing Shadows

这一步,我们要混合 realtime 和 baked shadow,超出实时阴影距离的,用烘焙阴影.

2.1 混合

在 shadow.hlsl 中,首先将计算级联阴影的代码从 GetDirShadowAttenuation 中分离出来,形式单独的 GetCascadedShadow 函数

然后定义获取烘焙阴影的函数,如果启用了 distance shandow,返回 ShadowMask.shadows.r

定义MixBakedAndRealtimeShadow函数混合阴影


// 获取级联阴影
float GetCascadedShadow(DirShadowData shadowData, ShadowData global, Surface surfaceWS)
{// 根据像素法线和图素对角线长度,计算偏移float3 normalBias = surfaceWS.normal * shadowData.normalBias * _CascadeData[global.cascadeIndex].y;// 计算采样阴影图的 UVfloat3 positionSTS = mul(_DirShadowMatrices[shadowData.tileIndex], float4(surfaceWS.position+normalBias,1.0)).xyz;// 采样阴影图float shadow = FilterDirectionalShadow(positionSTS);// 如果有级联混合,则需要跟下一级级联进行混合if (global.cascadeBlend < 1.0f){normalBias = surfaceWS.normal * shadowData.normalBias * _CascadeData[global.cascadeIndex + 1].y;positionSTS = mul(_DirShadowMatrices[shadowData.tileIndex + 1], float4(surfaceWS.position + normalBias, 1.0)).xyz;float nextShadow = FilterDirectionalShadow(positionSTS);shadow = lerp(shadow, nextShadow, global.cascadeBlend);}return shadow;
}// 获取 shadow mask 阴影衰减
float GetBakedShadow(ShadowMask shadowMask)
{float shadow = 1.0f;// 启用了 distance shadow mask,返回 r 通道中的阴影if(shadowMask.distance)return shadowMask.shadows.r;return shadow;
}// 混合烘焙和实时阴影
// global 全局阴影参数
// shadow 实时阴影
// strength 光源的阴影强度
float MixBakedAndRealtimeShadow(ShadowData global, float shadow, float strength)
{float baked = GetBakedShadow(global.shadowMask);// shadow mask 有效if(global.shadowMask.distance){// 混合实时和烘焙阴影shadow = lerp(baked, shadow, global.strength);// 应用光源阴影强度进行插值return lerp(1.0f, shadow, strength);}// 直接基于实时阴影和光源阴影强度进行插值else{return lerp(1.0f, shadow, strength*global.strength);}
}// 计算方向光阴影衰减
// dirShadowData 方向光阴影参数
// global 全局阴影参数
// surfaceWS 世界空间表面/像素
float GetDirShadowAttenuation(DirShadowData dirShadowData, ShadowData global, Surface surfaceWS)
{// 材质不接收阴影
#if !defined(_RECEIVE_SHADOWS)return 1.0f;
#endif// 方向光阴影强度为 0if(dirShadowData.strength <= 0.0f)return 1.0f;  else{// 计算采样实时阴影float shadow = GetCascadedShadow(dirShadowData, global, surfaceWS);// 混合实时阴影和烘焙阴影shadow = MixBakedAndRealtimeShadow(global, shadow, dirShadowData.strength);return shadow; }
}

之前在 Light.hlsl 中的 GetDirectionalLightShadowData 函数中,已经应用了最大阴影距离强度来进行淡出,现在是在 MixBakedAndRealtimeShadow 中完成的淡出,因此之前的要删掉

data.strength =_DirectionalLightShadowData[lightIndex].x; // * shadowData.strength;

2.2 仅烘焙阴影

现在,如果将摄像机拉的足够远,会发现实时阴影和烘焙阴影都消失了,这是因为在管线中,收集投影的光源时,如果光源的 cascade 没有裁剪到任何对象,光源就被裁掉了.

要解决该问题,需要修改光源裁减算法,改为只要该光源渲染了 shadow mask,那么即使光源阴影投射范围内没有对象,也收集该光源

同时需要在 shader 中区别出该情况,并仅使用烘焙阴影

在 shadow.hlsl 中

// 当光源完全没有投射(渲染) shadow map 时,仅使用 shadow mask 阴影
float GetBakedShadow(ShadowMask shadowMask, float strength)
{// distance shadow mask 有效if(shadowMask.distance)return lerp(1.0f, shadowMask.shadows.r, strength);return 1.0f;
}// 计算方向光阴影衰减
// dirShadowData 方向光阴影参数
// global 全局阴影参数
// surfaceWS 世界空间表面/像素
float GetDirShadowAttenuation(DirShadowData dirShadowData, ShadowData global, Surface surfaceWS)
{// 材质不接收阴影
#if !defined(_RECEIVE_SHADOWS)return 1.0f;
#endif// 方向光阴影强度为 0,不投射阴影,返回1// 管线中,收集投影方向光时,已经判断了如果强度为0则忽略,因此这里的判断不是很有必要if(dirShadowData.strength == 0.0f){return 1.0f;  }// 我们在管线中,如果方向光没有实时阴影,仅有烘焙阴影,则设置强度为负数// 这里识别并用绝对值获得烘焙阴影,不在采样级联阴影else if(dirShadowData.strength * global.strength <= 0.0f){return GetBakedShadow(global.shadowMask, abs(dirShadowData.strength));}else{// 计算采样实时阴影float shadow = GetCascadedShadow(dirShadowData, global, surfaceWS);// 混合实时阴影和烘焙阴影shadow = MixBakedAndRealtimeShadow(global, shadow, dirShadowData.strength);return shadow; }
}

在 shadow.cs 中

public Vector3 ReserveDirectionalShadows(Light light, int visibleLightIndex)
{if (directionalLightCount < maxdirectionalLightCount &&light.shadows != LightShadows.None &&light.shadowStrength > 0f){directionalLights[directionalLightCount] = new ShadowedDirectionalLight(){visibleLightIndex = visibleLightIndex,slopeScaleBias = light.shadowBias,nearPlaneOffset = light.shadowNearPlane};// 判断是否需要使用 shadow maskif (light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed &&light.bakingOutput.mixedLightingMode == MixedLightingMode.Shadowmask)useShadowMask = true;// 如果该光源投影范围内没有任何对象,则仅使用烘焙阴影.// 我们在返回值的 x 分量用负数的阴影强度,shader 中识别后,则仅使用烘焙阴影if (!cullingResults.GetShadowCasterBounds(visibleLightIndex, out Bounds b)){return new Vector3(-light.shadowStrength, 0f, 0f);}return new Vector3(light.shadowStrength,settings.directional.cascadeCount * directionalLightCount++,light.shadowNormalBias);}return Vector3.zero;
}

2.3 总是使用 shadow mask

shadow mask 是离线烘焙的,运行时不需要再进行阴影的渲染,因此性能提升明显.所以有些情况下,我们希望对于静态对象,完全抛弃实时阴影,仅使用 shadow mask.

  • 在 ProjectSettings/Quality/Shadows/Shadowmask Mode 配置,改为 Shadowmask 启用该特性
  • 需要加入新的 shader keyword :_SHADOW_MASK_ALWAYS,
// shadow.cs 中
private static string[] shadowMaskKeywords = { "_SHADOW_MASK_ALWAYS", "_SHADOW_MASK_DISTANCE" };// Lit.shader 中
#pragma multi_compile _ _SHADOW_MASK_ALWAYS _SHADOW_MASK_DISTANCE// common.hlsl 中,增加 always 模式对 instancing 的支持
// 只有定义了 SHADOWS_SHADOWMASK unity 才会对 shadow mask 材质兼容 instancing
#if defined(_SHADOW_MASK_DISTANCE) || defined(_SHADOW_MASK_ALWAYS)#define SHADOWS_SHADOWMASK
#endif
  • 渲染管线中,根据是否启用 shadow mask 以及 QualitySetting.shadowmaskMode 来设置 keyword
public void Render()
{...// 不论是否渲染实时阴影,都要根据跟踪的状态启用 keyword,因此烘焙阴影不是实时的buffer.BeginSample(bufferName);// 确定 shadow mask keywordint shadowMaskKeywordIndex = 0;if (useShadowMask){// 如果配置了 shadow maskif (QualitySettings.shadowmaskMode == ShadowmaskMode.Shadowmask)shadowMaskKeywordIndex = 1;// 否则就是 distance shadow maskelseshadowMaskKeywordIndex = 2;}SetKeywords(shadowMaskKeywords, shadowMaskKeywordIndex);buffer.EndSample(bufferName);ExecuteBuffer();
}
  • shadow.hlsl 中,为ShadowMask 数据增加 always 标记,在 IG.hlsl 中的 GetGI 函数中,根据 keyword 为其赋值
GI GetGI (float2 lightMapUV, Surface surfaceWS) 
{GI gi;gi.diffuse = SampleLightMap(lightMapUV) + SampleLightProbe(surfaceWS);// 启用了 shadow mask distance 才采样
#if defined(_SHADOW_MASK_DISTANCE)gi.shadowMask.distance = true;gi.shadowMask.always = false;gi.shadowMask.shadows = float4(SampleBakedShadow(lightMapUV, surfaceWS), 1.0);
#else ifdefined(_SHADOW_MASK_ALWAYS)gi.shadowMask.distance = false;gi.shadowMask.always = true;gi.shadowMask.shadows = float4(SampleBakedShadow(lightMapUV, surfaceWS), 1.0);
#elsegi.shadowMask.shadows = 1.0;gi.shadowMask.distance = false;gi.shadowMask.always = false;
#endifreturn gi;
}
  • 混合实时/烘焙阴影时,处理 always 的情况
// 获取 shadow mask 阴影衰减
float GetBakedShadow(ShadowMask shadowMask)
{float shadow = 1.0f;// 启用了 distance shadow mask,返回 r 通道中的阴影if(shadowMask.distance || shadowMask.always)return shadowMask.shadows.r;return shadow;
}// 当光源完全没有投射(渲染) shadow map 时,仅使用 shadow mask 阴影
float GetBakedShadow(ShadowMask shadowMask, float strength)
{// distance shadow mask 有效if(shadowMask.distance || shadowMask.always)return lerp(1.0f, shadowMask.shadows.r, strength);return 1.0f;
}// 混合烘焙和实时阴影
// global 全局阴影参数
// shadow 实时阴影
// strength 光源的阴影强度
float MixBakedAndRealtimeShadow(ShadowData global, float shadow, float strength)
{float baked = GetBakedShadow(global.shadowMask);// distance shadow mask 有效if(global.shadowMask.distance){// 混合实时和烘焙阴影shadow = lerp(baked, shadow, global.strength);// 应用光源阴影强度进行插值return lerp(1.0f, shadow, strength);}// always shadow maskelse if(global.shadowMask.always){// 根据最大阴影距离衰减阴影shadow = lerp(1.0f, shadow, global.strength);// 使用实时阴影和烘焙阴影中较小的值shadow = min(shadow, baked);// 应用光源阴影强度进行插值return lerp(1.0f, shadow, strength);}// 直接基于实时阴影和光源阴影强度进行插值else{return lerp(1.0f, shadow, strength*global.strength);}
}

3 支持多个光源

shadow mask map 有4个通道,因此最多支持4个 mixed lights.Unity 会根据“重要程度”对场景中的光源进行排序,并选取前4个分别烘焙到 shadow mask 的 r g b a 通道中.超过4个光源,会被当作 full baked 模式来进行处理.目前我们仅支持方向光.

增加一个方向光,并调整其强度比另一个方向光小一点,Unity 会为我们正确烘焙多光源 shadow mask.红色表示首第一个光源影响,绿色表示受第二个光源影响,黄色表示同时受到两个光源的影响.

前面的代码中值使用了 r 通道,第二个光源要使用 g 通道,这要求我们提供光源在 shadow mask map 中的索引,基于该索引选择通道.我们将利用方向光阴影数据的 w 通道来向 shader 提交数据

  • 在Light.cs 中,获取通道索引
public Vector4 ReserveDirectionalShadows(Light light, int visibleLightIndex)
{if (directionalLightCount < maxdirectionalLightCount &&light.shadows != LightShadows.None &&light.shadowStrength > 0f){directionalLights[directionalLightCount] = new ShadowedDirectionalLight(){visibleLightIndex = visibleLightIndex,slopeScaleBias = light.shadowBias,nearPlaneOffset = light.shadowNearPlane};float shadowMaskChannel = -1.0f;// 判断是否需要使用 shadow maskif (light.bakingOutput.lightmapBakeType == LightmapBakeType.Mixed &&light.bakingOutput.mixedLightingMode == MixedLightingMode.Shadowmask){useShadowMask = true;shadowMaskChannel = light.bakingOutput.occlusionMaskChannel;}// 如果该光源投影范围内没有任何对象,则仅使用烘焙阴影.// 我们在返回值的 x 分量用负数的阴影强度,shader 中识别后,则仅使用烘焙阴影if (!cullingResults.GetShadowCasterBounds(visibleLightIndex, out Bounds b)){return new Vector4(-light.shadowStrength, 0f, 0f, shadowMaskChannel);}return new Vector4(light.shadowStrength,settings.directional.cascadeCount * directionalLightCount++,light.shadowNormalBias, shadowMaskChannel);}return Vector4.zero;
}
  • 在shadow.hlsl 中,为方向光阴影数据增加 通道索引成员
struct DirShadowData
{float strength;       // 该光源的阴影强度int tileIndex;        // 在 cascaded shadow map 中的初始 tile 的索引float normalBias;      // 法线偏移,用于阴影偏移int shadowMaskChannel; // 在 shadow mask map 中的通道索引
}; 
  • 在 Light.hlsl 中,填充该数据
DirShadowData GetDirectionalLightShadowData(int index, ShadowData shadowData)
{DirShadowData dirShadowData;// 应用级联阴影强度默认为1,像素超出级联范围时强度为0dirShadowData.strength = _DirLightShadowData[index].x;// y 是该光源的第一个 tile 的索引, + shadowData.cascadeIndex 表示该光源的第几个级联阴影 tiledirShadowData.tileIndex = _DirLightShadowData[index].y + shadowData.cascadeIndex;dirShadowData.normalBias = _DirLightShadowData[index].z; // 法线偏移dirShadowData.shadowMaskChannel = s_DirLightShadowData[index].w;return dirShadowData;
}
  • shadow.hlsl 中,获取 shadow mask 时,根据索引返回对应通道数据
// 获取 shadow mask 阴影衰减
float GetBakedShadow(ShadowMask shadowMask, int channel)
{float shadow = 1.0f;// 启用了 distance shadow mask,返回 r 通道中的阴影if(shadowMask.distance || shadowMask.always){if(channel >= 0 && channel < 4)return shadowMask.shadows[channel];}return shadow;
}// 当光源完全没有投射(渲染) shadow map 时,仅使用 shadow mask 阴影
float GetBakedShadow(ShadowMask shadowMask, float strength, int channel)
{// distance shadow mask 有效if(shadowMask.distance || shadowMask.always){if(channel >= 0 && channel < 4)return lerp(1.0f, shadowMask.shadows[channel], strength);}return 1.0f;
}
  • 最后,正确调用 GetBakedShadow

http://www.dtcms.com/a/354085.html

相关文章:

  • Axure:如何将SVG转换为形状
  • leetcode 155 官方golang标准答案错误
  • Java Lambda 处理日期时间 根据区间找出区间内集合
  • Linux程序与进程:核心概念与管理全解析
  • Class45循环神经网络RNN
  • “互联网 +”时代下开源 AI 大模型 AI 智能名片 S2B2C 商城小程序:行业变革与未来展望
  • 基于 Ultralytics YOLO11与 TrackZone 的驱动的高效区域目标跟踪方案实践
  • Python Imaging Library (PIL) 全面指南:PIL基础入门-Python图像处理实战
  • 多版本兼容的golang客服系统
  • 稀土:从“稀有”到“命脉”的科技核心
  • 通过概率正 - 未标记网络从医学图像的特定感兴趣区域中学习|文献速递-深度学习人工智能医疗图像
  • 【底层机制】thread_local 变量的初始化时机和生命周期
  • Spring Retry Spring 生态系统优雅的重试组件
  • 浏览器网页路径扫描器(脚本)
  • SQL优化:SQL模拟Split二维数组
  • Linux 基础开发工具
  • django-redis 使用类实现和使用
  • React(面试)
  • JUC之异步编程理论总结
  • 实现基于数据库 flag 状态的消息消费控制
  • 【docker】P1 虚拟化与容器化
  • 全球协作无障碍:cpolar+Nextcloud实现跨国文件共享
  • 通过远程桌面横向移动(破解凭证)
  • 【51单片机】【protues仿真】 基于51单片机出租车计价器系统
  • 三轴云台之动态性能篇
  • 数字化时代催生变革,楼宇自控系统成为建筑管理新潮流的引领者
  • ESP32S3:开发环境搭建、VSCODE 单步调试、Systemview 分析任务运行情况
  • 北斗导航|接收机自主完好性监测算法综述
  • 【C++】类和对象 --- 类中的6个默认成员函数
  • CAS 浅析