UE4:Niagara扩展CameraQuery支持CPU获取ViewSize

前言

TA大佬在niagara里需要做一个CPU粒子,需要用到ViewSize这个参数,从CameraQuery里只有一个GPU方法可以获取,只能考虑GPU粒子,希望能有一个CPU获取ViewSize的方法

具体问题

新建一个NiagaraScript,默认长这样:
NiagaraScript

在MapGet里新增一个CameraQuery节点
CameraQuery

从这个Pin拉出来Get方法
Get方法

可以看到这个是CameraQuery的Get方法,有标注支持CPU还是GPU
主义这里GetViewSizeCPU是我新加上的,4.27本身不提供这个方法
如果用了不支持的方法,编译的时候会报错,比如CPU粒子非要调用仅支持GPU粒子的方法,直接编译就会报错
在添加新方法之前,只有GetViewPropertiesGPU里有ViewSize这个参数
GetViewPropertiesGPU

希望得到的结果是这样:
GetViewSize

可以在CPU粒子使用的获取ViewSize的方法

解决过程

下面这堆是摸索的过程,并不是清晰的有条理的具体的解决的方法,具体的清晰的整理过的解决过程会在后文,这里可以不看(/▽╲)但是我想水!

查看CameraQuery的源码,找到这几个已有的函数:

1
2
3
4
5
6
7
void CalculateParticleDistances(FVectorVMContext& Context);
void GetClosestParticles(FVectorVMContext& Context);
void GetCameraFOV(FVectorVMContext& Context);
void GetCameraProperties(FVectorVMContext& Context);
void GetViewPropertiesGPU(FVectorVMContext& Context);
void GetClipSpaceTransformsGPU(FVectorVMContext& Context);
void GetViewSpaceTransformsGPU(FVectorVMContext& Context);

先看一下GetViewPropertiesGPU,试图从这里看到他是怎么获取的ViewSize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ------- Dummy implementations for CPU execution ------------

void UNiagaraDataInterfaceCamera::GetViewPropertiesGPU(FVectorVMContext& Context)
{
VectorVM::FUserPtrHandler<FCameraDataInterface_InstanceData> InstData(Context);
TArray<VectorVM::FExternalFuncRegisterHandler<float>> OutParams;
OutParams.Reserve(24);
for (int i = 0; i < 24; i++)
{
OutParams.Emplace(Context);
}

for (int32 k = 0; k < Context.NumInstances; ++k)
{
for (int i = 0; i < 24; i++)
{
*OutParams[i].GetDestAndAdvance() = 0;
}
}
}

发现这里注释写了,这是CPU的模拟实现,只是在Context里塞了占位,世界上没有数据

再看一下其他的CPU函数的函数体

找一个短一点的

1
2
3
4
5
6
7
8
9
10
11
12
void UNiagaraDataInterfaceCamera::GetCameraFOV(FVectorVMContext& Context)
{
VectorVM::FUserPtrHandler<FCameraDataInterface_InstanceData> InstData(Context);
VectorVM::FExternalFuncRegisterHandler<float> OutFov(Context);

float Fov = InstData.Get()->CameraFOV;

for (int32 i = 0; i < Context.NumInstances; ++i)
{
*OutFov.GetDestAndAdvance() = Fov;
}
}

可以看到其实通过context构造了一个FCameraDataInterface_InstanceData,再从InstData中获取了具体的参数的值

通过VectorVM::FExternalFuncRegisterHandler<数据类型>的方式为Context添加输出,将输出的值赋值为刚才Get到的值,就完成了从InstData到输出pin的传递

UNiagaraDataInterfaceCamera::GetCameraProperties这个函数更长,里面可以看到更整齐的添加输出、获取值、赋值的操作

那么FCameraDataInterface_InstanceData里的值是从哪里来的呢

发现在InitPerInstanceData中虽然有new一个,但是并没有进行任何的赋值,而是在PerInstanceTick中进行的复制操作。因为是Camera参数,需要tick获取十分合理

在PerInstanceTick这个函数里,确认了其实是从World和PlayerController确认了摄像机,从而获取了各项参数。world的获取也是通过SystemInstance->GetWorldManager()->GetWorld(); Niagara系统自身提供的方法确认获得的。另外该函数还对编辑器模式进行了支持,可以在编辑器模式下正常获取View的Camera

GetFunctions函数里是对该DataInterface的Out添加方法的地方,主要是需要填充一个FNiagaraFunctionSignature然后加到OutFunctions中
其中需要注意的有:

  • Name,这个不是实际的函数的名字
  • bSupportsCPU,是否支持CPU粒子使用,编译时会检查
  • bSupportsGPU,是否支持GPU粒子
  • bMemberFunction,是不是DataInterface的成员函数

然后是调用AddInput和AddOutput来添加输入和输出

GetFunctionHLSL里如果只是CPU方法就不必了,可以大概看一下

1
2
3
4
5
6
7
8
9
10
11
if (FunctionInfo.DefinitionName == GetFieldOfViewName)
{
static const TCHAR *FormatSample = TEXT(R"(
void {FunctionName}(out float Out_FieldOfViewAngle)
{
Out_FieldOfViewAngle = degrees(View.FieldOfViewWideAngles.x);
}
)");
OutHLSL += FString::Format(FormatSample, ArgsSample);
return true;
}

其实就是自己写一下HLSL,通过函数的DefinitionName 确认到之后返回HLSL代码

那么现在要给CameraQuery增加一个方法,就是要在对应的DataInterface的结构体中增加对应的参数,在tick获取值的地方赋值,然后增加对应的function和GetFunctions的部分就可以了

解决方案

这里只描述具体应用,没有相关源码的分析

头文件中:

FCameraDataInterface_InstanceData

增加结构体的成员

1
2
3
4
5
6
7
8
9
10
11
struct FCameraDataInterface_InstanceData
{
FVector CameraLocation = FVector::ZeroVector;
FRotator CameraRotation = FRotator::ZeroRotator;
float CameraFOV = 0.0f;
#pragma region CPUViewSize
FIntPoint ViewSize = FIntPoint::ZeroValue;
#pragma endregion
TQueue<FDistanceData, EQueueMode::Mpsc> DistanceSortQueue;
TArray<FDistanceData> ParticlesSortedByDistance;
};

声明函数

1
void GetViewSizeCPU(FVectorVMContext& Context);

声明FName

1
static const FName GetViewSizeName;

源文件中

函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void UNiagaraDataInterfaceCamera::GetViewSizeCPU(FVectorVMContext& Context)
{
VectorVM::FUserPtrHandler<FCameraDataInterface_InstanceData> InstData(Context);

VectorVM::FExternalFuncRegisterHandler<float> ViewSizeX(Context);
VectorVM::FExternalFuncRegisterHandler<float> ViewSizeY(Context);

FIntPoint ViewportSize(1, 1);
ViewportSize = InstData->ViewSize;

for (int32 i = 0; i < Context.NumInstances; ++i)
{
*ViewSizeX.GetDestAndAdvance() = ViewportSize.X;
*ViewSizeY.GetDestAndAdvance() = ViewportSize.Y;
}
}

初始化成员变量

1
const FName UNiagaraDataInterfaceCamera::GetViewSizeName(TEXT("GetViewSizeCPU"));

PerInstanceTick添加对应部分

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
bool UNiagaraDataInterfaceCamera::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds)
{
FCameraDataInterface_InstanceData* PIData = (FCameraDataInterface_InstanceData*)PerInstanceData;
if (!PIData)
{
return true;
}

// calculate the distance for each particle and sort by distance (if required)
PIData->ParticlesSortedByDistance.Empty();
FDistanceData DistanceData;
while (PIData->DistanceSortQueue.Dequeue(DistanceData))
{
PIData->ParticlesSortedByDistance.Add(DistanceData);
}
PIData->ParticlesSortedByDistance.StableSort([](const FDistanceData& A, const FDistanceData& B) { return A.DistanceSquared < B.DistanceSquared; });

// grab the current camera data
UWorld* World = SystemInstance->GetWorldManager()->GetWorld();
if (World && PlayerControllerIndex < World->GetNumPlayerControllers())
{
int32 i = 0;
for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PlayerController = Iterator->Get();
if (i == PlayerControllerIndex && PlayerController)
{
PIData->CameraLocation = PlayerController->PlayerCameraManager->GetCameraLocation();
PIData->CameraRotation = PlayerController->PlayerCameraManager->GetCameraRotation();
PIData->CameraFOV = PlayerController->PlayerCameraManager->GetFOVAngle();
#pragma region CPUViewSize
int32 ViewSizeX = 0;
int32 ViewSizeY = 0;
PlayerController->GetViewportSize(ViewSizeX, ViewSizeY);
PIData->ViewSize = FIntPoint(ViewSizeY, ViewSizeY);
#pragma endregion
return false;
}
i++;
}
}
#if WITH_EDITORONLY_DATA
if (GCurrentLevelEditingViewportClient)
{
const FViewportCameraTransform& ViewTransform = GCurrentLevelEditingViewportClient->GetViewTransform();
PIData->CameraLocation = ViewTransform.GetLocation();
PIData->CameraRotation = ViewTransform.GetRotation();
PIData->CameraFOV = GCurrentLevelEditingViewportClient->ViewFOV;
#pragma region CPUViewSize
PIData->ViewSize = GCurrentLevelEditingViewportClient->Viewport->GetSizeXY();
#pragma endregion
return false;
}
#endif

PIData->CameraLocation = FVector::ZeroVector;
PIData->CameraRotation = FRotator(0);
PIData->CameraFOV = 0;
#pragma region CPUViewSize
PIData->ViewSize = FIntPoint::ZeroValue;
#pragma endregion
return false;
}

GetFunctions添加对应部分

1
2
3
4
5
6
7
8
9
10
11
Sig = FNiagaraFunctionSignature();
Sig.Name = GetViewSizeName;
#if WITH_EDITORONLY_DATA
Sig.Description = LOCTEXT("GetViewSize", "GetViewSize");
Sig.FunctionVersion = FNiagaraCameraDIFunctionVersion::LatestVersion;
#endif
Sig.bMemberFunction = true;
Sig.bRequiresContext = false;
Sig.AddInput(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("Camera interface")));
Sig.AddOutput(FNiagaraVariable(FNiagaraTypeDefinition::GetVec2Def(), TEXT("View Size")), LOCTEXT("ViewSize", "View Size"));
OutFunctions.Add(Sig);

GetFunctionHLSL

也可以添加来支持GPU使用但是既然有GPU的GetViewProperties方法就不必了

绑定

1
DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetViewSizeCPU);

GetVMExternalFunction添加对应部分

1
2
3
4
5
6
#pragma region CPUViewSize
else if (BindingInfo.Name == GetViewSizeName)
{
NDI_FUNC_BINDER(UNiagaraDataInterfaceCamera, GetViewSizeCPU)::Bind(this, OutFunc);
}
#pragma endregion

结果

随便拉一个debug的Material

GetViewSize

然后在编辑器里看一下

DebugMaterial

搞定!


UE4:Niagara扩展CameraQuery支持CPU获取ViewSize
http://muchenhen.com/posts/59534/
作者
木尘痕
发布于
2022年8月19日
许可协议