--- title: Untitled date: 2024-12-08 12:18:54 excerpt: tags: rating: ⭐ --- # 前言 阴影渲染笔记:[[Shadow]] 实现功能: 1. [ ] 控制深度偏移 2. [ ] CustomDepth制作头发阴影偏移效果哦 https://zhuanlan.zhihu.com/p/689578355 3. [ ] ContactShadow接触阴影实现衣服细节的DetailShadow 4. [ ] 半程阴影 # 半程阴影 由晨风&Neverwind提出: - 【[UFSH2024]用虚幻引擎5为《幻塔》定制高品质动画流程风格化渲染管线 | 晨风 Neverwind 完美世界游戏】 【精准空降到 07:27】 https://www.bilibili.com/video/BV1rW2LYvEox/?share_source=copy_web&vd_source=fe8142e8e12816535feaeabd6f6cdc8e&t=447 **阴影Setup阶段**: ```c++ if 启用半程阴影: { 额外进行一次CreatePerObjectProjectedShadow() { 处理阴影光照Matrix //猜测: //在ProjectedShadowInfo->SetupPerObjectProjection()中调整,FProjectedShadowInfo.TranslatedWorldToView。 //在LightSceneInfo->Proxy->GetPerObjectProjectedShadowInitializer(Bounds, ShadowInitializer)之后,修改WorldTolight。 向阴影信息写入IsHalfViewShadowFlag//猜测:在FProjectedShadowInfo中添加判断Flag并且写入。 用新的光源方向画Atlas //猜测:给带有对应Flog的DirectionLigh多创建一个对应的Atlas? } } ``` 截图代码(半程阴影修改LightDirection逻辑): ```c++ 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()被调用。 ```c++ 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阶段**: //此阶段需要屏蔽角色投射到自己的非半程阴影 //和角色投射到场景中会跟随视角移动的阴影 ```c++ if(Toon材质,且没有半程阴影Flag的阴影 &&非Toon材质但有半程阴影Flag的阴影) { 屏蔽此阴影 } ``` PS.很有可能在FProjectedShadowInfo::RenderProjection()阶段进行判断以此保证合成正确的**ScreenShadowMask**。 # 实现方法 ```c++ 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获取并且计算。代码如下: ```c++ //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 ```c++ ... 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()