9.4 KiB
Raw Blame History

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

前言

阴影渲染笔记:Shadow

实现功能:

  1. 控制深度偏移
  2. CustomDepth制作头发阴影偏移效果哦 https://zhuanlan.zhihu.com/p/689578355
  3. ContactShadow接触阴影实现衣服细节的DetailShadow
  4. 半程阴影

半程阴影

由晨风&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).GetSafeNormal()
		float LightViewBlendFactor = PrimitiveSceneinfo->Proxy->GetToonHalfVienShadowFactor();
		Fvector HalfViewLightDir = (LightDirection + ( 1 - LightViewBlendFactor) + CameraDirection * LightViewBlendFactor).GetSafeNormal();
		FMatrix FinalCombineMatrix = FInverseRotationMatrix(HalfViewLightDir.Rotation())
		ShadowInitializer.WorldToLight = FinalCombineMatrix;
	}
...
 }

相关代码位于FDirectionalLightSceneProxy::GetPerObjectProjectedShadowInitializer()在在FSceneRenderer::CreatePerObjectProjectedShadow()被调用。

virtual bool GetPerObjectProjectedShadowInitializer(const FBoxSphereBounds& SubjectBounds,FPerObjectProjectedShadowInitializer& OutInitializer) const override
	{
		OutInitializer.PreShadowTranslation = -SubjectBounds.Origin;
		OutInitializer.WorldToLight = FInverseRotationMatrix(FVector(WorldToLight.M[0][0],WorldToLight.M[1][0],WorldToLight.M[2][0]).GetSafeNormal().Rotation());
		OutInitializer.Scales = FVector2D(1.0f / SubjectBounds.SphereRadius,1.0f / SubjectBounds.SphereRadius);
		OutInitializer.SubjectBounds = FBoxSphereBounds(FVector::ZeroVector,SubjectBounds.BoxExtent,SubjectBounds.SphereRadius);
		OutInitializer.WAxis = FVector4(0,0,0,1);
		OutInitializer.MinLightW = -UE_OLD_HALF_WORLD_MAX;
		// Reduce casting distance on a directional light
		// This is necessary to improve floating point precision in several places, especially when deriving frustum verts from InvReceiverMatrix
		// This takes the object size into account to ensure that large objects get an extended distance
		OutInitializer.MaxDistanceToCastInLightW = FMath::Clamp(SubjectBounds.SphereRadius * CVarPerObjectCastDistanceRadiusScale.GetValueOnRenderThread(), CVarPerObjectCastDistanceMin.GetValueOnRenderThread(), (float)WORLD_MAX);

		return true;
	}

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

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

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

PS.很有可能在FProjectedShadowInfo::RenderProjection()阶段进行判断以此保证合成正确的ScreenShadowMask

实现方法

const FMaterialRenderProxy* MaterialRenderProxy = MeshBatch.MaterialRenderProxy;  
bool bEnableToonMeshDrawOutline = MaterialRenderProxy->GetToonOutlineDataAssetRT()->Settings.bEnableToonMeshDrawOutline;

FProjectedShadowInfo->Scene FPrimitiveSceneProxy

深度偏移

方法一

  1. FProjectedShadowInfo添加变量。 FSceneRenderer::RenderShadowDepthMaps() => RenderShadowDepthMapAtlases() => ProjectedShadowInfo->RenderDepth()

已放弃FProjectedShadowInfo无法判断MeshSection。

方法二(最终实现方法)

在材质中使用ShadowPassSwitch再对ViewSpace的Z轴方向使用DirectionalLightVector比较可以只对方向光进行偏移进行WPO偏移实现。

其优点就是可以用贴图来控制偏移过渡。

DirectionOffsetToViewShadow

最终实现方法

  1. FProjectionShadowInfo中添加bDirectionOffsetToViewShadow标记以及对应的判断函数IsDirectionOffsetToViewShadow()来判断是否是DirectionOffsetToViewShadow。
    1. FSceneRenderer::CreatePerObjectProjectedShadow() 中再次调用SetupPerObjectProjection()逻辑创建DirectionOffsetToViewShadow时将FProjectedShadowInfo的bDirectionOffsetToViewShadow设置成true
  2. 在PrimitiveSceneProxy.h 中添加DirectionOffsetToViewShadowAlpha变量作为偏移Alpha同事添加函数UseDirectionOffsetToViewShadow()来判断是否开启这个功能。
    1. FSceneRenderer::CreatePerObjectProjectedShadow() 中取得PrimitiveSceneProxy中的DirectionOffsetToViewShadowAlpha最后计算向量来设置ShadowInitializer.WorldToLight。大致为#DirectionOffsetToViewShadow Direction Code
  3. 在ToonDataAsset中添加RecivedViewOffsetShadow作为是否接收DirectionOffsetToViewShadow的依据。
    1. 将数据渲染到ToonDataAsset Texture中。
    2. 最终在ShadowProjectionPixelShader.usf获取并且计算。代码如下
	//BlueRose Modify
#if SHADING_PATH_DEFERRED && !FORWARD_SHADING && !SUBPIXEL_SHADOW && !STRATA_ENABLED /*&& !USE_TRANSMISSION*/
	FGBufferData GBufferData_Toon = GetGBufferData(ScreenUV);
	const uint ToonDataAssetID = GetToonDataAssetIDFromGBuffer(GBufferData_Toon);
	float RecivedViewOffsetShadow = GetRecivedViewOffsetShadow(ToonDataAssetID);
	
	if (IsDirectionOffsetToViewShadow > 0)//ViewShadow ProjectionShadowInfo
	{
		/*if (RecivedViewOffsetShadow > 0)//ViewOffsetShadow
		{
			PerObjectDistanceFadeFraction *= 1.0;
		}*/
		if (RecivedViewOffsetShadow == 0)
		{
			PerObjectDistanceFadeFraction *= 0.0;
		}
	}else//Normal ProjectionShadowInfo
	{
		if (RecivedViewOffsetShadow > 0)
		{
			PerObjectDistanceFadeFraction *= 0.0;
		}
	}
#endif
	//BlueRose Modify End
	float FadedShadow = lerp(1.0f, Shadow, ShadowFadeFraction * PerObjectDistanceFadeFraction);

#if FORWARD_SHADING || SHADING_PATH_MOBILE 
	float LightInfluenceMask = GetLightInfluenceMask(TranslateWorldPosition);
	// Constrain shadowing from this light to pixels inside the light's influence, since other non-overlapping lights are packed into the same channel
	FadedShadow = lerp(1, FadedShadow, LightInfluenceMask);
	// Write into all channels, the write mask will constrain to the correct one
	OutColor = EncodeLightAttenuation(FadedShadow);
#else
	float FadedSSSShadow = lerp(1.0f, SSSTransmission, ShadowFadeFraction * PerObjectDistanceFadeFraction);

	// the channel assignment is documented in ShadowRendering.cpp (look for Light Attenuation channel assignment)
	OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow));
#endif

DirectionOffsetToViewShadow Direction Code

...
bool bToonDirectionOffsetToViewShadow = ToonDirectionOffsetToViewShadowCVar->GetValueOnRenderThread();
float LightViewBlendFactor = PrimitiveSceneInfo->Proxy->DirectionOffsetToViewShadowAlpha;
if (bToonDirectionOffsetToViewShadow && LightSceneInfo->Proxy->GetLightType() == LightType_Directional && LightViewBlendFactor > 0.0f)
{
	//计算半程向量针对每个View都会生成一个FProjectedShadowInfo之后在RenderShadowProjection()中判断?
	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).GetSafeNormal();
		FVector HalfViewLightDir = (LightDirection * ( 1 - LightViewBlendFactor) + CameraDirection * LightViewBlendFactor).GetSafeNormal();
		FMatrix FinalCombineMatrix = FInverseRotationMatrix(HalfViewLightDir.Rotation());
		ShadowInitializer.WorldToLight = FinalCombineMatrix;
	...
	}
}

NoSelfShadow

https://zhuanlan.zhihu.com/p/10073818586?utm_psn=1877051516055076866

UPrimitiveComponent::bSelfShadowOnly => FPrimitiveSceneProxy::bSelfShadowOnly => CastsSelfShadowOnly() => FProjectedShadowInfo::bSelfShadowOnly

  • FProjectedShadowInfo::SetupMeshDrawCommandsForProjectionStenciling()设置Stencil为1。
    • FProjectedShadowInfo::GatherDynamicMeshElements()被调用FSceneRenderer::GatherShadowDynamicMeshElements()。
  • FProjectedShadowInfo::SetupProjectionStencilMask设置Stencil为7 pre-shadow/per-object static shadow
    • FProjectedShadowInfo::RenderProjectionInternal()被调用。

FProjectedShadowInfo::RenderProjectionInternal()