VirtualCamera是虚幻引擎中的工程示例项目。虚幻引擎4.26版本中将其升级到了2.0(Beta版本),功能和UI都全面更新(如图1-1所示)。虚拟摄像机在虚幻引擎中驱动电影摄像机(Cine Camera),它能够将结果输出到各种外部设备中。我使用它主要用来拍摄镜头预演。 演示连接:https://www.bilibili.com/video/BV195411w75h/

ue_virtualcamera_image1

图1-1

VirtualCamera如何在输出上覆盖自定义的UMG控件

我这边采用的是“BlackMagic”采集卡(型号:)通过SDI线将画面传输到监视设备。根据引擎自带的“BlackMagic”插件是能够将画面采集并输出到监视设备的,但并不能将“UMG”控件采集并输出。“VirtualCamera”中可以实现这样的功能。下面简单分析一下这一块的逻辑是如何执行的。 UMG是在Vcam组件属性中OutputPrivider中指定的,打开源码找到对应的文件 VcamOutputProviderBase.h。简单阅读一遍后很容易就能发现逻辑是从初始化函数“Initialize()”开始执行的,经过一系列判断后一次执行“Acitvate()”、“CreateUMG()”和“DisplayUMG()”。具体代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void UVCamOutputProviderBase::CreateUMG()
{
if (!UMGClass)
{
return;
}

if (UMGWidget)
{
UE_LOG(LogVCamOutputProvider, Error, TEXT("CreateUMG widget already set - failed to create"));
return;
}

UMGWidget = NewObject<UVPFullScreenUserWidget>(this, UVPFullScreenUserWidget::StaticClass());
UMGWidget->SetDisplayTypes(DisplayType, DisplayType, DisplayType);
UMGWidget->PostProcessDisplayType.bReceiveHardwareInput = true;

#if WITH_EDITOR
UMGWidget->SetAllTargetViewports(GetTargetLevelViewport());
#endif

UMGWidget->WidgetClass = UMGClass;
UE_LOG(LogVCamOutputProvider, Log, TEXT("CreateUMG widget named %s from class %s"), *UMGWidget->GetName(), *UMGWidget->WidgetClass->GetName());
}

void UVCamOutputProviderBase::DisplayUMG()
{
if (UMGWidget)
{
UWorld* ActorWorld = nullptr;
int32 WorldType = -1;

for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
if (Context.World())
{
// Prioritize PIE and Game modes if active
if ((Context.WorldType == EWorldType::PIE) || (Context.WorldType == EWorldType::Game))
{
ActorWorld = Context.World();
WorldType = (int32)Context.WorldType;
break;
}
else if (Context.WorldType == EWorldType::Editor)
{
// Only grab the Editor world if PIE and Game aren't available
ActorWorld = Context.World();
WorldType = (int32)Context.WorldType;
}
}
}

if (ActorWorld)
{
UMGWidget->Display(ActorWorld);
UE_LOG(LogVCamOutputProvider, Log, TEXT("DisplayUMG widget displayed in WorldType %d"), WorldType);
}

NotifyWidgetOfComponentChange();
}
}

进入“UMGWidget->Display(ActorWorld);”函数中。会发现是使用Slate,SNew出的控件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
bool FVPFullScreenUserWidget_Viewport::Display(UWorld* World, UUserWidget* Widget, float InDPIScale)
{
TSharedPtr<SConstraintCanvas> FullScreenWidgetPinned = FullScreenCanvasWidget.Pin();
if (Widget == nullptr || World == nullptr || FullScreenWidgetPinned.IsValid())
{
return false;
}

UGameViewportClient* ViewportClient = nullptr;
#if WITH_EDITOR
TSharedPtr<SLevelViewport> ActiveLevelViewport;
#endif

bool bResult = false;
if (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE)
{
ViewportClient = World->GetGameViewport();
bResult = ViewportClient != nullptr;
}
#if WITH_EDITOR
else if (FModuleManager::Get().IsModuleLoaded(NAME_LevelEditorName))
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(NAME_LevelEditorName);
if (TargetViewport.IsValid())
{
ActiveLevelViewport = TargetViewport.Pin();
}
else
{
ActiveLevelViewport = LevelEditorModule.GetFirstActiveLevelViewport();
}
bResult = ActiveLevelViewport.IsValid();
}
#endif

if (bResult)
{
TSharedRef<SConstraintCanvas> FullScreenCanvas = SNew(SConstraintCanvas);
FullScreenCanvasWidget = FullScreenCanvas;

FullScreenCanvas->AddSlot()
.Offset(FMargin(0, 0, 0, 0))
.Anchors(FAnchors(0, 0, 1, 1))
.Alignment(FVector2D(0, 0))

[ SNew(SDPIScaler) .DPIScale(InDPIScale) [ Widget->TakeWidget() ] ];

if (ViewportClient) { ViewportClient->AddViewportWidgetContent(FullScreenCanvas); } #if WITH_EDITOR else { check(ActiveLevelViewport.IsValid()); ActiveLevelViewport->AddOverlayWidget(FullScreenCanvas); OverlayWidgetLevelViewport = ActiveLevelViewport; } #endif }

1
2
return bResult;
}

这里是我从VirtualCamera中将这个功能剥离出来并放到了一个插件中。 百度云链接:

如何更改VirtualCamera的输入按键绑定 VirtualCamera共支持三种输入方式:鼠标点击、IOS端触屏、移动手柄。最终生产肯定不能用鼠标;采用IOS端触屏的方式需要配合一个IOS端的应用“Unreal Remote”,但是目前来看画面延迟很高,而且触屏的方式效率很低,不适用于影视行业。根据现场经验来看大多数摄影师都是使用各种外部设备来调整焦点、光圈以及录制和回放的。所以最终采用XBox360手柄,它的按键比较多,基本能够满足需求。画面传输方便采用的是“至迅”的无线图传。跟踪设备采用“Optitrack”。 总体来看逻辑可以分为两大块:UI和Vcam组件,基本就是UI和Vcam组件的数据通信。Vcam组件有两个重要的属性:“Output Provider”和“Modifier”。前者指定了输出设备,后者主要用来编写修改Vcam组件的逻辑。如果是UI和Vcam需要交互的,那么按键的输入绑定逻辑就会写在“Vcam2UI”中。如果不和UI交互的输入绑定逻辑就会写在“Modifier”中。UI相关的键位绑定是通过集合的方式(TMap和TArray)。 找到“VirtualCameraUserSettings.h”中的函数“InjectGamepadKeybinds()”。绑定关系如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
void UVirtualCameraUserSettings::InjectGamepadKeybinds()
{
ActionMappings = {
{TEXT("VirtualCamera_Home_EarSelection_Up"), EKeys::Gamepad_DPad_Up},
{TEXT("VirtualCamera_Home_EarSelection_Down"), EKeys::Gamepad_DPad_Down},
{TEXT("VirtualCamera_Home_EarSelection_Left"), EKeys::Gamepad_DPad_Left},
{TEXT("VirtualCamera_Home_EarSelection_Right"), EKeys::Gamepad_DPad_Right},
{TEXT("VirtualCamera_Home_Selection"), EKeys::Gamepad_FaceButton_Bottom},
{TEXT("VirtualCamera_Home_FStop"), EKeys::Gamepad_LeftTrigger},
{TEXT("VirtualCamera_Home_LensesMenu"), EKeys::Gamepad_RightTrigger},
{TEXT("VirtualCamera_Home_Screenshot"), EKeys::Gamepad_RightShoulder},
{TEXT("VirtualCamera_Home_ToggleStats"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Home_ToggleFlight"), EKeys::Gamepad_FaceButton_Top},
{TEXT("VirtualCamera_Home_ToggleRecord"), EKeys::Gamepad_LeftShoulder},
{TEXT("VirtualCamera_Playback_SelectsSwitch"), EKeys::Gamepad_LeftShoulder},
{TEXT("VirtualCamera_Playback_SelectClipsUp"), EKeys::Gamepad_DPad_Up},
{TEXT("VirtualCamera_Playback_SelectClipsDown"), EKeys::Gamepad_DPad_Down},
{TEXT("VirtualCamera_Playback_PlayPause"), EKeys::Gamepad_RightShoulder},
{TEXT("VirtualCamera_Playback_Fullscreen"), EKeys::Gamepad_FaceButton_Top},
{TEXT("VirtualCamera_Playback_MakeClipSelect"), EKeys::Gamepad_FaceButton_Left},
{TEXT("VirtualCamera_Playback_TargetClip"), EKeys::Gamepad_FaceButton_Bottom},
{TEXT("VirtualCamera_Playback_BackStart"), EKeys::Gamepad_DPad_Left},
{TEXT("VirtualCamera_Playback_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Map_SwitchFilterSnapshotFlagLeft"), EKeys::Gamepad_DPad_Left},
{TEXT("VirtualCamera_Map_SwitchFilterSnapshotFlagRight"), EKeys::Gamepad_DPad_Right},
{TEXT("VirtualCamera_Map_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Map_TeleportHome"), EKeys::Gamepad_FaceButton_Left},
{TEXT("VirtualCamera_Map_ToggleFullscreen"), EKeys::Gamepad_FaceButton_Top},
{TEXT("VirtualCamera_Recording_InitiateRecord"), EKeys::Gamepad_LeftTrigger},
{TEXT("VirtualCamera_Interaction_Undo"), EKeys::Gamepad_LeftShoulder},
{TEXT("VirtualCamera_Interaction_Redo"), EKeys::Gamepad_RightShoulder},
{TEXT("VirtualCamera_Interaction_SRTSwitchUp"), EKeys::Gamepad_DPad_Up},
{TEXT("VirtualCamera_Interaction_SRTSwitchDown"), EKeys::Gamepad_DPad_Down},
{TEXT("VirtualCamera_Interaction_XYZSwitchLeft"), EKeys::Gamepad_DPad_Left},
{TEXT("VirtualCamera_Interaction_XYZSwitchRight"), EKeys::Gamepad_DPad_Right},
{TEXT("VirtualCamera_Interaction_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Interaction_LocalWorldToggle"), EKeys::Gamepad_FaceButton_Top},
{TEXT("VirtualCamera_Interaction_SelectSpawnNumpad"), EKeys::Gamepad_FaceButton_Bottom},
{TEXT("VirtualCamera_Focus_CloseMenu"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Focus_Selection"), EKeys::Gamepad_FaceButton_Bottom},
{TEXT("VirtualCamera_Focus_ToggleAuto"), EKeys::Gamepad_FaceButton_Top},
{TEXT("VirtualCamera_Playback_Back"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_Map_Back"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_Interaction_Back"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_Settings_Close"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_Focus_CloseMenu"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_MotionAdjustments_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Settings_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Focus_AdjustSelectorDown"), EKeys::Gamepad_DPad_Up},
{TEXT("VirtualCamera_Focus_AdjustSelectorUp"), EKeys::Gamepad_DPad_Down},
{TEXT("VirtualCamera_Focus_ButtonSelect"), EKeys::Gamepad_FaceButton_Bottom},
{TEXT("VirtualCamera_AnimationPreview_PlayPause"), EKeys::Gamepad_RightShoulder},
{TEXT("VirtualCamera_AnimationPreview_Close"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_AnimationPreview_BackStart"), EKeys::Gamepad_DPad_Left},
{TEXT("VirtualCamera_AnimationPreview_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Reposition_ToggleTiltOffset"), EKeys::Gamepad_FaceButton_Left},
{TEXT("VirtualCamera_Reposition_Close"), EKeys::Gamepad_Special_Right},
{TEXT("VirtualCamera_Reposition_Close"), EKeys::Gamepad_FaceButton_Right},
{TEXT("VirtualCamera_MotionAdjustments_MoveAxisUp"), EKeys::Gamepad_DPad_Up},
{TEXT("VirtualCamera_MotionAdjustments_MoveAxisDown"), EKeys::Gamepad_DPad_Down},
{TEXT("VirtualCamera_MotionAdjustments_OpenNumpad"), EKeys::Gamepad_FaceButton_Bottom},
{TEXT("VirtualCamera_MotionAdjustments_SelectionSwitchLeft"), EKeys::Gamepad_LeftShoulder},
{TEXT("VirtualCamera_MotionAdjustments_SelectionSwitchRight"), EKeys::Gamepad_RightShoulder},
{TEXT("VirtualCamera_MotionAdjustments_LockAxis"), EKeys::Gamepad_FaceButton_Top},
{TEXT("VirtualCamera_MotionAdjustments_ResetDutch"), EKeys::Gamepad_FaceButton_Left},
{TEXT("VirtualCamera_MotionAdjustments_NumpadLeft"), EKeys::Gamepad_DPad_Left},
{TEXT("VirtualCamera_MotionAdjustments_NumpadRight"), EKeys::Gamepad_DPad_Right},
{TEXT("VirtualCamera_MotionAdjustments_Back"), EKeys::Gamepad_FaceButton_Right},

};

AxisMappings = {
{TEXT("VirtualCamera_Home_MoveFocalLengthSelection"), EKeys::Gamepad_RightY,1.0f},
{TEXT("VirtualCamera_Home_MoveFStopSelection"), EKeys::Gamepad_LeftY,1.0f},
{TEXT("VirtualCamera_Playback_FastScroll"), EKeys::Gamepad_LeftY,1.0f},
{TEXT("VirtualCamera_Playback_FastScrub"), EKeys::Gamepad_RightX,1.0f},
{TEXT("VirtualCamera_Playback_BackwardFrame"), EKeys::Gamepad_LeftTriggerAxis,1.0f},
{TEXT("VirtualCamera_Playback_ForwardFrame"), EKeys::Gamepad_RightTriggerAxis,1.0f},
{TEXT("VirtualCamera_Map_Zoom"), EKeys::Gamepad_RightY,1.0f},
{TEXT("VirtualCamera_Map_XAxisPan"), EKeys::Gamepad_LeftX,1.0f},
{TEXT("VirtualCamera_Map_YAxisPan"), EKeys::Gamepad_LeftY,1.0f},
{TEXT("VirtualCamera_Interaction_Value_Change"), EKeys::Gamepad_LeftY,1.0f},
{TEXT("VirtualCamera_Interaction_Value_Change"), EKeys::Gamepad_RightY,1.0f},
{TEXT("VirtualCamera_Focus_ReticleMovementY"), EKeys::Gamepad_LeftY,1.0f},
{TEXT("VirtualCamera_Focus_ReticleMovementX"), EKeys::Gamepad_LeftX,1.0f},
{TEXT("VirtualCamera_Focus_AdjustFocalDistance"), EKeys::Gamepad_RightY,1.0f},
{TEXT("VirtualCamera_AnimationPreview_FastScrub"), EKeys::Gamepad_RightX,1.0f},
{TEXT("VirtualCamera_AnimationPreview_ForwardFrame"), EKeys::Gamepad_RightTriggerAxis,1.0f},
{TEXT("VirtualCamera_AnimationPreview_BackwardFrame"), EKeys::Gamepad_LeftTriggerAxis,1.0f},
{TEXT("VirtualCamera_MotionAdjustments_AdjustRadial"), EKeys::Gamepad_LeftY,1.0f},
{TEXT("VirtualCamera_MotionAdjustments_AdjustRadial"), EKeys::Gamepad_RightY,1.0f},
{TEXT("VirtualCamera_MotionAdjustments_AdjustRadial"), EKeys::Gamepad_LeftX,1.0f},
{TEXT("VirtualCamera_MotionAdjustments_AdjustRadial"), EKeys::Gamepad_RightX,1.0f},
{TEXT("VirtualCamera_Home_LeftMoveHorizontal"), EKeys::Gamepad_LeftX,1.0f},
{TEXT("VirtualCamera_Home_RightMoveHorizontal"), EKeys::Gamepad_RightX,1.0f},
};
}

打开控件“Vcam2UI”,找到TMap“KeyBindingsHome”和“AxisBindingsHome”,通过更改Tmap中的Key值就能改变键位对应的函数关系。如图1-2所示。 ue_virtualcamera_image2

图1-2