38 KiB
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
- ShadowDepths => RenderShadowDepthMaps()
- Lights => RenderLights()
- DirectLighting
- UnbatchedLights
- ShadowProjectionOnOpaque
- UnbatchedLights
- DirectLighting
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:方向光投影
- TSpotPercentageCloserShadowProjectionPS:SpotLight
- FOnePassPointShadowProjectionPS(Moible?)
相关函数
- FDeferredShadingSceneRenderer::RenderLights()
- FDeferredShadingSceneRenderer::RenderDeferredShadowProjections()
- FSceneRenderer::RenderShadowProjections()
- FProjectedShadowInfo::SetupFrustumForProjection():构建阴影投影4棱椎平面信息。
- FProjectedShadowInfo::SetupProjectionStencilMask():
- FSceneRenderer::RenderShadowProjections()
- FDeferredShadingSceneRenderer::RenderDeferredShadowProjections()
其他
顺序: 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提出:
- 【[UFSH2024]用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线 | 晨风 Neverwind 完美世界游戏】 【精准空降到 07:27】 https://www.bilibili.com/video/BV1rW2LYvEox/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=447
阴影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的阴影)
{
屏蔽此阴影
}