BlueRoseNote/03-UnrealEngine/VirtualProduction/XR拍摄角色没有阴影的解决方案.md
2023-06-29 11:55:02 +08:00

227 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: XR拍摄角色没有阴影的解决方案
date: 2022-12-05 14:01:39
excerpt:
tags: XR
rating: ⭐
---
# 解决方案
在不修改引擎的情况下,解决思路有:
1. [[#手动渲染阴影]]
2. [[#使用Composure进行合成]]
修改引擎的解决思路有:
1.
## 手动渲染阴影
- 使用ShadowMapMaterialFunction插件渲染渲染阴影并且贴在一个面片上。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/XR/XR_ShadowMap_Material.png)
缺点:
1. 如果快速旋转视角,阴影会有残影效果。**原因:因为这个阴影贴图不是在同一帧渲染出来的关系。**
## 使用Composure进行合成
- 使用Composure渲染 Floor地面模型、Shadow Layer地面+角色模型再通过Shader实现阴影+角色抠像方法。
- 使用Composure渲染 Floor地面模型、Reflection Layer地面+角色模型再通过Shader实现反射+角色抠像方法。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/XR/XR_UEComposure_ShadowReflection.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/XR/XR_UEComposure_Material1.png)
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/XR/XR_UEComposure_Material2.png)
经过测试捕捉反射可以使用绿色面片作为背景最后使用Chroma进行抠像。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/XR/XR_UEComposure_Reflection_Env.png)
注意使用Composure捕捉的结果都是经过ToneMapping如果把RT再次贴到场景中进行渲染会有2次Tonemaping只是结果“发白”所以需要在几个Layer中添加Tonemap选项进行反ToneMapping。
![](https://cdn.jsdelivr.net/gh/blueroseslol/ImageBag@latest/ImageBag/XR/XR_UEComposure_Tonemap.png)
缺点:
1. 需要使用多次SceneCapture会严重降低帧数。即使只捕捉单个物体渲染消耗也和原始的渲染消耗一样。**建议把角色与Composure相关的东西放在一个子关卡中场景放在一个子关卡中然后2台机器使用SVN同步完工程之后启动对应的关卡进行推流。**
## ~~UPlanarReflectionComponent~~
核心函数:
- UPlanarReflectionComponent主要用于维护Scene的PlanarReflections数组以及数据更新标记bRegisteredReflectionCapturesHasChanged
- CreateRenderState_Concurrent()创建FPlanarReflectionSceneProxy并且调用Scene->AddPlanarReflection。
- SendRenderTransform_Concurrent()调用Scene->UpdatePlanarReflectionTransform。
- DestroyRenderState_Concurrent()调用Scene->RemovePlanarReflection(this)并且删除渲染线程的FPlanarReflectionSceneProxy。
- PostEditChangeProperty():更新可见性。
- FScene
- UpdatePlanarReflectionContents()
- 清理RT并重新初始化RT
- 计算反射平面与FSceneCaptureViewInfo主要是View与Projaction矩阵
- 构建FSceneViewFamilyContext可以理解为Capture的画布与相关View变量
- 调用FSceneRenderer::CreateSceneRenderer()创建渲染器
- 在渲染线程调用UpdatePlanarReflectionContents_RenderThread()渲染反射结果
```c++
// Reflection view late update
if (SceneRenderer->Views.Num() > 1)
{ const FMirrorMatrix MirrorMatrix(MirrorPlane);
for (int32 ViewIndex = 0; ViewIndex < SceneRenderer->Views.Num(); ++ViewIndex)
{ FViewInfo& ReflectionViewToUpdate = SceneRenderer->Views[ViewIndex];
const FViewInfo& UpdatedParentView = MainSceneRenderer->Views[ViewIndex];
ReflectionViewToUpdate.UpdatePlanarReflectionViewMatrix(UpdatedParentView, MirrorMatrix);
} }
// Render the scene normally
{
RDG_RHI_EVENT_SCOPE(GraphBuilder, RenderScene);
SceneRenderer->Render(GraphBuilder);
}
SceneProxy->RenderTarget = RenderTarget;
// Update the view rects into the planar reflection proxy.
for (int32 ViewIndex = 0; ViewIndex < SceneRenderer->Views.Num(); ++ViewIndex)
{ // Make sure screen percentage has correctly been set on render thread.
check(SceneRenderer->Views[ViewIndex].ViewRect.Area() > 0);
SceneProxy->ViewRect[ViewIndex] = SceneRenderer->Views[ViewIndex].ViewRect;
}
FRDGTextureRef ReflectionOutputTexture = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(RenderTarget->TextureRHI, TEXT("ReflectionOutputTexture")));
GraphBuilder.SetTextureAccessFinal(ReflectionOutputTexture, ERHIAccess::SRVGraphics);
FSceneTextureShaderParameters SceneTextureParameters = CreateSceneTextureShaderParameters(GraphBuilder, &SceneRenderer->GetActiveSceneTextures(), SceneRenderer->FeatureLevel, ESceneTextureSetupMode::SceneDepth);
const FMinimalSceneTextures& SceneTextures = SceneRenderer->GetActiveSceneTextures();
for (int32 ViewIndex = 0; ViewIndex < SceneRenderer->Views.Num(); ++ViewIndex)
{ FViewInfo& View = SceneRenderer->Views[ViewIndex];
RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
if (MainSceneRenderer->Scene->GetShadingPath() == EShadingPath::Deferred)
{
PrefilterPlanarReflection<true>(GraphBuilder, View, SceneTextureParameters, SceneProxy, SceneTextures.Color.Resolve, ReflectionOutputTexture);
}else
{
PrefilterPlanarReflection<false>(GraphBuilder, View, SceneTextureParameters, SceneProxy, SceneTextures.Color.Resolve, ReflectionOutputTexture);
}
}
```
可行的实现思路:
1. 将`class FPlanarReflectionRenderTarget* RenderTarget`的结果覆盖到对应的`UTextureRenderTarget2D* `中。
2. 使用`UTextureRenderTarget2D`数据来对`FPlanarReflectionRenderTarget`进行初始化。
1. 扩展FPlanarReflectionRenderTarget类添加新的构造函数将UTextureRenderTarget2D的数据来扩充内部的RT。进行手动的初始化。
2. 扩展UPlanarReflectionComponent类增加UTextureRenderTarget2D选项。
但因为RenderTarget是Private所以不修改源码的情况无法实现。
## 使用SceneCapture2D模拟UPlanarReflectionComponent
位于PlanarReflectionRendering.cpp的UpdatePlanarReflectionContents_RenderThread()
计算视锥与反射平面的代码:
```c++
const FMatrix ComponentTransform = CaptureComponent->GetComponentTransform().ToMatrixWithScale();
FPlane MirrorPlane = FPlane(ComponentTransform.TransformPosition(FVector::ZeroVector), ComponentTransform.TransformVector(FVector(0, 0, 1)));
// Normalize the plane to remove component scaling
bool bNormalized = MirrorPlane.Normalize();
if (!bNormalized)
{
MirrorPlane = FPlane(FVector(0, 0, 1), 0);
}
for (int32 ViewIndex = 0; ViewIndex < MainSceneRenderer.Views.Num() && ViewIndex < GMaxPlanarReflectionViews; ++ViewIndex)
{
const FViewInfo& View = MainSceneRenderer.Views[ViewIndex];
FSceneCaptureViewInfo NewView;
FVector2D ViewRectMin = FVector2D(View.UnscaledViewRect.Min.X, View.UnscaledViewRect.Min.Y);
FVector2D ViewRectMax = FVector2D(View.UnscaledViewRect.Max.X, View.UnscaledViewRect.Max.Y);
ViewRectMin *= FMath::Clamp(CaptureComponent->ScreenPercentage / 100.f, 0.25f, 1.f);
ViewRectMax *= FMath::Clamp(CaptureComponent->ScreenPercentage / 100.f, 0.25f, 1.f);
NewView.ViewRect.Min.X = FMath::TruncToInt(ViewRectMin.X);
NewView.ViewRect.Min.Y = FMath::TruncToInt(ViewRectMin.Y);
NewView.ViewRect.Max.X = FMath::CeilToInt(ViewRectMax.X);
NewView.ViewRect.Max.Y = FMath::CeilToInt(ViewRectMax.Y);
// Create a mirror matrix and premultiply the view transform by it
const FMirrorMatrix MirrorMatrix(MirrorPlane);
const FMatrix ViewMatrix(MirrorMatrix * View.ViewMatrices.GetViewMatrix());
const FVector ViewLocation = ViewMatrix.InverseTransformPosition(FVector::ZeroVector);
const FMatrix ViewRotationMatrix = ViewMatrix.RemoveTranslation();
const float HalfFOV = FMath::Atan(1.0f / View.ViewMatrices.GetProjectionMatrix().M[0][0]);
FMatrix ProjectionMatrix;
BuildProjectionMatrix(View.UnscaledViewRect.Size(), HalfFOV + FMath::DegreesToRadians(CaptureComponent->ExtraFOV), GNearClippingPlane, ProjectionMatrix);
NewView.ViewLocation = ViewLocation;
NewView.ViewRotationMatrix = ViewRotationMatrix;
NewView.ProjectionMatrix = ProjectionMatrix;
NewView.StereoPass = View.StereoPass;
NewView.StereoViewIndex = View.StereoViewIndex;
SceneCaptureViewInfo.Add(NewView);
}
```
```c++
void BuildProjectionMatrix(FIntPoint InRenderTargetSize, float InFOV, float InNearClippingPlane, FMatrix& OutProjectionMatrix)
{
float const XAxisMultiplier = 1.0f;
float const YAxisMultiplier = InRenderTargetSize.X / float(InRenderTargetSize.Y);
if ((int32)ERHIZBuffer::IsInverted)
{ OutProjectionMatrix = FReversedZPerspectiveMatrix(
InFOV,
InFOV,
XAxisMultiplier,
YAxisMultiplier,
InNearClippingPlane,
InNearClippingPlane
);
} else
{
OutProjectionMatrix = FPerspectiveMatrix( InFOV, InFOV, XAxisMultiplier, YAxisMultiplier, InNearClippingPlane, InNearClippingPlane ); }}
```
其他计算方式:
```c++
bool IsInFrustum( AActor* Actor)
{
ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();
if (LocalPlayer != nullptr && LocalPlayer->ViewportClient != nullptr && LocalPlayer->ViewportClient->Viewport)
{
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
LocalPlayer->ViewportClient->Viewport,
GetWorld()->Scene,
LocalPlayer->ViewportClient->EngineShowFlags)
.SetRealtimeUpdate(true));
FVector ViewLocation;
FRotator ViewRotation;
FSceneView* SceneView = LocalPlayer->CalcSceneView(&ViewFamily, ViewLocation, ViewRotation, LocalPlayer->ViewportClient->Viewport);
if (SceneView != nullptr)
{
return SceneView->ViewFrustum.IntersectSphere(
Actor->GetActorLocation(), Actor->GetSimpleCollisionRadius());
}
}
return false
}
```
### SceneCapture2D
位于SceneCaptureRendering.cpp
# 其他
## Disguise
支持的输入方式:
- sdi传输采集卡走这个协议
- ndi网络传输 (通过网卡网线)
- smpte2110协议传输显卡直接插大屏幕
## Composure相关问题
- Transforms
- Custom Material Pass
- Post Process Pass Set
- Tonemap
- Multi Pass Chroma Keyer
- Multi Pass Despill
- Outputs
- Media Capture走采集卡推流模式。
- Image Sequence
- Player Viewport可以通过在UE中实现虚拟摄像机再通过OBS推流。
- Render Target Asset
值得注意的是Transforms的Tonemap这是一个反ToneMapping效果。