56 KiB
Raw Permalink Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
Shadow 2024-12-17 16:57:24

阴影

阴影类型

  • Static Shadow
  • Cascading Shadow Map
  • Per Object Shadow:可移动组件使用的逐物体阴影应用阴影图到物体的包围盒,因此包围盒必须是精确的。对于骨骼网格,这意味着它们应该有一个物理资产。对于粒子系统,任何固定的边界框必须足够大,以容纳所有的粒子。在网格的Lighting属性组中Dynamic Inset Shadow可以开启逐物体阴影对需要高质量高精度的物体非常有用。
  • Dynamic Shadow:可移动光源在所有物体上投射出完全动态的阴影(和光)。这种光源的任何数据不会被烘焙到光照图中,它可以自由地在所有东西上投射动态阴影。静态网格、骨架网格、粒子效果等等将完全从可移动光源投射和接收动态阴影。
  • Capsule Shadow
  • Contact Shadow
  • Distance Field Shadow

相关类型

  • ShadowRendering.h FProjectedShadowInfo:存储投影阴影先关信息。包含各种变换矩阵、阴影渲染函数以及渲染参数、灯光&场景&图元信息。
  • LightSceneInfo.h FLightSceneInfo渲染一盏灯所用到的信息。可以看做是游戏线程中ULightComponent的镜像。
  • SceneCore.h FLightPrimitiveInteraction存储LightSceneInfo、PrimitiveSceneInfo等信息。
  • SceneRendering.h FVisibleLightInfo可见光源信息, 主要是阴影相关的信息. FVisibleLightViewInfo

相关Paas

  1. ShadowDepths => RenderShadowDepthMaps()
  2. Lights => RenderLights()
    1. DirectLighting
      1. UnbatchedLights
        1. ShadowProjectionOnOpaque

InitDynamicShadows

InitDynamicShadows() => CreateDynamicShadows()

InitViews() => FSceneRenderer::InitDynamicShadows() PS. UE5.3中相关逻辑移动到CreateDynamicShadows() 中了。InitDynamicShadows() => BeginInitDynamicShadows() => BeginGatherShadowPrimitives() => CreateDynamicShadows()

计算各种灯光类型,之后调用:

  • CreateWholeSceneProjectedShadow()
  • AddViewDependentWholeSceneShadowsForView()
  • SetupInteractionShadows()
    • CreatePerObjectProjectedShadow()
  • InitProjectedShadowVisibility()

丛越文章中的注释:

void FSceneRenderer::InitDynamicShadows(FRHICommandListImmediate& RHICmdList, FGlobalDynamicIndexBuffer& DynamicIndexBuffer, FGlobalDynamicVertexBuffer& DynamicVertexBuffer, FGlobalDynamicReadBuffer& DynamicReadBuffer)
{
    // 初始化各类标记和数量.
    const bool bMobile = FeatureLevel < ERHIFeatureLevel::SM5;
    bool bStaticSceneOnly = false;
    for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    {
        FViewInfo& View = Views[ViewIndex];
        bStaticSceneOnly = bStaticSceneOnly || View.bStaticSceneOnly;
    }

    const bool bProjectEnablePointLightShadows = Scene->ReadOnlyCVARCache.bEnablePointLightShadows;
    uint32 NumPointShadowCachesUpdatedThisFrame = 0;
    uint32 NumSpotShadowCachesUpdatedThisFrame = 0;

    // 预计算阴影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> PreShadows;
    // 视图关联的全景阴影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadows;
    // 视图关联的需要裁剪的全景阴影.
    TArray<FProjectedShadowInfo*,SceneRenderingAllocator> ViewDependentWholeSceneShadowsThatNeedCulling;
    {
        // 遍历所有光源, 将不同类型的光源加入不同类型的待渲染的阴影列表中.
        for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights); LightIt; ++LightIt)
        {
            const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
            FLightSceneInfo* LightSceneInfo = LightSceneInfoCompact.LightSceneInfo;

            FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());

            FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];

            // LightOcclusionType有阴影图和光追两种, 如果非阴影图类型, 则忽略.
            const FLightOcclusionType OcclusionType = GetLightOcclusionType(LightSceneInfoCompact);
            if (OcclusionType != FLightOcclusionType::Shadowmap)
                continue;

            // 如果光源没有开启阴影或阴影质量太小, 则忽略阴影图.
            if ((LightSceneInfoCompact.bCastStaticShadow || LightSceneInfoCompact.bCastDynamicShadow) && GetShadowQuality() > 0)
            {
                // 检测该光源是否在某个view里可见, 如果不可见, 则忽略.
                bool bIsVisibleInAnyView = false;
                for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
                {
                    bIsVisibleInAnyView = LightSceneInfo->ShouldRenderLight(Views[ViewIndex]);

                    if (bIsVisibleInAnyView) 
                    {
                        break;
                    }
                }

                // 所有裁剪条件都通过了, 处理光源的阴影.
                if (bIsVisibleInAnyView)
                {
                    // 初始化阴影的各种标记和变量.
                    static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
                    const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);
                    // 是否点光源阴影. 注意矩形光也当做点光源处理.
                    const bool bPointLightShadow = LightSceneInfoCompact.LightType == LightType_Point || LightSceneInfoCompact.LightType == LightType_Rect;

                    // 对不预计算阴影的移动光源只创建全景阴影(whole scene shadow).
                    const bool bShouldCreateShadowForMovableLight = 
                        LightSceneInfoCompact.bCastDynamicShadow
                        && (!LightSceneInfo->Proxy->HasStaticShadowing() || !bAllowStaticLighting);

                    const bool bCreateShadowForMovableLight = 
                        bShouldCreateShadowForMovableLight
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 对带有预计算阴影的尚未构建的光源创建全景阴影.
                    const bool bShouldCreateShadowToPreviewStaticLight =
                        LightSceneInfo->Proxy->HasStaticShadowing()
                        && LightSceneInfoCompact.bCastStaticShadow
                        && !LightSceneInfo->IsPrecomputedLightingValid();                        

                    const bool bCreateShadowToPreviewStaticLight = 
                        bShouldCreateShadowToPreviewStaticLight                        
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 对需要静态阴影但由于重叠导致没有有效阴影图的光源创建全景阴影.
                    const bool bShouldCreateShadowForOverflowStaticShadowing =
                        LightSceneInfo->Proxy->HasStaticShadowing()
                        && !LightSceneInfo->Proxy->HasStaticLighting()
                        && LightSceneInfoCompact.bCastStaticShadow
                        && LightSceneInfo->IsPrecomputedLightingValid()
                        && LightSceneInfo->Proxy->GetShadowMapChannel() == INDEX_NONE;

                    const bool bCreateShadowForOverflowStaticShadowing =
                        bShouldCreateShadowForOverflowStaticShadowing
                        && (!bPointLightShadow || bProjectEnablePointLightShadows);

                    // 添加点光源的全景阴影.
                    const bool bPointLightWholeSceneShadow = (bShouldCreateShadowForMovableLight || bShouldCreateShadowForOverflowStaticShadowing || bShouldCreateShadowToPreviewStaticLight) && bPointLightShadow;
                    if (bPointLightWholeSceneShadow)
                    {                        
                        UsedWholeScenePointLightNames.Add(LightSceneInfoCompact.LightSceneInfo->Proxy->GetComponentName());
                    }

                    // 创建光源的全景阴影.
                    if (bCreateShadowForMovableLight || bCreateShadowToPreviewStaticLight || bCreateShadowForOverflowStaticShadowing)
                    {
                        CreateWholeSceneProjectedShadow(LightSceneInfo, NumPointShadowCachesUpdatedThisFrame, NumSpotShadowCachesUpdatedThisFrame);
                    }

                    // 允许移动和固定的光源创建CSM(级联阴影), 或者是尚未构建的静态光源.
                    if ((!LightSceneInfo->Proxy->HasStaticLighting() && LightSceneInfoCompact.bCastDynamicShadow) || bCreateShadowToPreviewStaticLight)
                    {
                        // 增加视图关联的全景阴影.
                        if( !bMobile ||
                            ((LightSceneInfo->Proxy->UseCSMForDynamicObjects() || LightSceneInfo->Proxy->IsMovable()) 
                                && (LightSceneInfo == Scene->MobileDirectionalLights[0] || LightSceneInfo == Scene->MobileDirectionalLights[1] || LightSceneInfo == Scene->MobileDirectionalLights[2])))
                        {
                            AddViewDependentWholeSceneShadowsForView(ViewDependentWholeSceneShadows, ViewDependentWholeSceneShadowsThatNeedCulling, VisibleLightInfo, *LightSceneInfo);
                        }

                        // 处理交互阴影, 此处的交互是指光源和图元之间的影响. 包含PerObject阴影、透明阴影、自阴影等. 
                        if( !bMobile || (LightSceneInfo->Proxy->CastsModulatedShadows() && !LightSceneInfo->Proxy->UseCSMForDynamicObjects()))
                        {
                            Scene->FlushAsyncLightPrimitiveInteractionCreation();

                            // 处理动态图元的交互阴影.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionOftenMovingPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
                            {
                                SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
                            }

                            // 处理静态图元的交互阴影.
                            for (FLightPrimitiveInteraction* Interaction = LightSceneInfo->GetDynamicInteractionStaticPrimitiveList(false); Interaction; Interaction = Interaction->GetNextPrimitive())
                            {
                                SetupInteractionShadows(RHICmdList, Interaction, VisibleLightInfo, bStaticSceneOnly, ViewDependentWholeSceneShadows, PreShadows);
                            }
                        }
                    }
                }
            }
        }

        // 计算投射阴影的可见性.
        InitProjectedShadowVisibility(RHICmdList);
    }

    // 清理旧的预计算阴影, 尝试增加新的到缓存中.
    UpdatePreshadowCache(FSceneRenderTargets::Get(RHICmdList));

    // 收集图元列表, 以绘制不同类型的阴影.
    GatherShadowPrimitives(PreShadows, ViewDependentWholeSceneShadowsThatNeedCulling, bStaticSceneOnly);

    // 分配阴影深度渲染纹理.
    AllocateShadowDepthTargets(RHICmdList);

    // 收集阴影的动态网格元素, 跟之前剖析的GatherDynamicMeshElements类似.
    GatherShadowDynamicMeshElements(DynamicIndexBuffer, DynamicVertexBuffer, DynamicReadBuffer);
}

ShadowDepths

渲染阴影深度贴图与图集。

  • FSceneRenderer::RenderShadowDepthMaps()位于CustomDepth之前。
    • RenderVirtualShadowMaps()
    • RenderShadowDepthMapAtlases()
    • SortedShadowsForShadowDepthPass.ShadowMapCubemaps循环。渲染点光源阴影立方体图
      • FProjectedShadowInfo::RenderDepth()
      • FProjectedShadowInfo::RenderTranslucencyDepths

RenderShadowDepthMapAtlases() 渲染的图集位于FSceneRenderer::SortedShadowsForShadowDepthPass.ShadowMapAtlases (ShadowMapAtlases.RenderTargets.DepthTarget为深度ShadowMapAtlases.Shadows 为FProjectedShadowInfo)

RenderDepth

MeshDrawProcessor为FShadowDepthPassMeshProcessor。渲染的Shader为ShadowDepthPixelShader.usf

void FProjectedShadowInfo::RenderDepth(
	FRDGBuilder& GraphBuilder,
	const FSceneRenderer* SceneRenderer,
	FRDGTextureRef ShadowDepthTexture,
	bool bDoParallelDispatch,
	bool bDoCrossGPUCopy)
{
#if WANTS_DRAW_MESH_EVENTS
	FString EventName;

	if (GetEmitDrawEvents())
	{
		GetShadowTypeNameForDrawEvent(EventName);
		EventName += FString(TEXT(" ")) + FString::FromInt(ResolutionX) + TEXT("x") + FString::FromInt(ResolutionY);
	}

	RDG_EVENT_SCOPE(GraphBuilder, "%s", *EventName);
#endif

	CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_RenderWholeSceneShadowDepthsTime, bWholeSceneShadow);
	CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_RenderPerObjectShadowDepthsTime, !bWholeSceneShadow);
	QUICK_SCOPE_CYCLE_COUNTER(STAT_RenderShadowDepth);

	FScene* Scene = SceneRenderer->Scene;
	const ERHIFeatureLevel::Type FeatureLevel = ShadowDepthView->FeatureLevel;
	BeginRenderView(GraphBuilder, Scene);

	FShadowDepthPassParameters* PassParameters = GraphBuilder.AllocParameters<FShadowDepthPassParameters>();
	PassParameters->View = ShadowDepthView->ViewUniformBuffer;
	PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(
		ShadowDepthTexture,
		ERenderTargetLoadAction::ELoad,
		ERenderTargetLoadAction::ENoAction,
		FExclusiveDepthStencil::DepthWrite_StencilNop);

	if (CacheMode == SDCM_MovablePrimitivesOnly || CacheMode == SDCM_CSMScrolling)
	{
		// Copy in depths of static primitives before we render movable primitives.
		FMeshPassProcessorRenderState DrawRenderState;
		SetStateForShadowDepth(bOnePassPointLightShadow, bDirectionalLight, DrawRenderState, MeshPassTargetType);
		CopyCachedShadowMap(GraphBuilder, *ShadowDepthView, SceneRenderer, PassParameters->RenderTargets, DrawRenderState);
	}

	PassParameters->VirtualShadowMap = SceneRenderer->VirtualShadowMapArray.GetUniformBuffer();

	switch (FSceneInterface::GetShadingPath(FeatureLevel))
	{
	case EShadingPath::Deferred:
	{
		auto* ShadowDepthPassParameters = GraphBuilder.AllocParameters<FShadowDepthPassUniformParameters>();
		SetupShadowDepthPassUniformBuffer(this, GraphBuilder, *ShadowDepthView, *ShadowDepthPassParameters);//设置矩阵、Shader等相关ShadowDepthPassParameters参数
		PassParameters->DeferredPassUniformBuffer = GraphBuilder.CreateUniformBuffer(ShadowDepthPassParameters);
	}
	break;
	case EShadingPath::Mobile:
	{
		auto* ShadowDepthPassParameters = GraphBuilder.AllocParameters<FMobileShadowDepthPassUniformParameters>();
		SetupShadowDepthPassUniformBuffer(this, GraphBuilder, *ShadowDepthView, *ShadowDepthPassParameters);//设置矩阵、Shader等相关ShadowDepthPassParameters参数
		PassParameters->MobilePassUniformBuffer = GraphBuilder.CreateUniformBuffer(ShadowDepthPassParameters);
	}
	break;
	default:
		checkNoEntry();
	}

	ShadowDepthPass.BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);

#if WITH_MGPU
	// Need to fetch GPU mask outside "AddPass", as it's not updated during pass execution
	FRHIGPUMask GPUMask = GraphBuilder.RHICmdList.GetGPUMask();
#endif

	if (bDoParallelDispatch)
	{
		RDG_WAIT_FOR_TASKS_CONDITIONAL(GraphBuilder, IsShadowDepthPassWaitForTasksEnabled());

		GraphBuilder.AddPass(
			RDG_EVENT_NAME("ShadowDepthPassParallel"),
			PassParameters,
			ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
			[this, PassParameters
#if WITH_MGPU
			, ShadowDepthTexture, GPUMask, bDoCrossGPUCopy
#endif
			](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
		{
			FShadowParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, *ShadowDepthView, *this, FParallelCommandListBindings(PassParameters));
			ShadowDepthPass.DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams);

#if WITH_MGPU
			if (bDoCrossGPUCopy)
			{
				CopyCachedShadowMapCrossGPU(RHICmdList, ShadowDepthTexture->GetRHI(), GPUMask);
			}
#endif
		});
	}
	else
	{
		GraphBuilder.AddPass(
			RDG_EVENT_NAME("ShadowDepthPass"),
			PassParameters,
			ERDGPassFlags::Raster,
			[this, PassParameters
#if WITH_MGPU
			, ShadowDepthTexture, GPUMask, bDoCrossGPUCopy
#endif
			](FRHICommandList& RHICmdList)
		{
			SetStateForView(RHICmdList);
			ShadowDepthPass.DispatchDraw(nullptr, RHICmdList, &PassParameters->InstanceCullingDrawParams);

#if WITH_MGPU
			if (bDoCrossGPUCopy)
			{
				CopyCachedShadowMapCrossGPU(RHICmdList, ShadowDepthTexture->GetRHI(), GPUMask);
			}
#endif
		});
	}
}
void SetupShadowDepthPassUniformBuffer(
	const FProjectedShadowInfo* ShadowInfo,
	FRDGBuilder& GraphBuilder,
	const FViewInfo& View,
	FShadowDepthPassUniformParameters& ShadowDepthPassParameters)
{
	static const auto CSMCachingCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Shadow.CSMCaching"));
	const bool bCSMCachingEnabled = CSMCachingCVar && CSMCachingCVar->GetValueOnAnyThread() != 0;

	SetupSceneTextureUniformParameters(GraphBuilder, View.GetSceneTexturesChecked(), View.FeatureLevel, ESceneTextureSetupMode::None, ShadowDepthPassParameters.SceneTextures);//设置对应的RT与Texture

	ShadowDepthPassParameters.ProjectionMatrix = FTranslationMatrix44f(FVector3f(ShadowInfo->PreShadowTranslation - View.ViewMatrices.GetPreViewTranslation())) * ShadowInfo->TranslatedWorldToClipOuterMatrix;		// LWC_TDOO: Precision loss?
	ShadowDepthPassParameters.ViewMatrix = FMatrix44f(ShadowInfo->TranslatedWorldToView);	// LWC_TODO: Precision loss

	// Disable the SlopeDepthBias because we couldn't reconstruct the depth offset if it is not 0.0f when scrolling the cached shadow map.
	ShadowDepthPassParameters.ShadowParams = FVector4f(ShadowInfo->GetShaderDepthBias(), bCSMCachingEnabled ? 0.0f : ShadowInfo->GetShaderSlopeDepthBias(), ShadowInfo->GetShaderMaxSlopeDepthBias(), ShadowInfo->bOnePassPointLightShadow ? 1 : ShadowInfo->InvMaxSubjectDepth);
	ShadowDepthPassParameters.bClampToNearPlane = ShadowInfo->ShouldClampToNearPlane() ? 1.0f : 0.0f;

	if (ShadowInfo->bOnePassPointLightShadow)
	{
		check(ShadowInfo->BorderSize == 0);

		// offset from translated world space to (pre translated) shadow space 
		const FMatrix Translation = FTranslationMatrix(ShadowInfo->PreShadowTranslation - View.ViewMatrices.GetPreViewTranslation());

		for (int32 FaceIndex = 0; FaceIndex < 6; FaceIndex++)
		{
			ShadowDepthPassParameters.ShadowViewProjectionMatrices[FaceIndex] = FMatrix44f(Translation * ShadowInfo->OnePassShadowViewProjectionMatrices[FaceIndex]);		// LWC_TODO: Precision loss?
			ShadowDepthPassParameters.ShadowViewMatrices[FaceIndex] = FMatrix44f(Translation * ShadowInfo->OnePassShadowViewMatrices[FaceIndex]);
		}
	}

	ShadowDepthPassParameters.bRenderToVirtualShadowMap = false;
	ShadowDepthPassParameters.VirtualSmPageTable = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(uint32)));
	ShadowDepthPassParameters.PackedNaniteViews = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(Nanite::FPackedView)));
	ShadowDepthPassParameters.PageRectBounds = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FIntVector4)));

	FRDGTextureRef DepthBufferArray = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2DArray( FIntPoint(4,4), PF_R32_UINT, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV, 1 ), TEXT("Dummy-OutDepthBuffer") );

	ShadowDepthPassParameters.OutDepthBufferArray = GraphBuilder.CreateUAV( DepthBufferArray );
}

FShadowDepthPassMeshProcessor

CullMode双面物体会使用CM_None其余的使用 CM_CW或者CM_CCW。

bool FShadowDepthPassMeshProcessor::TryAddMeshBatch(const FMeshBatch& RESTRICT MeshBatch,
	uint64 BatchElementMask,
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	int32 StaticMeshId,
	const FMaterialRenderProxy& MaterialRenderProxy,
	const FMaterial& Material)
{
	const bool bShouldCastShadow = Material.ShouldCastDynamicShadows();

	const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
	const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);

	const ERasterizerCullMode MeshCullMode = FMeshPassProcessor::ComputeMeshCullMode(Material, OverrideSettings);
	ERasterizerCullMode FinalCullMode = SetupShadowCullMode(FeatureLevel, MeshPassTargetType, ShadowDepthType, Material, MeshCullMode, PrimitiveSceneProxy->CastsShadowAsTwoSided());

	//判断是否投射动态阴影 && 是否在MainPass中渲染 && 是否会走OpaquePass 
	bool bResult = true;
	if (bShouldCastShadow
		&& ShouldIncludeDomainInMeshPass(Material.GetMaterialDomain())
		&& ShouldIncludeMaterialInDefaultOpaquePass(Material)
		&& EnumHasAnyFlags(MeshSelectionMask, MeshBatch.VertexFactory->SupportsGPUScene(FeatureLevel) ? EShadowMeshSelection::VSM : EShadowMeshSelection::SM))
	{
		const FMaterialRenderProxy* EffectiveMaterialRenderProxy = &MaterialRenderProxy;
		const FMaterial* EffectiveMaterial = &Material;
		const bool bVFTypeSupportsNullPixelShader = MeshBatch.VertexFactory->SupportsNullPixelShader();
		const bool bEvaluateWPO = Material.MaterialModifiesMeshPosition_RenderThread()
			&& (!ShouldOptimizedWPOAffectNonNaniteShaderSelection() || PrimitiveSceneProxy->EvaluateWorldPositionOffset());

		//取得NullPixelShader以及计算材质中的WPO之后如果对没有Pixel进行写入且没有改变WPO使用默认材质渲染否则则采用当前模型的材质。
		if (UseDefaultMaterialForShadowDepth(Material, bVFTypeSupportsNullPixelShader, bEvaluateWPO))
		{
			const FMaterialRenderProxy* DefaultProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy();
			const FMaterial* DefaultMaterialResource = DefaultProxy->GetMaterialNoFallback(FeatureLevel);
			check(DefaultMaterialResource);

			// Override with the default material for opaque materials that don't modify mesh position.
			EffectiveMaterialRenderProxy = DefaultProxy;
			EffectiveMaterial = DefaultMaterialResource;
		}

		bResult = Process(MeshBatch, BatchElementMask, StaticMeshId, PrimitiveSceneProxy, *EffectiveMaterialRenderProxy, *EffectiveMaterial, MeshFillMode, FinalCullMode);
	}

	return bResult;
}

ShaderDepthBias

相关计算位于FProjectedShadowInfo::UpdateShaderDepthBias()。 起效条件勾选Dynamic Inset ShadowbCastInsetShadow或者勾选bSelfShadowOnly。骨骼物体有额外的bCastCapsuleDirectShadow CMD: CVarPerObjectDirectionalShadowDepthBias => r.Shadow.PerObjectDirectionalDepthBias

// per object shadows (the whole-scene are taken care of above)
if(bDirectionalLight)
{
	// we use CSMShadowDepthBias cvar but this is per object shadows, maybe we want to use different settings

	// the z range is adjusted to we need to adjust here as well
	DepthBias = CVarPerObjectDirectionalShadowDepthBias.GetValueOnRenderThread() / (MaxSubjectZ - MinSubjectZ);

	float WorldSpaceTexelScale = ShadowBounds.W / FMath::Max(ResolutionX, ResolutionY);

	DepthBias *= WorldSpaceTexelScale;
	DepthBias *= 0.5f;	// avg GetUserShadowBias, in that case we don't want this adjustable

	SlopeScaleDepthBias = CVarPerObjectDirectionalShadowSlopeScaleDepthBias.GetValueOnRenderThread();
	SlopeScaleDepthBias *= LightSceneInfo->Proxy->GetUserShadowSlopeBias();
}

bCastInsetShadow

bCastInsetShadow => PrimitiveProxy->CastsInsetShadow() => DoesPrimitiveCastInsetShadow() => FilterPrimitiveForShadows() => FGatherShadowPrimitivesPacket::AnyThreadTask() => FGatherShadowPrimitivesPrepareTask::DoTask()

Shader

VertexShader为ShadowDepthVertexShader.usf有4种变体

  • IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_PerspectiveCorrect);
  • IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_OutputDepth);
  • IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_OnePassPointLight);
  • IMPLEMENT_SHADOW_DEPTH_SHADERMODE_SHADERS(VertexShadowDepth_VirtualShadowMap); PixelShader为ShadowDepthPixelShader.usf有4种变体
  • IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_NonPerspectiveCorrect);
  • IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_PerspectiveCorrect);
  • IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_OnePassPointLight);
  • IMPLEMENT_SHADOWDEPTHPASS_PIXELSHADER_TYPE(PixelShadowDepth_VirtualShadowMap);

使用对应的Shader逻辑位于GetShadowDepthPassShaders()。只有非方向光同时不是OnePass的状态下会使用PerspectiveCorrect

void SetShadowDepthOutputs(
	float4x4 WorldToClipMatrix, 
	float4x4 WorldToShadowMatrix, 
	float4 WorldPosition, 
	float3 WorldVertexNormal, 
	out float4 OutPosition, 
	out float ShadowDepth
#if PERSPECTIVE_CORRECT_DEPTH
	, out float OutDepthBias
#endif
)
{
	OutPosition = mul(WorldPosition, WorldToClipMatrix);

	// Clamp the vertex to the near plane if it is in front of the near plane
	// This has problems if some vertices of a triangle get clamped and others do not, also causes artifacts with non-ortho projections
	if (PassStruct.bClampToNearPlane > 0 && OutPosition.z > OutPosition.w)
	{
		OutPosition.z = 0.999999f;
		OutPosition.w = 1.0f;
	}

	#if ONEPASS_POINTLIGHT_SHADOW
	const float3 ViewDirection = -normalize(mul(WorldPosition, WorldToShadowMatrix).xyz);
	const float3 ViewNormal = mul(float4(WorldVertexNormal,0), WorldToShadowMatrix).xyz;
	const float NoL = abs(dot(ViewDirection, ViewNormal));
	#else
	const float NoL = abs(dot(
		float3(WorldToShadowMatrix[0].z, WorldToShadowMatrix[1].z, WorldToShadowMatrix[2].z),
		WorldVertexNormal));
	#endif

	const float MaxSlopeDepthBias = PassStruct.ShadowParams.z;
	const float Slope = clamp(abs(NoL) > 0 ? sqrt(saturate(1 - NoL*NoL)) / NoL : MaxSlopeDepthBias, 0, MaxSlopeDepthBias);
	
	const float SlopeDepthBias = PassStruct.ShadowParams.y;
	const float SlopeBias = SlopeDepthBias * Slope;

	const float ConstantDepthBias = PassStruct.ShadowParams.x;
	const float DepthBias = SlopeBias + ConstantDepthBias;

	#if PERSPECTIVE_CORRECT_DEPTH
		ShadowDepth = OutPosition.z;
		OutDepthBias = DepthBias;
	#elif ONEPASS_POINTLIGHT_SHADOW
		ShadowDepth = 0;
		//OutPosition.z += DepthBias;
	#else
		// Output linear, normalized depth
		const float InvMaxSubjectDepth = PassStruct.ShadowParams.w;
		#if PLATFORM_NEEDS_PRECISE_SHADOW_DEPTH
			precise 
		#endif
		float AdjustedDepth = ( 1 - OutPosition.z * InvMaxSubjectDepth ) + DepthBias;
		ShadowDepth = AdjustedDepth;
		OutPosition.z = AdjustedDepth;
	#endif
}

void Main(
	FVertexFactoryInput Input,
#if ONEPASS_POINTLIGHT_SHADOW
	out FShadowDepthVSToPS OutParameters,
#else
	out FShadowDepthVSToPS OutParameters,
#endif
#if VIRTUAL_SM_ENABLED
	out nointerpolation uint PackedPageInfo : TEXCOORD8,
#endif
	out float4 OutPosition : SV_POSITION
#if ONEPASS_POINTLIGHT_SHADOW
	, out uint LayerIndex : SV_RenderTargetArrayIndex
#endif
#if VIRTUAL_SM_ENABLED	
	// OLA-TODO: this collides with instanced stereo, which thankfully is not used with shadow maps, so should be fine, presumably.
	, out float4 OutVirtualSmPageClip : SV_ClipDistance
#endif // VIRTUAL_SM_ENABLED
	)
{
	ResolvedView = ResolveView();

	//计算FMaterialVertexParameters最终获取顶点WorldPosition
	FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
	float4 WorldPos = VertexFactoryGetWorldPosition(Input, VFIntermediates);
	half3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);

	FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPos.xyz, TangentToLocal);
	const float3 WorldNormal = VertexFactoryGetWorldNormal(Input, VFIntermediates);

	WorldPos.xyz += GetMaterialWorldPositionOffset(VertexParameters);

#if ONEPASS_POINTLIGHT_SHADOW

	OutPosition = WorldPos;

	#if INTERPOLATE_VF_ATTRIBUTES
		// Masked materials need texture coords to clip
		OutParameters.FactoryInterpolants = VertexFactoryGetInterpolantsVSToPS(Input, VFIntermediates, VertexParameters);
	#endif

	#if INTERPOLATE_POSITION
		OutParameters.PixelPosition = WorldPos.xyz;
	#endif

	LayerIndex = bUseGpuSceneInstancing ? VertexFactoryGetViewIndex(VFIntermediates) : LayerId;
	OutPosition = mul(WorldPos, PassStruct.ShadowViewProjectionMatrices[LayerIndex]);

#else
	float Dummy;

	SetShadowDepthOutputs(
		PassStruct.ProjectionMatrix,
		PassStruct.ViewMatrix,
		WorldPos, 
		WorldNormal,
		OutPosition, 
#if !PERSPECTIVE_CORRECT_DEPTH
		Dummy
#else
		OutParameters.ShadowDepth,
		OutParameters.DepthBias
#endif
		);
	
	#if INTERPOLATE_VF_ATTRIBUTES
		// Masked materials need texture coords to clip
		OutParameters.FactoryInterpolants = VertexFactoryGetInterpolantsVSToPS(Input, VFIntermediates, VertexParameters);
	#endif

	#if INTERPOLATE_POSITION
		OutParameters.PixelPosition = WorldPos.xyz;
	#endif

	#if !PERSPECTIVE_CORRECT_DEPTH && !COMPILER_SUPPORTS_EMPTY_STRUCTS
		OutParameters.Dummy = 0;
	#endif

#endif

#if VIRTUAL_SM_ENABLED
	PackedPageInfo = 0;
	
	OutVirtualSmPageClip = float4(1.0f, 1.0f, 1.0f, 1.0f);
	if (PassStruct.bRenderToVirtualShadowMap != 0)
	{
		// Get the offset from which we loaded the instance ID
		uint InstanceIdIndex = VertexFactoryGetInstanceIdLoadIndex(VFIntermediates);
		PackedPageInfo = InstanceCulling.PageInfoBuffer[InstanceIdIndex];

		FPageInfo PageInfo = UnpackPageInfo(PackedPageInfo);

		TransformToVirtualSmPage(OutPosition, OutVirtualSmPageClip, PageInfo, WorldPos.xyz);
	}
#endif // VIRTUAL_SM_ENABLED
}

#if POSITION_ONLY
void PositionOnlyMain(
	in FPositionAndNormalOnlyVertexFactoryInput Input,
#if ONEPASS_POINTLIGHT_SHADOW
	out uint LayerIndex : SV_RenderTargetArrayIndex,
#endif
	out FShadowDepthVSToPS OutParameters,

#if VIRTUAL_SM_ENABLED
	out nointerpolation uint PackedPageInfo : TEXCOORD8,
#endif
	out float4 OutPosition : SV_POSITION
#if VIRTUAL_SM_ENABLED	
	// OLA-TODO: this collides with instanced stereo, which thankfully is not used with shadow maps, so should be fine, presumably.
	, out float4 OutVirtualSmPageClip : SV_ClipDistance
#endif // VIRTUAL_SM_ENABLED
	)
{
	ResolvedView = ResolveView();

	float4 WorldPos = VertexFactoryGetWorldPosition(Input);

#if INTERPOLATE_VF_ATTRIBUTES
	OutParameters.FactoryInterpolants = (FVertexFactoryInterpolantsVSToPS)0;
#endif

	float3 WorldNormal = VertexFactoryGetWorldNormal(Input);

#if ONEPASS_POINTLIGHT_SHADOW
	LayerIndex = bUseGpuSceneInstancing ? VertexFactoryGetViewIndex(Input) : LayerId;
	OutPosition = mul(WorldPos, PassStruct.ShadowViewProjectionMatrices[LayerIndex]);

#else // !ONEPASS_POINTLIGHT_SHADOW
	float ShadowDepth;
	SetShadowDepthOutputs(
		PassStruct.ProjectionMatrix, 
		PassStruct.ViewMatrix, 
		WorldPos, 
		WorldNormal, 
		OutPosition,
	#if PERSPECTIVE_CORRECT_DEPTH
		OutParameters.ShadowDepth,
		OutParameters.DepthBias
	#else
		ShadowDepth
	#endif
	);

	#if !PERSPECTIVE_CORRECT_DEPTH && !COMPILER_SUPPORTS_EMPTY_STRUCTS
		OutParameters.Dummy = 0;
	#endif
#endif // ONEPASS_POINTLIGHT_SHADOW

#if INTERPOLATE_POSITION
	OutParameters.PixelPosition = WorldPos.xyz;
#endif

#if VIRTUAL_SM_ENABLED
	PackedPageInfo = 0;

	OutVirtualSmPageClip = float4(1.0f, 1.0f, 1.0f, 1.0f);
	if (PassStruct.bRenderToVirtualShadowMap != 0)
	{
		// Get the offset from which we loaded the instance ID
		uint InstanceIdIndex = VertexFactoryGetInstanceIdLoadIndex(Input);
		// TODO: Maybe offset index to local buffer for this? We may not want to use a global since most passes are not supporting this
		//       Or perhaps both are different, as they are managed somewhat differently anyway, maybe.
		PackedPageInfo = InstanceCulling.PageInfoBuffer[InstanceIdIndex];
		FPageInfo PageInfo = UnpackPageInfo(PackedPageInfo);
		TransformToVirtualSmPage(OutPosition, OutVirtualSmPageClip, PageInfo, WorldPos.xyz);
	}
#endif // VIRTUAL_SM_ENABLED
}

#endif // POSITION_ONLY
void Main( 
	FShadowDepthVSToPS Inputs,
#if VIRTUAL_SM_ENABLED
	nointerpolation uint PackedPageInfo : TEXCOORD8,
#endif
	in float4 SvPosition : SV_Position		// after all interpolators
#if PERSPECTIVE_CORRECT_DEPTH || COMPILER_METAL
	,out float OutDepth : SV_DEPTH
#endif
	)
{
	ResolvedView = ResolveView();

#if INTERPOLATE_VF_ATTRIBUTES
	
	FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Inputs.FactoryInterpolants, SvPosition);//取得材质中的PixelShader相关变量PixelMaterialInputs。
	FPixelMaterialInputs PixelMaterialInputs;
	#if INTERPOLATE_POSITION
	    {
			float4 ScreenPosition  = SvPositionToResolvedScreenPosition(SvPosition);  
			
			float3 TranslatedWorld = Inputs.PixelPosition.xyz;

			CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, 1, TranslatedWorld, TranslatedWorld);
		}
	#else
		CalcMaterialParameters(MaterialParameters, PixelMaterialInputs, SvPosition, 1);
	#endif

	// Evaluate the mask for masked materials
	GetMaterialClippingShadowDepth(MaterialParameters, PixelMaterialInputs);
#else
	ClipLODTransition(SvPosition.xy);
#endif

#if PERSPECTIVE_CORRECT_DEPTH
	const float InvMaxSubjectDepth = PassStruct.ShadowParams.w;
	Inputs.ShadowDepth = 1 - Inputs.ShadowDepth * InvMaxSubjectDepth;
	Inputs.ShadowDepth += Inputs.DepthBias;

	OutDepth = saturate(Inputs.ShadowDepth);
#elif COMPILER_METAL
	// Metal fragment shader must not be empty,
	// so output depth value explicitly if this shader permuation was not discarded
	OutDepth = SvPosition.z;
#endif

#if ENABLE_NON_NANITE_VSM && VIRTUAL_TEXTURE_TARGET
	uint2 vAddress = (uint2)SvPosition.xy;
	float DeviceZ = SvPosition.z;

	FPageInfo PageInfo = UnpackPageInfo( PackedPageInfo );
	FNaniteView NaniteView = UnpackNaniteView( PassStruct.PackedNaniteViews[ PageInfo.ViewId ] );

	FShadowPhysicalPage Page = ShadowDecodePageTable( PassStruct.VirtualSmPageTable[ CalcPageOffset( NaniteView.TargetLayerIndex, NaniteView.TargetMipLevel, vAddress >> VSM_LOG2_PAGE_SIZE ) ] );

	if( Page.bThisLODValid )
	{
		uint2 pAddress = Page.PhysicalAddress * VSM_PAGE_SIZE + (vAddress & VSM_PAGE_SIZE_MASK);
		// If requested, render to the static page
		const int ArrayIndex = PageInfo.bStaticPage ? GetVirtualShadowMapStaticArrayIndex() : 0;
		InterlockedMax( PassStruct.OutDepthBufferArray[ uint3( pAddress, ArrayIndex ) ], asuint( DeviceZ ) );
	}
#endif
}

RenderLights

渲染ScreenShadowMask并用于光照计算。

FDeferredShadingSceneRenderer::RenderLights() => RDG_EVENT_SCOPE(GraphBuilder, "UnbatchedLights");//batchedLights为没有LightFunction与没有阴影的灯光。 => for (int32 LightIndex = UnbatchedLightStart; LightIndex < LumenLightStart; LightIndex++) => if (bDrawShadows){...} => RenderRayTracingShadows()/RenderDeferredShadowProjections() => if (bDirectLighting){...} if (bDirectLighting){...} => RenderLight() if (bUseHairLighting){...}=> RenderLightForHair()

RenderDeferredShadowProjections()

void FDeferredShadingSceneRenderer::RenderDeferredShadowProjections(
	FRDGBuilder& GraphBuilder,
	const FMinimalSceneTextures& SceneTextures,
	const FTranslucencyLightingVolumeTextures& TranslucencyLightingVolumeTextures,
	const FLightSceneInfo* LightSceneInfo,
	FRDGTextureRef ScreenShadowMaskTexture,
	FRDGTextureRef ScreenShadowMaskSubPixelTexture)
{
	CheckShadowDepthRenderCompleted();

	SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderShadowProjections, FColor::Emerald);
	SCOPE_CYCLE_COUNTER(STAT_ProjectedShadowDrawTime);
	RDG_EVENT_SCOPE(GraphBuilder, "ShadowProjectionOnOpaque");
	RDG_GPU_STAT_SCOPE(GraphBuilder, ShadowProjection);

	const FVisibleLightInfo& VisibleLightInfo = VisibleLightInfos[LightSceneInfo->Id];
	
	const bool bProjectingForForwardShading = false;
	RenderShadowProjections(GraphBuilder, SceneTextures, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture, LightSceneInfo, bProjectingForForwardShading);
	ShadowSceneRenderer->ApplyVirtualShadowMapProjectionForLight(GraphBuilder, SceneTextures, LightSceneInfo, ScreenShadowMaskTexture, ScreenShadowMaskSubPixelTexture);

	RenderCapsuleDirectShadows(GraphBuilder, SceneTextures.UniformBuffer, *LightSceneInfo, ScreenShadowMaskTexture, VisibleLightInfo.CapsuleShadowsToProject, bProjectingForForwardShading);

	// Inject deep shadow mask for regular shadow map. When using virtual shadow map, it is directly handled in the shadow kernel.
	if (HairStrands::HasViewHairStrandsData(Views))
	{
		bool bNeedHairShadowMaskPass = false;
		const bool bVirtualShadowOnePass = VisibleLightInfo.VirtualShadowMapClipmaps.Num() > 0;
		if (!bVirtualShadowOnePass)
		{
			for (int32 ShadowIndex = 0; ShadowIndex < VisibleLightInfo.ShadowsToProject.Num(); ShadowIndex++)
			{
				FProjectedShadowInfo* ProjectedShadowInfo = VisibleLightInfo.ShadowsToProject[ShadowIndex];
				if (ProjectedShadowInfo->HasVirtualShadowMap())
				{
					bNeedHairShadowMaskPass = false;
					break;
				}
				else
				{
					bNeedHairShadowMaskPass = true;
					break;
				}
			}
		}
		if (bNeedHairShadowMaskPass)
		{
			RenderHairStrandsShadowMask(GraphBuilder, Views, LightSceneInfo, false /*bForward*/, ScreenShadowMaskTexture);
		}
	}
}

RenderShadowProjections():

  1. 取得当前FVisibleLightInfo、FLightSceneProxy创建FProjectedShadowInfoArray DistanceFieldShadows、NormalShadows。
  2. 遍历VisibleLightInfo.ShadowsToProject按照阴影特征将每个FProjectedShadowInfo加入DistanceFieldShadows、NormalShadows。
  3. 调用Lamda来渲染ScreenShadowMaskTexture与ScreenShadowMaskSubPixelTexture。
  4. 遍历DistanceFieldShadows调用FProjectedShadowInfo->RenderRayTracedDistanceFieldProjection()来渲染距离场阴影。

FSceneRenderer::RenderShadowProjections()

  1. 初始化UniformStruct变量。
  2. 遍历传入的FProjectedShadowInfo数组并调用ProjectedShadowInfo->RenderProjection() 将所有灯光的阴影Mask绘制到一张ScreenShadowMask上。
    1. 在TShadowProjectionPS中通过SetParameters() => ProjectionParameters.Set()来设置ShadowDepthTextureValue。ShadowDepthTextureValue = ShadowInfo->RenderTargets.DepthTarget->GetRHI();
void FSceneRenderer::RenderShadowProjections(
	FRDGBuilder& GraphBuilder,
	FRDGTextureRef OutputTexture,
	const FMinimalSceneTextures& SceneTextures,
	const FLightSceneProxy* LightSceneProxy,
	TArrayView<const FProjectedShadowInfo* const> Shadows,
	bool bSubPixelShadow,
	bool bProjectingForForwardShading)
{
	CheckShadowDepthRenderCompleted();

	for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
	{
		const FViewInfo& View = Views[ViewIndex];
		const FExclusiveDepthStencil ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;

		if (bSubPixelShadow && !HairStrands::HasViewHairStrandsData(View))
		{
			continue;
		}

		View.BeginRenderView();

		// Sanity check
		if (bSubPixelShadow)
		{
			check(View.HairStrandsViewData.VisibilityData.HairOnlyDepthTexture);
		}

		FShadowProjectionPassParameters CommonPassParameters;
		CommonPassParameters.SceneTextures = SceneTextures.GetSceneTextureShaderParameters(View.FeatureLevel);
		CommonPassParameters.HairStrands = HairStrands::BindHairStrandsViewUniformParameters(View);

		if (Strata::IsStrataEnabled())
		{
			CommonPassParameters.Strata = Strata::BindStrataGlobalUniformParameters(View);
		}
		
		CommonPassParameters.RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ELoad);
		CommonPassParameters.RenderTargets.DepthStencil =
			bSubPixelShadow ?
			FDepthStencilBinding(View.HairStrandsViewData.VisibilityData.HairOnlyDepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil) :
			FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil);

		RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
		RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);

		// Project the shadow depth buffers onto the scene.
		for (const FProjectedShadowInfo* ProjectedShadowInfo : Shadows)
		{
			if (ProjectedShadowInfo->bAllocated) 
			{
				// Only project the shadow if it's large enough in this particular view (split screen, etc... may have shadows that are large in one view but irrelevantly small in others)
				if (ProjectedShadowInfo->FadeAlphas[ViewIndex] > 1.0f / 256.0f)
				{
					if (ProjectedShadowInfo->bOnePassPointLightShadow)
					{
						ProjectedShadowInfo->RenderOnePassPointLightProjection(GraphBuilder, CommonPassParameters, ViewIndex, View, LightSceneProxy, bProjectingForForwardShading, bSubPixelShadow);
					}
					else
					{
						ProjectedShadowInfo->TShadowProjectionPS(GraphBuilder, CommonPassParameters, ViewIndex, &View, LightSceneProxy, this, bProjectingForForwardShading, bSubPixelShadow);
					}
				}
			}
		}
	}
}

Shader

投射阴影渲染用Shader为

  • ShadowProjectionVertexShader.usf
    • FShadowProjectionNoTransformVS直接输出顶点坐标。
    • FShadowVolumeBoundProjectionVS
  • ShadowProjectionPixelShader.usf
    • TShadowProjectionPS
    • TShadowProjectionFromTranslucencyPS
    • TDirectionalPercentageCloserShadowProjectionPS
    • TModulatedShadowProjection
  • ShadowProjectionCommon.ush
    • ShadowDepthTexture
    • ShadowDepthCubeTexture

主要步骤:

  1. 将SceneDepthScreenSpace =>ShadowSpace
  2. 根据过滤方式调用:
    1. 不过滤:Shadow = LightSpacePixelDepthForOpaque < Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).r;
    2. PCSS初始化FPCSSSamplerSettings之后调用DirectionalPCSS()
    3. PCF初始化FPCFSamplerSettings之后调用ManualPCF()
  3. 调整阴影数值。钳制、模糊、过滤。
  4. OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow));LinearSpace => sRGB 本质为sqrt(input)比pow(x, 1/2.2) 节约性能。

PS. PreObjectShadow使用的Shader为ShadowProjectionPixelShader.usf的Main() BindShaderShaders<FShadowProjectionNoTransformVS, TShadowProjectionPS<5, true> >(RHICmdList, GraphicsPSOInit, ViewIndex, View, ShadowInfo, StencilRef); break;

RenderLight()

// Render the light to the scene color buffer, conditionally using the attenuation buffer or a 1x1 white texture as input 
if (bDirectLighting)
{
	for (int32 ViewIndex = 0, ViewCount = Views.Num(); ViewIndex < ViewCount; ++ViewIndex)
	{
		const FViewInfo& View = Views[ViewIndex];

		// If the light elided the screen space shadow mask, sample directly from the packed shadow mask
		int32 VirtualShadowMapId = INDEX_NONE;
		if (bElideScreenShadowMask)
		{
			INC_DWORD_STAT(STAT_VSMLocalProjectionOnePassFast);
			VirtualShadowMapId = VisibleLightInfo.GetVirtualShadowMapId(&View);
		}

		RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, ViewCount > 1, "View%d", ViewIndex);
		SCOPED_GPU_MASK(GraphBuilder.RHICmdList, View.GPUMask);
		RenderLight(GraphBuilder, Scene, View, SceneTextures, &LightSceneInfo, VirtualShadowMapId != INDEX_NONE ? nullptr : ScreenShadowMaskTexture, LightingChannelsTexture, false /*bRenderOverlap*/, true /*bCloudShadow*/, VirtualShadowMapArray.GetUniformBuffer(), ShadowSceneRenderer->VirtualShadowMapMaskBits, VirtualShadowMapId);
	}
}

在RenderLight()中通过PassParameters->PS = GetDeferredLightPSParameters()传入参数使用ScreenShadowMaskTexture来渲染阴影LightAttenuationTexture

Shader

DeferredLightPixelShaders.usf

void DeferredLightPixelMain(  
#if LIGHT_SOURCE_SHAPE > 0  
    float4 InScreenPosition : TEXCOORD0,  
#else  
    float2 ScreenUV          : TEXCOORD0,  
    float3 ScreenVector       : TEXCOORD1,  
#endif  
    float4 SVPos         : SV_POSITION,  
    out float4 OutColor       : SV_Target0  
#if STRATA_OPAQUE_ROUGH_REFRACTION_ENABLED  
    , out float3 OutOpaqueRoughRefractionSceneColor : SV_Target1  
    , out float3 OutSubSurfaceSceneColor : SV_Target2#endif  
    )
    {
    ...
    #else // STRATA_ENABLED

	FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InputParams.ScreenUV);
	// Only light pixels marked as using deferred shading
	BRANCH if (ScreenSpaceData.GBuffer.ShadingModelID > 0
#if USE_LIGHTING_CHANNELS
		&& (GetLightingChannelMask(InputParams.ScreenUV) & DeferredLightUniforms.LightingChannelMask)//灯光通道计算
#endif
		)
	{
		const float SceneDepth = CalcSceneDepth(InputParams.ScreenUV);
		const FDerivedParams DerivedParams = GetDerivedParams(InputParams, SceneDepth);//FDerivedParams主要存储CameraVector以及摄像机的世界坐标

		FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);//根据灯光类型,填充对应灯光数据。
		UpdateLightDataColor(LightData, InputParams, DerivedParams);//计算IES * Atmosphere * Cloud 的Attenuation。结果输出到LightData.Color

	 #if USE_HAIR_COMPLEX_TRANSMITTANCE
		if (ScreenSpaceData.GBuffer.ShadingModelID == SHADINGMODELID_HAIR && ShouldUseHairComplexTransmittance(ScreenSpaceData.GBuffer))
		{
			LightData.HairTransmittance = EvaluateDualScattering(ScreenSpaceData.GBuffer, DerivedParams.CameraVector, -DeferredLightUniforms.Direction);
		}
	#endif

		float Dither = InterleavedGradientNoise(InputParams.PixelPos, View.StateFrameIndexMod8);//http://advances.realtimerendering.com/s2014/index.html

		float SurfaceShadow = 1.0f;
		
		float4 LightAttenuation = GetLightAttenuationFromShadow(InputParams, SceneDepth);//取得ScreenShadowProjection
		float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow);

		OutColor += Radiance;
	}

#endif // STRATA_ENABLED

	// RGB:SceneColor Specular and Diffuse
	// A:Non Specular SceneColor Luminance
	// So we need PreExposure for both color and alpha
	OutColor.rgba *= GetExposure();
    ...
    }

GetLightAttenuationFromShadow()

float4 GetLightAttenuationFromShadow(in FInputParams InputParams, float SceneDepth)
{
	float4 LightAttenuation = float4(1, 1, 1, 1);

#if USE_VIRTUAL_SHADOW_MAP_MASK
	if (VirtualShadowMapId != INDEX_NONE)
	{
		float ShadowMask = GetVirtualShadowMapMaskForLight(
			ShadowMaskBits,
			uint2(InputParams.PixelPos),
			SceneDepth,
			0,	// TODO: EyeIndex
			VirtualShadowMapId);

		// TODO: Subsurface...?
		return ShadowMask.xxxx;
	}
	else
#endif
	{
		return GetPerPixelLightAttenuation(InputParams.ScreenUV);
	}
}

float4 GetPerPixelLightAttenuation(float2 UV)
{
	return DecodeLightAttenuation(Texture2DSampleLevel(LightAttenuationTexture, LightAttenuationTextureSampler, UV, 0));//DecodeLightAttenuation return x * x;
}

GetDynamicLighting()

float4 GetDynamicLighting(
	float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, 
	FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, 
	inout float SurfaceShadow)
{
	FDeferredLightingSplit SplitLighting = GetDynamicLightingSplit(
		TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, 
		LightData, LightAttenuation, Dither, SVPos, 
		SurfaceShadow);

	return SplitLighting.SpecularLighting + SplitLighting.DiffuseLighting;
}

FDeferredLightingSplit GetDynamicLightingSplit(  
    float3 TranslatedWorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID,   
	FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos,   
	inout float SurfaceShadow)  
{  
    FLightAccumulator LightAccumulator = AccumulateDynamicLighting(TranslatedWorldPosition, CameraVector, GBuffer, AmbientOcclusion, ShadingModelID, LightData, LightAttenuation, Dither, SVPos, SurfaceShadow);  
    return LightAccumulator_GetResultSplit(LightAccumulator);  
}

FLightAccumulator AccumulateDynamicLighting(  
    float3 TranslatedWorldPosition, half3 CameraVector, FGBufferData GBuffer, half AmbientOcclusion, uint ShadingModelID,  
    FDeferredLightData LightData, half4 LightAttenuation, float Dither, uint2 SVPos,   
inout float SurfaceShadow)  
{
...
BRANCH
	if( LightMask > 0 )
	{
		FShadowTerms Shadow;
		Shadow.SurfaceShadow = AmbientOcclusion;
		Shadow.TransmissionShadow = 1;
		Shadow.TransmissionThickness = 1;
		Shadow.HairTransmittance.OpaqueVisibility = 1;
		const float ContactShadowOpacity = GBuffer.CustomData.a;
		GetShadowTerms(GBuffer.Depth, GBuffer.PrecomputedShadowFactors, GBuffer.ShadingModelID, ContactShadowOpacity,
			LightData, TranslatedWorldPosition, L, LightAttenuation, Dither, Shadow);
		SurfaceShadow = Shadow.SurfaceShadow;

		LightAccumulator.EstimatedCost += 0.3f;		// add the cost of getting the shadow terms

#if SHADING_PATH_MOBILE
		const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
		
		FDirectLighting Lighting = (FDirectLighting)0;

		half NoL = max(0, dot(GBuffer.WorldNormal, L));
	#if TRANSLUCENCY_NON_DIRECTIONAL
		NoL = 1.0f;
	#endif
		Lighting = EvaluateBxDF(GBuffer, N, V, L, NoL, Shadow);

		Lighting.Specular *= LightData.SpecularScale;
				
		LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
		LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
#else // SHADING_PATH_MOBILE
		BRANCH
		if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
		{
			const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);

		#if NON_DIRECTIONAL_DIRECT_LIGHTING
			float Lighting;

			if( LightData.bRectLight )
			{
				FRect Rect = GetRect( ToLight, LightData );

				Lighting = IntegrateLight( Rect );
			}
			else
			{
				FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

				Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
			}

			float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
			LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
		#else
			FDirectLighting Lighting;

			if (LightData.bRectLight)
			{
				FRect Rect = GetRect( ToLight, LightData );
				const FRectTexture SourceTexture = ConvertToRectTexture(LightData);

				#if REFERENCE_QUALITY
					Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
				#else
					Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
				#endif
			}
			else
			{
				FCapsuleLight Capsule = GetCapsule( ToLight, LightData );

				#if REFERENCE_QUALITY
					Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
				#else
					Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
				#endif
			}

			Lighting.Specular *= LightData.SpecularScale;
				
			LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
			LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, MaskedLightColor * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );

			LightAccumulator.EstimatedCost += 0.4f;		// add the cost of the lighting computations (should sum up to 1 form one light)
		#endif
		}
#endif // SHADING_PATH_MOBILE
	}
	return LightAccumulator;
}

GetShadowTerms()

  1. ContactShadow在GetShadowTerms()的ShadowRayCast()处开始计算。
void GetShadowTerms(float SceneDepth, half4 PrecomputedShadowFactors, uint ShadingModelID, float ContactShadowOpacity, FDeferredLightData LightData, float3 TranslatedWorldPosition, half3 L, half4 LightAttenuation, float Dither, inout FShadowTerms Shadow)
{
	float ContactShadowLength = 0.0f;
	const float ContactShadowLengthScreenScale = GetTanHalfFieldOfView().y * SceneDepth;

	BRANCH
	if (LightData.ShadowedBits)
	{
		// Remapping the light attenuation buffer (see ShadowRendering.cpp)

		// LightAttenuation: Light function + per-object shadows in z, per-object SSS shadowing in w, 
		// Whole scene directional light shadows in x, whole scene directional light SSS shadows in y
		// Get static shadowing from the appropriate GBuffer channel
#if ALLOW_STATIC_LIGHTING
		half UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, half4(1, 1, 1, 1));
		half StaticShadowing = lerp(1, dot(PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap);
#else
		half StaticShadowing = 1.0f;
#endif

		if (LightData.bRadialLight || SHADING_PATH_MOBILE)
		{
			// Remapping the light attenuation buffer (see ShadowRendering.cpp)

			Shadow.SurfaceShadow = LightAttenuation.z * StaticShadowing;
			// SSS uses a separate shadowing term that allows light to penetrate the surface
			//@todo - how to do static shadowing of SSS correctly?
			Shadow.TransmissionShadow = LightAttenuation.w * StaticShadowing;

			Shadow.TransmissionThickness = LightAttenuation.w;
		}
		else
		{
			// Remapping the light attenuation buffer (see ShadowRendering.cpp)
			//  R:     WholeSceneShadows, non SSS  
			//  G:     WholeSceneShadows,     SSS  
			//  B: non WholeSceneShadows, non SSS  
			//  A: non WholeSceneShadows,     SSS  
			//  
			// SSS: SubsurfaceScattering materials  
			// non SSS: shadow for opaque materials  
			// WholeSceneShadows: directional light CSM  
			// non WholeSceneShadows: spotlight, per object shadows, translucency lighting, omni-directional lights

			float DynamicShadowFraction = DistanceFromCameraFade(SceneDepth, LightData);
			// For a directional light, fade between static shadowing and the whole scene dynamic shadowing based on distance + per object shadows
			Shadow.SurfaceShadow = lerp(LightAttenuation.x, StaticShadowing, DynamicShadowFraction);//针对DirectionalLight有一个StaticShadow与DynamicShadow过度。
			// Fade between SSS dynamic shadowing and static shadowing based on distance
			Shadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);//SSS阴影

			Shadow.SurfaceShadow *= LightAttenuation.z;
			Shadow.TransmissionShadow *= LightAttenuation.z;

			// Need this min or backscattering will leak when in shadow which cast by non perobject shadow(Only for directional light)
			Shadow.TransmissionThickness = min(LightAttenuation.y, LightAttenuation.w);
		}

		FLATTEN
		if (LightData.ShadowedBits > 1 && LightData.ContactShadowLength > 0)
		{
			ContactShadowLength = LightData.ContactShadowLength * (LightData.ContactShadowLengthInWS ? 1.0f : ContactShadowLengthScreenScale);
		}
	}

#if SUPPORT_CONTACT_SHADOWS

#if STRATA_ENABLED == 0
	if (LightData.ShadowedBits < 2 && (ShadingModelID == SHADINGMODELID_HAIR))
	{
		ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
	}
	// World space distance to cover eyelids and eyelashes but not beyond
	if (ShadingModelID == SHADINGMODELID_EYE)
	{
		ContactShadowLength = 0.5;
		
	}
#endif

	#if MATERIAL_CONTACT_SHADOWS
		ContactShadowLength = 0.2 * ContactShadowLengthScreenScale;
	#endif

	BRANCH
	if (ContactShadowLength > 0.0)//开始计算接触阴影
	{
		float StepOffset = Dither - 0.5;
		bool bHitCastContactShadow = false;
		bool bHairNoShadowLight = ShadingModelID == SHADINGMODELID_HAIR && !LightData.ShadowedBits;
		float HitDistance = ShadowRayCast( TranslatedWorldPosition, L, ContactShadowLength, 8, StepOffset, bHairNoShadowLight, bHitCastContactShadow );
				
		if ( HitDistance > 0.0 )
		{
			float ContactShadowOcclusion = bHitCastContactShadow ? LightData.ContactShadowCastingIntensity : LightData.ContactShadowNonCastingIntensity;

#if STRATA_ENABLED == 0
			// Exponential attenuation is not applied on hair/eye/SSS-profile here, as the hit distance (shading-point to blocker) is different from the estimated 
			// thickness (closest-point-from-light to shading-point), and this creates light leaks. Instead we consider first hit as a blocker (old behavior)
			BRANCH
			if (ContactShadowOcclusion > 0.0 && 
				IsSubsurfaceModel(ShadingModelID) &&
				ShadingModelID != SHADINGMODELID_HAIR &&
				ShadingModelID != SHADINGMODELID_EYE &&
				ShadingModelID != SHADINGMODELID_SUBSURFACE_PROFILE)
			{
				// Reduce the intensity of the shadow similar to the subsurface approximation used by the shadow maps path
				// Note that this is imperfect as we don't really have the "nearest occluder to the light", but this should at least
				// ensure that we don't darken-out the subsurface term with the contact shadows
				float Density = SubsurfaceDensityFromOpacity(ContactShadowOpacity);
				ContactShadowOcclusion *= 1.0 - saturate( exp( -Density * HitDistance ) );
			}
#endif

			float ContactShadow = 1.0 - ContactShadowOcclusion;

			Shadow.SurfaceShadow *= ContactShadow;
			Shadow.TransmissionShadow *= ContactShadow;
		}
		
	}
#endif

	Shadow.HairTransmittance = LightData.HairTransmittance;
	Shadow.HairTransmittance.OpaqueVisibility = Shadow.SurfaceShadow;
}

Remark

  1. RenderDepth()渲染完深度贴图之后经过GatherAndSortLights()、ComputeLightGrid()获取到经过剪裁的FSortedLightSetSceneInfo &SortedLightSet之后传递给RenderLights()。
  2. BindShadowProjectionShaders() => BindShaderShaders() =>
  3. TShadowProjectionPS => ProjectionParameters.Bind(Initializer); => ShadowDepthTexture.Bind(ParameterMap,TEXT("ShadowDepthTexture"));