vault backup: 2024-11-26 18:16:19
This commit is contained in:
158
02-Note/ASoul/流程笔记/MultiView逻辑.md
Normal file
158
02-Note/ASoul/流程笔记/MultiView逻辑.md
Normal file
@@ -0,0 +1,158 @@
|
||||
TsScreenPlayerTextureRenderer => AMultiViewActor
|
||||
|
||||
# 渲染逻辑
|
||||
- UMultiViewRendererComponent::DrawMultiViewCameras()
|
||||
|
||||
渲染函数:GetRendererModule().BeginRenderingViewFamily(&SceneCanvas, &ViewFamily);
|
||||
摄像机相关函数:FSceneView* UMultiViewRendererComponent::CalcSceneView(FSceneViewFamily* InViewFamily, UCineCameraComponent* InCamera,
|
||||
const uint32 InViewIndex)
|
||||
# 多屏与采集卡
|
||||
以Preview为例:
|
||||
TsDirectorCamManagerActor.ts
|
||||
```c++
|
||||
this.PreviewWindow = UE.MultiViewActor.Open(this.GetWorld(), UE.EMultiViewCameraLayout.Display_1920x1080_Layout_4x4, UE.EMultiViewMultiGPUMode.HalfSplit)
|
||||
this.PreviewWindow.SetRenderFeaturePlanarReflection(false);
|
||||
this.PreviewWindow.SetRenderFeatureNiagara(false);
|
||||
|
||||
// video output
|
||||
let videoOutputParam = new UE.VideOutputParam()
|
||||
videoOutputParam.bBlackMagicCard = false
|
||||
videoOutputParam.bLazyStart = false
|
||||
this.PreviewWindow.StartVideoOutput(videoOutputParam)
|
||||
```
|
||||
|
||||
PVW&PGM使用 **BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL** 也就是/Game/ResArt/BlackmagicMedia/MO_BlackmagicVideoOutput。
|
||||
```ts
|
||||
export function StartVideoOutput(camManager : TsDirectorCamManagerActor, targetWindow : UE.MultiViewActor):void{
|
||||
let videoOutpuParam = new UE.VideOutputParam()
|
||||
videoOutpuParam.FilmbackMode = camManager.FilmbackMode
|
||||
videoOutpuParam.OutputConfigPath = BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL
|
||||
videoOutpuParam.bBlackMagicCard = true
|
||||
videoOutpuParam.bLazyStart = false
|
||||
if(camManager.FilmbackMode == UE.EFilmbackMode.EFM_1080x1920){
|
||||
videoOutpuParam.MatV2H = GetV2HMaterialInstace(camManager)
|
||||
}
|
||||
targetWindow.StartVideoOutput(videoOutpuParam)
|
||||
}
|
||||
```
|
||||
|
||||
- DirectorMode.Preview:bBlackMagicCard = false
|
||||
- PVW&PGM:bBlackMagicCard = true
|
||||
|
||||
## c++
|
||||
核心函数在于**AMultiViewActor::StartVideoOutput**
|
||||
|
||||
# TS传入设置位置
|
||||
- TsDirectorCamManagerActor.ts SwitchToDirectMode(newTag: UE.GameplayTag)
|
||||
- FilmbackHelper.ts StartVideoOutput()
|
||||
|
||||
SwitchToDirectMode()
|
||||
```ts
|
||||
case DirectorMode.Preview:
|
||||
console.log('启动Splite4x4预览窗口')
|
||||
if (shouldCreateWindow) {
|
||||
this.PreviewWindow = UE.MultiViewActor.Open(this.GetWorld(), UE.EMultiViewCameraLayout.Display_1920x1080_Layout_4x4, UE.EMultiViewMultiGPUMode.HalfSplit)
|
||||
this.PreviewWindow.SetRenderFeaturePlanarReflection(false);
|
||||
this.PreviewWindow.SetRenderFeatureNiagara(false);
|
||||
|
||||
// video output
|
||||
let videoOutputParam = new UE.VideOutputParam()
|
||||
videoOutputParam.bBlackMagicCard = false
|
||||
videoOutputParam.bLazyStart = false
|
||||
this.PreviewWindow.StartVideoOutput(videoOutputParam)
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
function StartVideoOutput(camManager : TsDirectorCamManagerActor, targetWindow : UE.MultiViewActor):void{
|
||||
if(!BE_USE_DECKLINK){
|
||||
let videoOutpuParam = new UE.VideOutputParam()
|
||||
videoOutpuParam.FilmbackMode = camManager.FilmbackMode
|
||||
videoOutpuParam.OutputConfigPath = BLACKMAGIC_OUTPUT_CONFIG_HORIZONTAL
|
||||
videoOutpuParam.bBlackMagicCard = true
|
||||
videoOutpuParam.bLazyStart = false
|
||||
if(camManager.FilmbackMode == UE.EFilmbackMode.EFM_1080x1920){
|
||||
videoOutpuParam.MatV2H = GetV2HMaterialInstace(camManager)
|
||||
}
|
||||
targetWindow.StartVideoOutput(videoOutpuParam)
|
||||
}
|
||||
}
|
||||
```
|
||||
# UDeckLinkMediaCapture
|
||||
m_DeckLinkOutputDevice = DeckLinkDiscovery->GetDeviceByName(m_DeviceName);
|
||||
|
||||
```c++
|
||||
FDeckLinkDeviceDiscovery::DeckLinkDeviceArrived(IDeckLink *device)
|
||||
{
|
||||
/*TComPtr<IDeckLink> device_;
|
||||
device_ = device;*/
|
||||
|
||||
TComPtr<FDeckLinkOutputDevice> newDeviceComPtr = new FDeckLinkOutputDevice(device);
|
||||
if (!newDeviceComPtr->Init())
|
||||
return S_OK;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(m_DeviceMutex);
|
||||
FString deviceName = newDeviceComPtr->GetDeviceName();//看这个Com对象的设备名称是否对应
|
||||
if (!m_Devices.Contains(deviceName) )
|
||||
{
|
||||
m_Devices.Add(deviceName,newDeviceComPtr);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
```
|
||||
|
||||
DeckLinkDeviceArrived调用逻辑位于**DeckLinkAPI_h.h**。
|
||||
|
||||
## ADeckLinkOutputActor
|
||||
ADeckLinkOutputActor的DeviceName 默认值为"DeckLink Mini Monitor 4K";
|
||||
|
||||
判断错误函数
|
||||
UMediaCapture::CaptureTextureRenderTarget2D()=>UMediaCapture::StartSourceCapture() => ValidateMediaOutput()
|
||||
```c++
|
||||
bool UMediaCapture::ValidateMediaOutput() const
|
||||
{
|
||||
if (MediaOutput == nullptr)
|
||||
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. The Media Output is invalid."));
|
||||
return false;
|
||||
}
|
||||
FString FailureReason;
|
||||
if (!MediaOutput->Validate(FailureReason))
|
||||
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. %s."), *FailureReason);
|
||||
return false;
|
||||
}
|
||||
if(DesiredCaptureOptions.bAutostopOnCapture && DesiredCaptureOptions.NumberOfFramesToCapture < 1)
|
||||
{ UE_LOG(LogMediaIOCore, Error, TEXT("Can not start the capture. Please set the Number Of Frames To Capture when using Autostop On Capture in the Media Capture Options"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
bool UDeckLinkMediaCapture::InitBlackmagic(int _Width, int _Height)
|
||||
{
|
||||
if (DeckLinkDiscovery == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Width = _Width;
|
||||
Height = _Height;
|
||||
check(Height > 0 && Width > 0)
|
||||
BMDDisplayMode displayMode = GetDisplayMode(Width, Height);
|
||||
m_DeckLinkOutputDevice = DeckLinkDiscovery->GetDeviceByName(m_DeviceName);
|
||||
if (m_DeckLinkOutputDevice.Get() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!m_DeckLinkOutputDevice->EnableOutput(displayMode, bmdFormat8BitYUV))
|
||||
{
|
||||
m_DeckLinkOutputDevice.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
***DeckLinkDiscovery->GetDeviceByName(m_DeviceName);***
|
266
02-Note/ASoul/流程笔记/VJ播放.md
Normal file
266
02-Note/ASoul/流程笔记/VJ播放.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# 相关蓝图类
|
||||
BP_Live:里面可以指定MediaPlayer以及MediaTexture,并且替换蓝图子StaticMesh材质中的EmissiveMap为MediaTexture。
|
||||
|
||||
# 导播台
|
||||
之后就可以将视频放到指定的Saved文件夹里,就可以在导播台播放了。
|
||||
|
||||
|
||||
# NDI 播放逻辑
|
||||
通过道具来添加NDI 设置。
|
||||
## 道具
|
||||
- BP_ProjectorD0
|
||||
- BP_Screen011
|
||||
|
||||
## 相关注释掉的代码
|
||||
- TsMapEnvironmentAssets.ts
|
||||
- TsMapEnvironmentSingleSelectItemView.ts
|
||||
- SetMediaData()
|
||||
- TsScreenPlayerItemView.ts
|
||||
- SetData()
|
||||
- TsScreenPlayerSelectItemPopupView.ts
|
||||
- ChangeMediaType()
|
||||
|
||||
# NDI播放模糊问题解决
|
||||
- bool UNDIMediaReceiver::CaptureConnectedVideo()
|
||||
|
||||
|
||||
```c++
|
||||
bool UNDIMediaReceiver::Initialize(const FNDIConnectionInformation& InConnectionInformation, UNDIMediaReceiver::EUsage InUsage)
|
||||
{
|
||||
if (this->p_receive_instance == nullptr)
|
||||
{
|
||||
if (IsValid(this->InternalVideoTexture))
|
||||
this->InternalVideoTexture->UpdateResource();
|
||||
|
||||
// create a non-connected receiver instance
|
||||
NDIlib_recv_create_v3_t settings;
|
||||
settings.allow_video_fields = false;
|
||||
settings.bandwidth = NDIlib_recv_bandwidth_highest;
|
||||
settings.color_format = NDIlib_recv_color_format_fastest;
|
||||
|
||||
p_receive_instance = NDIlib_recv_create_v3(&settings);
|
||||
|
||||
// check if it was successful
|
||||
if (p_receive_instance != nullptr)
|
||||
{
|
||||
// If the incoming connection information is valid
|
||||
if (InConnectionInformation.IsValid())
|
||||
{
|
||||
//// Alright we created a non-connected receiver. Lets actually connect
|
||||
ChangeConnection(InConnectionInformation);
|
||||
}
|
||||
|
||||
if (InUsage == UNDIMediaReceiver::EUsage::Standalone)
|
||||
{
|
||||
this->OnNDIReceiverVideoCaptureEvent.Remove(VideoCaptureEventHandle);
|
||||
VideoCaptureEventHandle = this->OnNDIReceiverVideoCaptureEvent.AddLambda([this](UNDIMediaReceiver* receiver, const NDIlib_video_frame_v2_t& video_frame)
|
||||
{
|
||||
FTextureRHIRef ConversionTexture = this->DisplayFrame(video_frame);
|
||||
if (ConversionTexture != nullptr)
|
||||
{
|
||||
if ((GetVideoTextureResource() != nullptr) && (GetVideoTextureResource()->TextureRHI != ConversionTexture))
|
||||
{
|
||||
GetVideoTextureResource()->TextureRHI = ConversionTexture;
|
||||
RHIUpdateTextureReference(this->VideoTexture->TextureReference.TextureReferenceRHI, ConversionTexture);
|
||||
}
|
||||
if ((GetInternalVideoTextureResource() != nullptr) && (GetInternalVideoTextureResource()->TextureRHI != ConversionTexture))
|
||||
{
|
||||
GetInternalVideoTextureResource()->TextureRHI = ConversionTexture;
|
||||
RHIUpdateTextureReference(this->InternalVideoTexture->TextureReference.TextureReferenceRHI, ConversionTexture);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// We don't want to limit the engine rendering speed to the sync rate of the connection hook
|
||||
// into the core delegates render thread 'EndFrame'
|
||||
FCoreDelegates::OnEndFrameRT.Remove(FrameEndRTHandle);
|
||||
FrameEndRTHandle.Reset();
|
||||
FrameEndRTHandle = FCoreDelegates::OnEndFrameRT.AddLambda([this]()
|
||||
{
|
||||
while(this->CaptureConnectedMetadata())
|
||||
; // Potential improvement: limit how much metadata is processed, to avoid appearing to lock up due to a metadata flood
|
||||
this->CaptureConnectedVideo();
|
||||
});
|
||||
|
||||
#if UE_EDITOR
|
||||
// We don't want to provide perceived issues with the plugin not working so
|
||||
// when we get a Pre-exit message, forcefully shutdown the receiver
|
||||
FCoreDelegates::OnPreExit.AddWeakLambda(this, [&]() {
|
||||
this->Shutdown();
|
||||
FCoreDelegates::OnPreExit.RemoveAll(this);
|
||||
});
|
||||
|
||||
// We handle this in the 'Play In Editor' versions as well.
|
||||
FEditorDelegates::PrePIEEnded.AddWeakLambda(this, [&](const bool) {
|
||||
this->Shutdown();
|
||||
FEditorDelegates::PrePIEEnded.RemoveAll(this);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
绘制函数
|
||||
```c++
|
||||
/**
|
||||
Attempts to immediately update the 'VideoTexture' object with the last capture video frame
|
||||
from the connected source
|
||||
*/
|
||||
FTextureRHIRef UNDIMediaReceiver::DisplayFrame(const NDIlib_video_frame_v2_t& video_frame)
|
||||
{
|
||||
// we need a command list to work with
|
||||
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
|
||||
|
||||
// Actually draw the video frame from cpu to gpu
|
||||
switch(video_frame.frame_format_type)
|
||||
{
|
||||
case NDIlib_frame_format_type_progressive:
|
||||
if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVY)
|
||||
return DrawProgressiveVideoFrame(RHICmdList, video_frame);
|
||||
else if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVA)
|
||||
return DrawProgressiveVideoFrameAlpha(RHICmdList, video_frame);
|
||||
break;
|
||||
case NDIlib_frame_format_type_field_0:
|
||||
case NDIlib_frame_format_type_field_1:
|
||||
if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVY)
|
||||
return DrawInterlacedVideoFrame(RHICmdList, video_frame);
|
||||
else if(video_frame.FourCC == NDIlib_FourCC_video_type_UYVA)
|
||||
return DrawInterlacedVideoFrameAlpha(RHICmdList, video_frame);
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
```
|
||||
|
||||
DrawProgressiveVideoFrame
|
||||
|
||||
|
||||
UNDIMediaReceiver::CaptureConnectedVideo
|
||||
=>
|
||||
DisplayFrame NDIlib_frame_format_type_progressive NDIlib_FourCC_video_type_UYVY
|
||||
=>
|
||||
DrawProgressiveVideoFrame
|
||||
|
||||
## Shader Binding RT
|
||||
设置RT:
|
||||
```c++
|
||||
FTextureRHIRef TargetableTexture;
|
||||
|
||||
// check for our frame sync object and that we are actually connected to the end point
|
||||
if (p_framesync_instance != nullptr)
|
||||
{
|
||||
// Initialize the frame size parameter
|
||||
FIntPoint FrameSize = FIntPoint(Result.xres, Result.yres);
|
||||
|
||||
if (!RenderTarget.IsValid() || !RenderTargetDescriptor.IsValid() ||
|
||||
RenderTargetDescriptor.GetSize() != FIntVector(FrameSize.X, FrameSize.Y, 0) ||
|
||||
DrawMode != EDrawMode::Progressive)
|
||||
{
|
||||
// Create the RenderTarget descriptor
|
||||
RenderTargetDescriptor = FPooledRenderTargetDesc::Create2DDesc(
|
||||
FrameSize, PF_B8G8R8A8, FClearValueBinding::None, TexCreate_None, TexCreate_RenderTargetable | TexCreate_SRGB, false);
|
||||
|
||||
// Update the shader resource for the 'SourceTexture'
|
||||
// The source texture will be given UYVY data, so make it half-width
|
||||
#if (ENGINE_MAJOR_VERSION > 5) || ((ENGINE_MAJOR_VERSION == 5) && (ENGINE_MINOR_VERSION >= 1))
|
||||
const FRHITextureCreateDesc CreateDesc = FRHITextureCreateDesc::Create2D(TEXT("NDIMediaReceiverProgressiveSourceTexture"))
|
||||
.SetExtent(FrameSize.X / 2, FrameSize.Y)
|
||||
.SetFormat(PF_B8G8R8A8)
|
||||
.SetNumMips(1)
|
||||
.SetFlags(ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Dynamic);
|
||||
|
||||
SourceTexture = RHICreateTexture(CreateDesc);
|
||||
#elif (ENGINE_MAJOR_VERSION == 4) || (ENGINE_MAJOR_VERSION == 5)
|
||||
FRHIResourceCreateInfo CreateInfo(TEXT("NDIMediaReceiverProgressiveSourceTexture"));
|
||||
TRefCountPtr<FRHITexture2D> DummyTexture2DRHI;
|
||||
RHICreateTargetableShaderResource2D(FrameSize.X / 2, FrameSize.Y, PF_B8G8R8A8, 1, TexCreate_Dynamic,
|
||||
TexCreate_RenderTargetable, false, CreateInfo, SourceTexture,
|
||||
DummyTexture2DRHI);
|
||||
#else
|
||||
#error "Unsupported engine major version"
|
||||
#endif
|
||||
|
||||
// Find a free target-able texture from the render pool
|
||||
GRenderTargetPool.FindFreeElement(RHICmdList, RenderTargetDescriptor, RenderTarget, TEXT("NDIIO"));
|
||||
|
||||
DrawMode = EDrawMode::Progressive;
|
||||
}
|
||||
|
||||
#if ENGINE_MAJOR_VERSION >= 5
|
||||
TargetableTexture = RenderTarget->GetRHI();
|
||||
#elif ENGINE_MAJOR_VERSION == 4
|
||||
TargetableTexture = RenderTarget->GetRenderTargetItem().TargetableTexture;
|
||||
...
|
||||
...
|
||||
// Initialize the Render pass with the conversion texture
|
||||
FRHITexture* ConversionTexture = TargetableTexture.GetReference();
|
||||
FRHIRenderPassInfo RPInfo(ConversionTexture, ERenderTargetActions::DontLoad_Store);
|
||||
|
||||
// Needs to be called *before* ApplyCachedRenderTargets, since BeginRenderPass is caching the render targets.
|
||||
RHICmdList.BeginRenderPass(RPInfo, TEXT("NDI Recv Color Conversion"));
|
||||
```
|
||||
|
||||
设置NDI传入的UYVY:
|
||||
```c++
|
||||
// set the texture parameter of the conversion shader
|
||||
FNDIIOShaderUYVYtoBGRAPS::Params Params(SourceTexture, SourceTexture, FrameSize,
|
||||
FVector2D(0, 0), FVector2D(1, 1),
|
||||
bPerformsRGBtoLinear ? FNDIIOShaderPS::EColorCorrection::sRGBToLinear : FNDIIOShaderPS::EColorCorrection::None,
|
||||
FVector2D(0.f, 1.f));
|
||||
ConvertShader->SetParameters(RHICmdList, Params);
|
||||
|
||||
// Create the update region structure
|
||||
FUpdateTextureRegion2D Region(0, 0, 0, 0, FrameSize.X/2, FrameSize.Y);
|
||||
|
||||
// Set the Pixel data of the NDI Frame to the SourceTexture
|
||||
RHIUpdateTexture2D(SourceTexture, 0, Region, Result.line_stride_in_bytes, (uint8*&)Result.p_data);
|
||||
```
|
||||
|
||||
## 解决方案
|
||||
[NDI plugin质量问题](https://forums.unrealengine.com/t/ndi-plugin-quality-trouble/1970097)
|
||||
|
||||
I changed only shader “NDIIO/Shaders/Private/NDIIOShaders.usf”.
|
||||
For example function **void NDIIOUYVYtoBGRAPS (// Shader from 8 bits UYVY to 8 bits RGBA (alpha set to 1)):**
|
||||
|
||||
_WAS:_
|
||||
|
||||
```c++
|
||||
float4 UYVYB = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerB, InUV);
|
||||
float4 UYVYT = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV);
|
||||
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
|
||||
float4 YUVA;
|
||||
float FracX = PosX % 2.0f;
|
||||
YUVA.x = (1 - FracX) * UYVYT.y + FracX * UYVYT.w;
|
||||
YUVA.yz = UYVYB.zx;
|
||||
YUVA.w = 1;
|
||||
```
|
||||
|
||||
_I DID:_
|
||||
|
||||
```c++
|
||||
float4 UYVYB = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerB, InUV);
|
||||
float4 UYVYT0 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV + float2(-0.25f / NDIIOShaderUB.InputWidth, 0));
|
||||
float4 UYVYT1 = NDIIOShaderUB.InputTarget.Sample(NDIIOShaderUB.SamplerT, InUV + float2(0.25f / NDIIOShaderUB.InputWidth, 0));
|
||||
float PosX = 2.0f * InUV.x * NDIIOShaderUB.InputWidth;
|
||||
float4 YUVA;
|
||||
float FracX = (PosX % 2.0f) * 0.5f;
|
||||
YUVA.x = (1 - FracX) * UYVYT1.y + FracX * UYVYT0.w;
|
||||
YUVA.yz = UYVYB.zx;
|
||||
YUVA.w = 1;
|
||||
```
|
||||
|
||||
Small changes but result is seems much more better.
|
||||
Of course, I added a bit of sharpness to the material after I changed the shader, but even without that, the result looks better than in the original version.
|
||||
|
||||
滤波资料:https://zhuanlan.zhihu.com/p/633122224
|
||||
## UYVY(YUV422)
|
||||
- https://zhuanlan.zhihu.com/p/695302926
|
||||
- https://blog.csdn.net/gsp1004/article/details/103037312
|
||||

|
50
02-Note/ASoul/流程笔记/场景流程&镜头流程.md
Normal file
50
02-Note/ASoul/流程笔记/场景流程&镜头流程.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 前言
|
||||
1. 角色需要`BP_LiveArea`(LiveAreaActor)
|
||||
2. Sequence与Camera需要`CameraRoot`Actor(需要与LiveArea完全重叠)
|
||||
1. ~~FollowMovementComponent编写各种相机跟踪物体(IdolName、Socket、Bone)~~
|
||||
2. 摄像机挂载FollowingComponment。
|
||||
|
||||
# LiveDirector
|
||||
|
||||
## BP_LiveArea
|
||||
基类为**ALiveAreaActor**,位于Source/LiveDirector/DirectorFramework/LiveAreaActor.h。直播区域占位用Actor,可以用于定义:
|
||||
- WeatherOverride:进入该区域后天气系统重载。
|
||||
- WeatherBlendDuration:天气系统重载过渡时间。
|
||||
- CareLayers:该区域加载时会自动加载关联的 DataLayer 层级。
|
||||
|
||||
# DirectorCam
|
||||
## Core
|
||||
### BP_CamPlacement_LiveArea
|
||||
一般挂载在**BP_LiveArea**下面。使用LiveDirector - DirectorCam - Template下的模板生成。继承关系为**BP_CamWorkShopPlacement -> ACamWorkShopPlacementActor**,位于Modules/DirectorCam/Core/CamWorkShopPlacementActor.h
|
||||
|
||||
## Data
|
||||
### UDirectorCamGroupData
|
||||
镜头组数据资产,里面存储***各个镜头LevelSequence***。
|
||||
|
||||
### UDirectorCamSetupData
|
||||
里面存储各种***UDirectorCamGroupData***。
|
||||
|
||||
# LevelSequences
|
||||
LevelSequence的帧数为30fps。
|
||||
|
||||
- DirectorCamSetupData
|
||||
- Example5400x4800(大动捕室):/Game/LevelSequences/Example5400x4800/CamSetup_5400x4800
|
||||
- DirectorCamGroupData:/Game/LevelSequences/Example5400x4800/General
|
||||
- Dance
|
||||
|
||||
## 创建一个新镜头的步骤
|
||||
***推荐直接复制之前制作的镜头,并在此基础上进行编辑。***
|
||||
|
||||
1. 在LevelSequences目录下新建一个LevelSequence资产。
|
||||
2. 拖入角色蓝图资产到到LevelSequence中(Spawnable),并且Attach指定的**LiveArea**Actor下,对齐直播场景的原点。拖入SoundBase资产到LevelSequence中(可选)。
|
||||
3. 角色蓝图添加CharacterMesh轨道,并且给CharacterMesh轨道指定对应的AnimationSequence资产。
|
||||
4. 添加摄像机
|
||||
1. CinemaCamera
|
||||
1. 如果需要追踪则给CinemaCamera,添加FollowingMovement组件以及轨道,并且指定组件的FollowingActor为对应Actor,并且***填写FollowingIdolName***(例如Idol.BeiLa)。以及FollowingSocketName
|
||||
2. DirectorCamera为场景相关的相机(静态镜头)
|
||||
1. Sequence中添加DirectorCamera,之后Attach到CameraRoot下。
|
||||
2. 如果需要追踪则给DirectorCamera,添加FollowingMovement组件以及轨道,并且指定组件的FollowingActor为对应Actor,并且***填写FollowingIdolName***(例如Idol.BeiLa)。以及FollowingSocketName
|
||||
5. 将LevelSequence添加到对应的`UDirectorCamGroupData`资产中。
|
||||
6. 将`UDirectorCamGroupData`放入到对应`UDirectorCamGroupData`资产中。
|
||||
7. 点击ASoul工具-配置ShotGroupBroad通用按钮。配置StreamDock面部按钮以及图标。
|
||||
|
11
02-Note/ASoul/流程笔记/大世界添加笔记.md
Normal file
11
02-Note/ASoul/流程笔记/大世界添加笔记.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 添加步骤
|
||||
1. 在Maps文件中构建**Map_xxx**文件夹以及同名称的大世界Map。
|
||||
2. 在地图中间添加LiveArea,并且设置标题。
|
||||
3. 添加Config/LiveDirectorAsset/LevelAreaConfig.json配置。
|
||||
|
||||
## 相关资产存放位置
|
||||
- 在Maps文件中构建**Map_xxx**文件夹以及同名称的大世界Map。
|
||||
- **Maps/Scenes/Map_xxx**存放对应大世界的资产。
|
||||
- Maps/Epic存放官方商城下载的资源。
|
||||
- UIAssets/MapEnvironments/ICON_/Area存放LiveArea位置的预览图标
|
||||
- UIAssets/MapEnvironments/ICON_/Level存放对应Map或者大世界的预览图标
|
1
02-Note/ASoul/流程笔记/环境&四季流程.md
Normal file
1
02-Note/ASoul/流程笔记/环境&四季流程.md
Normal file
@@ -0,0 +1 @@
|
||||
BP_ProjectV_EnvironmentBlendable
|
149
02-Note/ASoul/流程笔记/角色流程.md
Normal file
149
02-Note/ASoul/流程笔记/角色流程.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# 前言
|
||||
继承关系:BP_XXX_Base -> BP_Idol_Base -> TsIdolActor -> AVCharacter -> ACharacter 。
|
||||
主要逻辑位于TsIdolActor中,文件路径为`Script/LiveDirector/Character/TsIdolActor.ts`
|
||||
|
||||
# 添加新衣服流程
|
||||
1. 在Content/Character/XXX中创建继承自BP_XXX_Base的蓝图。
|
||||
2. 设置SkeletalMesh。并且确保勾选了**Dynamic InsetShadow**。
|
||||
3. 添加PhysicalAsset,并且保证胶囊体大致包裹模型,否则阴影会出现被裁剪的问题。
|
||||
4. 添加对应的OutlineMaterial。
|
||||
5. 在DressName中输入新衣服的显示名称。
|
||||
6. 添加Prop标签。
|
||||
1. Idol.XXX
|
||||
2. Prop.Dress(第一件衣服需要设置成Prop.Dress.Default)
|
||||
3. Prop.MountPoint.Body
|
||||
7. ***脚本扫描***:点击编辑器的嘉然头像->角色道具配置全量生成 or 角色道具配置增量生成。会往IdolPropAssetConfig.json添加对应衣服或者道具配置数据。
|
||||
# 添加新角色流程笔记
|
||||
1. 添加一个Idol.xxx标签。
|
||||
2. 修改下面相关文件[[#含有角色标签的文件]]。
|
||||
3. 添加对应的蓝图与文件结构。
|
||||
1. Content/Character
|
||||
1. 在Idol_Base中添加[[#BP_XXX_Base]]。
|
||||
2. 设置衣服、头发的SkeletalMesh。并且确保勾选了**Dynamic InsetShadow**。
|
||||
3. 添加角色、衣服、头发的PhysicalAsset,并且保证胶囊体大致包裹模型,否则阴影会出现被裁剪的问题。
|
||||
2. Content/ResArt/CharacterArt:放置角色与服装,按照
|
||||
1. 动画蓝图中的***FullBody节点需要设置角色标签***。
|
||||
1. 指定用于修型的PostProcess动画蓝图。
|
||||
2. 添加Prop标签。
|
||||
1. Idol.XXX
|
||||
2. Prop.Dress
|
||||
3. Prop.MountPoint.Body
|
||||
3. ***脚本扫描***:点击编辑器的嘉然头像->角色道具配置全量生成 or 角色道具配置增量生成。会往IdolPropAssetConfig.json添加对应衣服或者道具配置数据。
|
||||
4. 设置道具挂载信息数据表:ResArt/MountPointConfig/DT_MountPointConfig:用于设置道具挂载时的相对偏移。
|
||||
5. ***材质相关操作***:
|
||||
1. 在ResArt/CommonMaterial/Functions/CameraLightCollection中添加对应角色的属性。
|
||||
2. 在ResArt/CommonMaterial/Functions/MF_CharacterMainLightIntensity中添加对应RoleID。
|
||||
3. 在ResArt/CommonMaterial/Functions/MF_CharacterRimLightIntensity中添加对应RoleID。
|
||||
4. 在对应角色的基础材质中设置RoleID数值。
|
||||
5. 调用Python脚本制作Dissolve材质。LiveDirector/Editor/MaterialMigration/MakeDissolveMaterials.py
|
||||
|
||||
## 含有角色标签的文件
|
||||
1. [x] TsCharacterItem.ts `Script/LiveDirector/Character/View/TsCharacterItem.ts`
|
||||
1. 3级角色控制界面相关的UI操作。
|
||||
2. [x] TsCharacterMocapViewTmp.ts :这个是MotionProcess的UI,继承控件`/Content/UIAssets/Character/Mocap/WBP_CharacterMocapViewTmp`
|
||||
1. 在MotionProcess专用地图中创建对应的Idol。
|
||||
3. [x] TsPropMocapItemTmp.ts
|
||||
1. 在MotionProcess专用地图中控制道具Attach到对应Idol(UI逻辑)
|
||||
4. [x] TsDirectorConsoleCommandHandler.ts
|
||||
1. 快捷命令 Motion同步相关 GetMotionOffset
|
||||
2. 快捷命令 快速创建4个角色 IdolCostume
|
||||
5. [x] TsSpawnPointSettingItem.ts
|
||||
1. IdolItemUI,继承控件`/Content/UIAssets/Character/WBP_SpawnPointSettingItem`
|
||||
6. [x] TsIdolPropManagerComponent.ts
|
||||
1. 没有思诺与心怡
|
||||
2. 需要搞清楚。
|
||||
7. [x] ~~TsSimpleLevelManager.ts~~
|
||||
1. SwitchLiveArea()中调用,只调用了Idol.BeiLa,属于容错语句。
|
||||
8. ~~CameraDebug.cpp ~~(这个不需求)
|
||||
|
||||
## BP_XXX_Base
|
||||
1. 指定动画蓝图。
|
||||
2. 指定LiveLinkName。
|
||||
3. 指定OutlineMaterial。
|
||||
|
||||
# AVCharacter
|
||||
主要实现了`virtual void OnRep_AttachmentReplication() override;`,声明了若干BlueprintNativeEvent:
|
||||
- bool CanSyncRelativeTransform();
|
||||
- void BeforeAttachToNewParent();
|
||||
- void AfterAttachToNewParent();
|
||||
|
||||
## OnRep_AttachmentReplication()
|
||||
注释:
|
||||
>// 动捕模式下,CanSync=false. 各端自行计算Actor Location, client无需使用Server计算结果
|
||||
// 自由行走模式下, CanSync=true,client需要同步server的transform信息。
|
||||
|
||||
同步Attachment行为。在AActor::OnRep_AttachmentReplication()的基础上添加:
|
||||
- 判断CanSync标记,以此来决定是否同步Transform
|
||||
- 未Attach组件=>Attch组件前后添加BeforeAttachToNewParent()、AfterAttachToNewParent()
|
||||
```c++
|
||||
auto CanSync = CanSyncRelativeTransform(); //获取Sync标记,具体的逻辑位于TsIdolActor.ts中
|
||||
if (attachmentReplication.AttachParent)
|
||||
{
|
||||
if (RootComponent)
|
||||
{
|
||||
USceneComponent* AttachParentComponent = (attachmentReplication.AttachComponent ? attachmentReplication.AttachComponent : attachmentReplication.AttachParent->GetRootComponent());
|
||||
if (AttachParentComponent)
|
||||
{
|
||||
if(CanSync)//增加判断Sync判断,只有在自由行走模式下才会同步Transform。
|
||||
{
|
||||
RootComponent->SetRelativeLocation_Direct(attachmentReplication.LocationOffset);
|
||||
RootComponent->SetRelativeRotation_Direct(attachmentReplication.RotationOffset);
|
||||
RootComponent->SetRelativeScale3D_Direct(attachmentReplication.RelativeScale3D);
|
||||
}
|
||||
|
||||
// If we're already attached to the correct Parent and Socket, then the update must be position only.
|
||||
// AttachToComponent would early out in this case.
|
||||
// Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated
|
||||
// if the body is simulated (see AActor::GatherMovement).
|
||||
const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && attachmentReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent));
|
||||
if (bAlreadyAttached)
|
||||
{
|
||||
// Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above).
|
||||
if(CanSync)
|
||||
{
|
||||
RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BeforeAttachToNewParent();//增加BlueprintNativeEvent
|
||||
RootComponent->AttachToComponent(AttachParentComponent, FAttachmentTransformRules::KeepRelativeTransform, attachmentReplication.AttachSocket);
|
||||
AfterAttachToNewParent();//增加BlueprintNativeEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# TsIdolActor.ts
|
||||
## VirtualOverrider
|
||||
CanSyncRelativeTransform()
|
||||
- 不需要同步Transform的情况
|
||||
- AI控制的ACao角色不需要同步。
|
||||
- 使用TsIdolMovementComponent并且勾选了ManulMovement的情况不需要同步。
|
||||
- 动画蓝图中使用了**AnimGraphNode_Fullbody**节点,并且bGetMotionData为true的情况不需要同步。
|
||||
|
||||
具体代码如下:
|
||||
```typescript
|
||||
CanSyncRelativeTransform(): boolean {
|
||||
if (Utils.HasTag(this.PropTags, new UE.GameplayTag("Idol.AIACao"))) {
|
||||
return false;
|
||||
}
|
||||
if(this.MovementComp && this.MovementComp.ManulMovement){
|
||||
return false
|
||||
}
|
||||
var animInstance = this.Mesh.GetAnimInstance() as UE.IdolAnimInstance
|
||||
let fullbodyNode = Reflect.get(animInstance, 'AnimGraphNode_Fullbody') as UE.AnimNode_FullBody
|
||||
return !(fullbodyNode && fullbodyNode.bGetMotionData)
|
||||
}
|
||||
```
|
||||
|
||||
# Prop.Dress.Default
|
||||
1. TsIdolPropManagerComponent.ts ServerLoadProp()
|
||||
2. DoLoadProp()
|
||||
3. ServerDoLoadPropPreset()
|
||||
4. GetPropPreset()
|
||||
5. GetDefaultDress():取得DefaultDress标签字符串。
|
||||
6. GetPropAssetConfigsByTags(tags):根据标签取得对应的资产配置(UPropAssetConfig)
|
||||
|
||||
扫描所有资产:TsPropAssetManager.ts CollectAllAssets()
|
18
02-Note/ASoul/流程笔记/调试启动流程.md
Normal file
18
02-Note/ASoul/流程笔记/调试启动流程.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# 原始流程
|
||||
1. StartListenServer_(异世界).bat
|
||||
2. MotionServer.exe
|
||||
3. StartClient_MotionProcessor.bat
|
||||
4. Edior
|
||||
5. MotionProcessor 设置IP 动作传输
|
||||
6. Editor Open IP
|
||||
7. 4级添加地图
|
||||
8. 3级添加角色
|
||||
9. 在StartListenServer中运行run pvw
|
||||
|
||||
# 改进流程
|
||||
1. 启动Editor,并且以专用服务模式启动。(Play As Listen Server)
|
||||
2. MotionServer.exe
|
||||
3. StartClient_MotionProcessor.bat
|
||||
4. MotionProcessor 设置IP 动作传输
|
||||
5. 4级添加地图
|
||||
6. 3级添加角色
|
106
02-Note/ASoul/流程笔记/道具流程.md
Normal file
106
02-Note/ASoul/流程笔记/道具流程.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# 相关类
|
||||
- TsPropActor
|
||||
- TsIdolPropActor
|
||||
- TsScenePropActor
|
||||
- TsPropEffectActor
|
||||
|
||||
# 相关资产
|
||||
ResArt/MountPointConfig/DT_MountPointConfig:用于设置道具挂载时的相对偏移。
|
||||
# MountPoint
|
||||
GameplayTag中有定义相关Prop的挂载位置标签:
|
||||
- Prop
|
||||
- MountPoint
|
||||
- Back
|
||||
- Body
|
||||
- Feet
|
||||
- Head
|
||||
- HeadBottom
|
||||
- HeadUp
|
||||
- Hips
|
||||
- LeftFoot
|
||||
- LeftHand
|
||||
- RightFoot
|
||||
- RightHand
|
||||
- Root
|
||||
|
||||
对应逻辑TsPropAssetManager.ts中的枚举,查找函数为GetMountPointIndexByTagName():
|
||||
```ts
|
||||
export const enum MountPointEnum {
|
||||
HeadUp,
|
||||
Head,
|
||||
HeadDown,
|
||||
LeftHand,
|
||||
RightHand,
|
||||
Back,
|
||||
Feet,
|
||||
Hips
|
||||
}
|
||||
```
|
||||
|
||||
TsIdolPropManagerComponent.ts
|
||||
AttachAllProp() => AttachProp()
|
||||
|
||||
TsPropAssetManager.ts
|
||||
```ts
|
||||
static GetMountPointName(Tag: UE.GameplayTag): string {
|
||||
if (Tag.TagName.startsWith('Prop.MountPoint')) {
|
||||
let res = Tag.TagName.split('.')
|
||||
return res[res.length - 1]
|
||||
}
|
||||
return ''
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# 换装代码调用步骤
|
||||
PrepareLoadNewModel_Multicast
|
||||
ServerDoLoadPropPreset() => ServerStartSwitchPreset() => ServerLoadProp() => DoLoadProp() => LoadDressByConfig()
|
||||
|
||||
- TsDirectorController.ts CreateIdol()
|
||||
- TsIdolControllerActor.ServerCreateIdolControllerAtLiveArea
|
||||
- controller.PropComp.ServerLoadPropPreset(0)
|
||||
- TsIdolPropManagerComponent
|
||||
- ServerLoadPropPreset()
|
||||
- ServerLoadProp()
|
||||
- DoLoadProp()
|
||||
- LoadDressByConfig
|
||||
|
||||
LocalLoadDressByConfig() 本地加载。
|
||||
LoadDressByConfig() 服务器加载。
|
||||
|
||||
# 角色衣服套装预设切换逻辑
|
||||
在WBP_CharacterItem中5个按钮BtnSuit_1、BtnSuit_2、BtnSuit_3、BtnSuit_4、BtnSuit_5会调用EventOnPresetClicked。
|
||||
|
||||
```ts
|
||||
EventOnPresetClicked(PresetIndex: number, IsDoubleClick: boolean):void {
|
||||
let curTime = UE.KismetSystemLibrary.GetGameTimeInSeconds(this)
|
||||
if (curTime - this.LastLoadPresetTime < 0.5) {
|
||||
console.warn('Click too fast , please try again later')
|
||||
return
|
||||
}
|
||||
this.LastLoadPresetTime = curTime;
|
||||
if (IsDoubleClick) {
|
||||
this.LoadPreset(PresetIndex)
|
||||
} else {
|
||||
this.PreviewPreset(PresetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public LoadPreset(PresetIndex: number): void {
|
||||
if (this.Idol == null) {
|
||||
console.error(`TsCharacterItem@LoadPreset error: idol is null`);
|
||||
return;
|
||||
}
|
||||
this.Idol.PropComp.ServerLoadPropPreset(PresetIndex);
|
||||
}
|
||||
|
||||
public PreviewPreset(PresetIndex: number): void {
|
||||
if (this.Idol == null) {
|
||||
console.error(`TsCharacterItem@PreviewPreset error: idol is null`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.Idol.PropComp.ClientLoadPropPreset(PresetIndex);
|
||||
this.RefreshPresetUIStatus()
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user