38 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
Untitled 2024-12-08 12:18:54

阴影

阴影类型

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

相关类型

  • ShadowRendering.h FProjectedShadowInfo:存储投影阴影先关信息。包含各种变换矩阵、阴影渲染函数以及渲染参数、灯光&场景&图元信息。
  • SceneRendering.h FVisibleLightInfo可见光源信息, 主要是阴影相关的信息. FVisibleLightViewInfo

DynamicShadows

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);
}

阴影渲染

阴影偏移

可以考虑的Buffer有

  • ShadowDepths
  • CustomDepth

相关Paas

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

ShadowDepths

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

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;
}

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
}

Lights

Shader

ShadowProjectionPixelShader.usf

  • TShadowProjectionPS
  • TDirectionalPercentageCloserShadowProjectionPS方向光投影
  • TSpotPercentageCloserShadowProjectionPSSpotLight
  • FOnePassPointShadowProjectionPS(Moible?)

相关函数

  • FDeferredShadingSceneRenderer::RenderLights()
    • FDeferredShadingSceneRenderer::RenderDeferredShadowProjections()
      • FSceneRenderer::RenderShadowProjections()
        • FProjectedShadowInfo::SetupFrustumForProjection()构建阴影投影4棱椎平面信息。
        • FProjectedShadowInfo::SetupProjectionStencilMask()

其他

顺序: RenderCustomDepthPass

FSceneRenderer::CreateDynamicShadows => FSceneRenderer::CreatePerObjectProjectedShadow

if (!IsForwardShadingEnabled(ShaderPlatform))  
{  
    // Dynamic shadows are synced later when using the deferred path to make more headroom for tasks.  
    FinishInitDynamicShadows(GraphBuilder, InitViewTaskDatas.DynamicShadows, InstanceCullingManager, ExternalAccessQueue);  
}
if (RendererOutput == ERendererOutput::DepthPrepassOnly)
	{
		RenderOcclusionLambda();

		if (bUpdateNaniteStreaming)
		{
			Nanite::GStreamingManager.SubmitFrameStreamingRequests(GraphBuilder);
		}

		CopySceneCaptureComponentToTarget(GraphBuilder, SceneTextures, ViewFamilyTexture, ViewFamily, Views);
	}
	else
	{
		GVRSImageManager.PrepareImageBasedVRS(GraphBuilder, ViewFamily, SceneTextures);

		if (!IsForwardShadingEnabled(ShaderPlatform))
		{
			// Dynamic shadows are synced later when using the deferred path to make more headroom for tasks.
			FinishInitDynamicShadows(GraphBuilder, InitViewTaskDatas.DynamicShadows, InstanceCullingManager, ExternalAccessQueue);
		}

		// Update groom only visible in shadow
		if (IsHairStrandsEnabled(EHairStrandsShaderType::All, Scene->GetShaderPlatform()) && RendererOutput == ERendererOutput::FinalSceneColor)
		{
			UpdateHairStrandsBookmarkParameters(Scene, Views, HairStrandsBookmarkParameters);

			// Interpolation for cards/meshes only visible in shadow needs to happen after the shadow jobs are completed
			const bool bRunHairStrands = HairStrandsBookmarkParameters.HasInstances() && (Views.Num() > 0);
			if (bRunHairStrands)
			{
				RunHairStrandsBookmark(GraphBuilder, EHairStrandsBookmark::ProcessCardsAndMeshesInterpolation_ShadowView, HairStrandsBookmarkParameters);
			}
		}
	
		// NOTE: The ordering of the lights is used to select sub-sets for different purposes, e.g., those that support clustered deferred.
		FSortedLightSetSceneInfo& SortedLightSet = *GraphBuilder.AllocObject<FSortedLightSetSceneInfo>();
		{
			RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, SortLights);
			RDG_GPU_STAT_SCOPE(GraphBuilder, SortLights);
			ComputeLightGridOutput = GatherLightsAndComputeLightGrid(GraphBuilder, bComputeLightGrid, SortedLightSet);
		}

RenderLights

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

RenderDeferredShadowProjections()

半程阴影

由晨风&Neverwind提出

阴影Setup阶段:

if 启用半程阴影:
{
	额外进行一次CreatePerObjectProjectedShadow()
	{
		处理阴影光照Matrix                //猜测:
											 //在ProjectedShadowInfo->SetupPerObjectProjection()中调整FProjectedShadowInfo.TranslatedWorldToView。
											 //在LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer)之后修改WorldTolight。
		向阴影信息写入IsHalfViewShadowFlag//猜测在FProjectedShadowInfo中添加判断Flag并且写入。
		用新的光源方向画Atlas             //猜测给带有对应Flog的DirectionLigh多创建一个对应的Atlas
	}
}

截图代码(半程阴影修改LightDirection逻辑)

if(PrimitiveSceneinfo->Proxy->IsToonDisableSelfShadow())
{
...
	for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
	{
		const FViewInfo& View = Views[ViewIndex];
		const FMatrix& ViewMatrix = View.ShadowViewMatrices.GetViewMatrix()
		FVector LightDirection = Lightsceneinfo->Proxy- GetDirection();
		const FVector CameraDirection = ViewMatrix.GetColumn(2).GetSafeNonmal()
		float LightViewBlendFactor = PrimitiveSceneinfo->Proxy->GetToonHalfVienShadowFactor();
		Fvector HalfViewLightDir = (LightDirection + ( 1 - LightViewBlendFactor) + CameraDirection * LightViewBlendFactor).GetSafeNormal();
		FMatrix FinalCombineMatrix = FNnverseRotationMatrix(HalfViewLightDir.Rotation())
		ShadowInitializer.WorldTolight = FinalCombineMatrix;
	}
...
 }

PS.很有可能需要创建2个Atlas。Atlas的创建位于FSceneRenderer::AllocateCachedShadowDepthTargets()。数据存储在SortedShadowsForShadowDepthPass.ShadowMapAtlases中。大致由FSceneRenderer::FinishInitDynamicShadows()调用。

阴影Projection阶段: //此阶段需要屏蔽角色投射到自己的非半程阴影 //和角色投射到场景中会跟随视角移动的阴影

if(Toon材质,且没有程阴影Flag的阴影
&&Toon材质但有半程阴影Flag的阴影)
{
	屏蔽此阴影
}