前言
TA大佬在niagara里需要做一个CPU粒子,需要用到ViewSize这个参数,从CameraQuery里只有一个GPU方法可以获取,只能考虑GPU粒子,希望能有一个CPU获取ViewSize的方法
具体问题
新建一个NiagaraScript,默认长这样:
在MapGet里新增一个CameraQuery节点
从这个Pin拉出来Get方法
可以看到这个是CameraQuery的Get方法,有标注支持CPU还是GPU
主义这里GetViewSizeCPU是我新加上的,4.27本身不提供这个方法
如果用了不支持的方法,编译的时候会报错,比如CPU粒子非要调用仅支持GPU粒子的方法,直接编译就会报错
在添加新方法之前,只有GetViewPropertiesGPU里有ViewSize这个参数
希望得到的结果是这样:
可以在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 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 ; } 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; }); 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
然后在编辑器里看一下
搞定!