Bootstrap

UE5.5 PCGFrameWork--GPU CustomHLSL

在上一篇UE5.5 PCGFrameWork使用入门-CSDN博客 大致介绍了UE5 PCG框架的基本使用.

本篇探索PCGFrame的高级应用--GPU点云。也就是利用GPU HLSL编程对点云进行操纵,可以大幅度提升点云生成效率。

目前在UE5 PCG框架中,点云GPU的应用大致分为三类: PointGenerator, PointProcessor, Custom

GPU节点

三种模式下是同一个节点,只是选择模式,GPU变量预声明,GPU函数等存在一些差别。

SourceEditor

SourceEditor可以打开相应模式下的GPU,可以看到预先声明变量和各种公用函数。

PointGenerator

预先生成的点云函数和变量

预生成数量

预定义生成点云的数量, 

在GPU HLSL NumPoints和ElementIndex

输入声明(Input Declartions)

由节点的输入类型和名字动态决定来生成相应输入函数,可以用来在GPU获取点云,地形,纹理等数据,类型有下面几种

 比如增加一个点云输入和地形数据输入

预先生成输入代码:

下面具体分析每种类型输入的相应简单使用

点云输入

"InPosition"名的点云输入

/*** INPUT DATA FUNCTIONS ***/

uint InPosition_GetNumData();
uint InPosition_GetNumElements();
uint InPosition_GetNumElements(uint DataIndex);

// Valid types: bool, int, float, float2, float3, float4, Rotator (float3), Quat (float4), Transform (float4x4), StringKey (int), Name (uint2)

<type> InPosition_Get<type>(uint DataIndex, uint ElementIndex, int AttributeId);
<type> InPosition_Get<type>(uint DataIndex, uint ElementIndex, 'AttributeName');

/*** INPUT POINT DATA FUNCTIONS ***/

float3 InPosition_GetPosition(uint DataIndex, uint ElementIndex);
float4 InPosition_GetRotation(uint DataIndex, uint ElementIndex);
float3 InPosition_GetScale(uint DataIndex, uint ElementIndex);
float3 InPosition_GetBoundsMin(uint DataIndex, uint ElementIndex);
float3 InPosition_GetBoundsMax(uint DataIndex, uint ElementIndex);
float4 InPosition_GetColor(uint DataIndex, uint ElementIndex);
float InPosition_GetDensity(uint DataIndex, uint ElementIndex);
int InPosition_GetSeed(uint DataIndex, uint ElementIndex);
float InPosition_GetSteepness(uint DataIndex, uint ElementIndex);
float4x4 InPosition_GetPointTransform(uint DataIndex, uint ElementIndex);
bool InPosition_IsPointRemoved(uint DataIndex, uint ElementIndex);

这里暂时不清楚NumData意义,可以确定的并不是点云数量

这里的Type代表你获取数据的类型, 而ElementIndex代码第N个点云,DataIndex暂时推荐都使用0.

InPosition_GetNumElements

获取输入点云的数量

InPosition_Get<type>

这里是获取第N个点云的相应属性, 这里有两种方法

[1]通过属性序号来获取

<type> InPosition_Get<type>(uint DataIndex, uint ElementIndex, int AttributeId);

案例: 获取输入的第2个点云的位置,Position的AttributeId为0

float3 Position = InPosition_GetFloat3(0, 1, 0);

[2]一种是通过属性名字来获取

<type> InPosition_Get<type>(uint DataIndex, uint ElementIndex, 'AttributeName');

案例: 获取输入的第2个点云的位置,Position的名字为'$Position', 注意单引号和$字符前缀

否则会报语法错误.当然类型映射措辞和属性名字不存在也会报语法错误.


float3 Position = InPosition_GetFloat3(0, 0, '$Position');
InPosition_GetXXX

这里比较容易理解,直接获取点云各种属性(位置,缩放,旋转,Color, Bound等等)。

float3 InPosition_GetPosition(uint DataIndex, uint ElementIndex);
float4 InPosition_GetRotation(uint DataIndex, uint ElementIndex);
float3 InPosition_GetScale(uint DataIndex, uint ElementIndex);
float3 InPosition_GetBoundsMin(uint DataIndex, uint ElementIndex);
float3 InPosition_GetBoundsMax(uint DataIndex, uint ElementIndex);
float4 InPosition_GetColor(uint DataIndex, uint ElementIndex);
float InPosition_GetDensity(uint DataIndex, uint ElementIndex);
int InPosition_GetSeed(uint DataIndex, uint ElementIndex);
float InPosition_GetSteepness(uint DataIndex, uint ElementIndex);
float4x4 InPosition_GetPointTransform(uint DataIndex, uint ElementIndex);
bool InPosition_IsPointRemoved(uint DataIndex, uint ElementIndex);

地形输入

"Landscape"名的地形输入

float Landscape_GetHeight(float3 WorldPos);
float3 Landscape_GetNormal(float3 WorldPos);

这里不用解释太多,就是根据WorldPos采样地形的高度和法线等属性

纹理输入

“Texture”名的纹理输入

float2 Texture_GetTexCoords(float2 WorldPos, float2 Min, float2 Max);
float4 Texture_Sample(uint DataIndex, float2 TexCoords);

// Computes sample coordinates of the WorldPos relative to the texture data's bounds.float4 
Texture_SampleWorldPos(uint DataIndex, float2 WorldPos);

案例: 在一个PCG Volume的Grid2D点云设置相应的纹理值为点云缩放

float3 Min = GetComponentBoundsMin(); // World-space
float3 Max = GetComponentBoundsMax(); // World-space
float3 InPosition = CreateGrid2D(ElementIndex, NumPoints, Min, Max);
float2 UV = Texture_GetTexCoords(float2(InPosition.x, InPosition.y), Min, Max);
float4 SampleValue = Texture_Sample(0, UV);
Out_SetScale(Out_DataIndex, ElementIndex, float3(SampleValue.x, SampleValue.x, SampleValue.x));
Out_SetPosition(Out_DataIndex, ElementIndex, InPosition);

AttributeSet

输出函数(Output Declarations)

/*** OUTPUT DATA FUNCTIONS ***/

// Valid types: bool, int, float, float2, float3, float4, Rotator (float3), Quat (float4), Transform (float4x4), StringKey (int), Name (uint2)

void Out_Set<type>(uint DataIndex, uint ElementIndex, int AttributeId, <type> Value);
void Out_Set<type>(uint DataIndex, uint ElementIndex, 'AttributeName', <type> Value);

/*** OUTPUT POINT DATA FUNCTIONS ***/

void Out_InitializePoint(uint DataIndex, uint ElementIndex);
void Out_CopyElementFrom_<input pin>(uint TargetDataIndex, uint TargetElementIndex, uint SourceDataIndex, uint SourceElementIndex);
bool Out_RemovePoint(uint DataIndex, uint ElementIndex);

void Out_SetPosition(uint DataIndex, uint ElementIndex, float3 Position);
void Out_SetRotation(uint DataIndex, uint ElementIndex, float4 Rotation);
void Out_SetScale(uint DataIndex, uint ElementIndex, float3 Scale);
void Out_SetBoundsMin(uint DataIndex, uint ElementIndex, float3 BoundsMin);
void Out_SetBoundsMax(uint DataIndex, uint ElementIndex, float3 BoundsMax);
void Out_SetColor(uint DataIndex, uint ElementIndex, float4 Color);
void Out_SetDensity(uint DataIndex, uint ElementIndex, float Density);
void Out_SetSeed(uint DataIndex, uint ElementIndex, int Seed);
void Out_SetSteepness(uint DataIndex, uint ElementIndex, float Steepness);
void Out_SetPointTransform(uint DataIndex, uint ElementIndex, float4x4 Transform);

设置输出点云数据的各种函数(虽然Ouput引脚支持各种类型,暂时只发现只有Point类型可以输出)

这里和上面的输入函数用法基本一致。主要解释一个特殊函数: 

Out_RemovePoint(Out_DataIndex, ElementIndex);

这个函数用于移除某个ElementIndex的点云

代码例子: 移除ElementIndex为3的整数倍的点云

if(ElementIndex % 3 == 0)
    Out_RemovePoint(Out_DataIndex, ElementIndex);

辅助函数(Helper Declarations)

/*** HELPER FUNCTIONS ***/

int3 GetNumThreads();
uint GetThreadCountMultiplier();

// Returns false if thread has no data to operate on.
// Valid pins: InPosition, Landscape, Texture, Out
bool <pin>_GetThreadData(uint ThreadIndex, out uint OutDataIndex, out uint OutElementIndex);

float3 GetComponentBoundsMin(); // World-space
float3 GetComponentBoundsMax();
uint GetSeed();

float FRand(inout uint Seed); // Returns random float between 0 and 1.
uint ComputeSeed(uint A, uint B);
uint ComputeSeed(uint A, uint B, uint C);
uint ComputeSeedFromPosition(float3 Position);

// Returns the position of the Nth point in a 2D or 3D grid with the given constraints.
float3 CreateGrid2D(int ElementIndex, int NumPoints, float3 Min, float3 Max);
float3 CreateGrid2D(int ElementIndex, int NumPoints, int NumRows, float3 Min, float3 Max);
float3 CreateGrid3D(int ElementIndex, int NumPoints, float3 Min, float3 Max);
float3 CreateGrid3D(int ElementIndex, int NumPoints, int NumRows, int NumCols, float3 Min, float3 Max);

GetComponentBoundsMin和GetComponentBoundsMax

获取PCG Volume的BoundMin, BoundMax

随机函数

uint GetSeed();

float FRand(inout uint Seed); // Returns random float between 0 and 1.
uint ComputeSeed(uint A, uint B);
uint ComputeSeed(uint A, uint B, uint C);
uint ComputeSeedFromPosition(float3 Position);

和随机种子和随机值密切相关的一组函数,非常常见的配套函数.

GetSeed函数获取的种子来自节点面板:

创建Grid点云函数

// Returns the position of the Nth point in a 2D or 3D grid with the given constraints.
float3 CreateGrid2D(int ElementIndex, int NumPoints, float3 Min, float3 Max);
float3 CreateGrid2D(int ElementIndex, int NumPoints, int NumRows, float3 Min, float3 Max);
float3 CreateGrid3D(int ElementIndex, int NumPoints, float3 Min, float3 Max);
float3 CreateGrid3D(int ElementIndex, int NumPoints, int NumRows, int NumCols, float3 Min, float3 Max);

一组可以让用户快速创建Grid(2D或者3D)点云的函数

演示一个案例: 创建400个 行数为10的Grid 2D点云

使用

float3 CreateGrid2D(int ElementIndex, int NumPoints, int NumRows, float3 Min, float3 Max);

完整代码:

float3 Min = GetComponentBoundsMin(); // World-space
float3 Max = GetComponentBoundsMax(); // World-space
float3 InPosition = CreateGrid2D(ElementIndex, NumPoints, 10, Min, Max);
Out_SetPosition(Out_DataIndex, ElementIndex, InPosition);

演示效果:

自定义函数(ShaderFunction)

用户自定义函数,如何生成各种分形点云,各种自定义几何形状分布的点云等等

演示Demo: 生成一个以某点位置为中心的贴地形点云圆圈

GPU代码相关

Shader Function
/** CUSTOM SHADER FUNCTIONS **/

float3 CreateCircle2D(uint ElementIndex, int NumPoints, float3 Center, float Radius)
{
     // 计算角度(均匀分布)
    float Angle = 2 * 3.14159265358979323846 * ElementIndex / NumPoints;

    // 极坐标转笛卡尔坐标
    float X = Center.x + Radius * cos(Angle);
    float Z = Center.y + Radius * sin(Angle);
    return float3(X, Z, 0);
}
Shader Source
float3 InPosition = InPosition_GetFloat3(0, 0, 0);
float3 Position = CreateCircle2D(ElementIndex, NumPoints, InPosition, 3000.0);
float Height = Landscape_GetHeight(Position);
Position.z = Height;
Out_SetPosition(Out_DataIndex, ElementIndex, Position);

运行Demo效果

跟随PCG Actor移动的贴地点云圆圈

PointProcessor和Custom

目前看PointProcessor和Custom像PointGenerator除了无法预定义点云数量, 暂时看不出什么和PointGenerator存在什么区别

参考资料

[1]PCG: Advanced Topics & New Features in UE 5.5 | Unreal Fest 2024

[2]Unreal Engine 5.5 - Compute Shaders With PCG Introduction (Height Thresholding in HLSL)

[3]Unreal Engine 5.5 - PCG Compute Introduction (Fractals in HLSL)

[4]Directx11入门教程四十八之小议ComputeShader_dx11 dispatch-CSDN博客

;