mcpField
U
ue-procedural-generation

ue-procedural-generation

github.com/quodsoler开发4191.2k 次安装4 周前更新
代码仓库

关于 ue-procedural-generation

面向 Unreal Engine 5.8 开发者的程序化生成技能,汇集 PCG、运行时网格、实例化和样条线等 API 用法,并强调当前引擎版本的接口。

主要功能

  1. 01PCG 图与运行时生成
  2. 02PCG 点数据读写
  3. 03自定义 C++ PCG 节点
  4. 04运行时程序化网格
  5. 05Dynamic Mesh 与 Geometry Script
  6. 06实例化、样条线与随机噪声

适用场景

  1. 01配置 PCG 插件和模块
  2. 02创建或扩展 PCG 图节点
  3. 03运行时构建或更新网格
  4. 04用样条线放置对象或生成地形

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-procedural-generation

安装后在 Claude Code 中输入/ue-procedural-generation确认已加载。

附带文件references/pcg-node-reference.mdreferences/procedural-mesh-patterns.md

详细介绍

UE 程序化生成

目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;旧版形式列于“已弃用 — 请勿使用”部分。

涵盖 PCG 框架(插件 PCG,位于 Engine/Plugins/PCG,默认启用,Build.cs 模块为 PCG)、使用 UProceduralMeshComponent 进行运行时网格构建(插件 ProceduralMeshComponent,模块 ProceduralMeshComponent),以及使用 UDynamicMeshComponent + Geometry Script 进行运行时网格构建(模块 GeometryFramework 和 GeometryScriptingCore)、使用 UInstancedStaticMeshComponent/UHierarchicalInstancedStaticMeshComponent 进行实例化、由样条线驱动的放置,以及来自 Core 的确定性噪声/随机数。

上下文

如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用的插件、GAS/网络设置)。如果该文件不存在,也请继续。

根据请求和代码库确定所属领域。只有在两种可能的理解会导致不同代码时才提问。

请求涉及……转到
启用 PCG、模块/插件接线PCG 设置
从 Actor 驱动生成、运行时生成、分区PCG 组件和运行时生成
在图中读取/写入点点数据
用 C++ 编写新的 PCG 节点用 C++ 编写自定义 PCG 节点
哪个节点能执行某操作、引脚标签、节点设置字段PCG 节点参考
在运行时构建三角形ProceduralMeshComponent
布尔运算、基本体、烘焙为静态网格体动态网格和 Geometry Script
数千个重复网格体实例化静态网格体
道路、河流、围栏、电缆样条线
高度场、散布、可复现结果噪声和确定性随机数
行进立方体、BSP 地下城、WFC、泊松圆盘程序化网格模式

PCG 设置

// MyGame.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "PCG" });
{ "Name": "PCG", "Enabled": true }
类头文件作用
UPCGComponentPCGComponent.h持有图并驱动生成的 Actor 组件
UPCGGraphPCGGraph.h图资产:节点、边、UserParameters
UPCGGraphInstancePCGGraph.h带有每个实例参数覆盖项的图实例
UPCGGraphInterfacePCGGraph.h图和图实例的公共基类
UPCGSettingsPCGSettings.h节点设置基类
IPCGElementPCGElement.h节点中负责执行的部分
FPCGContextPCGContext.h每次执行的状态:InputData、OutputData、Node、ExecutionSource
UPCGBasePointDataData/PCGBasePointData.h抽象点集合
UPCGPointArrayDataData/PCGPointArrayData.h结构数组式点数据
UPCGSubsystemSubsystems/PCGSubsystem.h调度、分区、运行时生成
APCGVolumePCGVolume.h携带 UPCGComponent 的体积 Actor

PCG 组件和运行时生成

#include "PCGComponent.h"
#include "PCGGraph.h"

void AMyGenerator::StartGeneration(UPCGGraphInterface* Graph, int32 InSeed)
{
    UPCGComponent* PCG = FindComponentByClass<UPCGComponent>();
    if (!PCG)
    {
        return;
    }

    PCG->Seed = InSeed;
    PCG->bActivated = true;
    PCG->SetGraph(Graph);      // NetMulticast, Reliable
    PCG->Generate(/*bForce=*/true);  // NetMulticast, Reliable
}
调用复制用途
Generate(bool bForce) / Cleanup(bool bRemoveComponents)NetMulticast, Reliable由服务器驱动的生成,必须在客户端上显示
GenerateLocal(bool bForce) / CleanupLocal(bool bRemoveComponents)无客户端或单人游戏生成
NotifyPropertiesChangedFromBlueprint()无标记为脏,并在编辑公开属性后有条件地重新生成
CancelGeneration()无中止正在进行的生成
GetGeneratedGraphOutput()无读取图表生成的 FPCGDataCollection

EPCGComponentGenerationTrigger(PCGComponent.h:77):GenerateOnLoad、GenerateOnDemand、GenerateAtRuntime。

分区和运行时生成:

  • SetIsPartitioned(bool) / IsPartitioned() 对应 bIsComponentPartitioned 属性;分区组件会将工作分派给网格上的本地组件。
  • GenerateAtRuntime 会将组件交给运行时生成调度器。可通过 SchedulingPolicyClass / SchedulingPolicy(UPCGSchedulingPolicyBase)以及 bOverrideGenerationRadii + GenerationRadii(FPCGRuntimeGenerationRadii)进行调整。
  • 调度器 CVar:pcg.RuntimeGeneration.Enable、pcg.RuntimeGeneration.NumGeneratingComponents、pcg.RuntimeGeneration.GlobalRadiusMultiplier、pcg.RuntimeGeneration.EnablePooling、pcg.RuntimeGeneration.BasePoolSize、pcg.RuntimeGeneration.FramesBeforeFirstGenerate、pcg.RuntimeGeneration.EnableChangeDetection、pcg.RuntimeGeneration.EnableDebugging。
  • UPCGSubsystem::GetSubsystemForCurrentWorld() 返回子系统;RefreshAllComponentsFiltered(Filter, ChangeType) 强制刷新子集(仅限 WITH_EDITOR,Subsystems/PCGSubsystem.h:227-230)。

层级生成位于 UPCGGraph 上:bUseHierarchicalGeneration、HiGenGridSize(EPCGHiGenGrid::Grid4 … Grid2048,以及 Unbounded)、HiGenGridSizeMultiplier、bUse2DGrid。

图表参数是 UPCGGraph 上的 FInstancedPropertyBag UserParameters,通过 UPCGGraphInterface 读取和写入:

TValueOrError<double, EPropertyBagResult> Result = Graph->GetGraphParameter<double>(TEXT("SpawnRadius"));
if (Result.HasValue())
{
    const double Radius = Result.GetValue();
    Graph->SetGraphParameter<double>(TEXT("SpawnRadius"), Radius * 2.0);
}

点数据

点集合属于 UPCGBasePointData。UPCGPointArrayData 是结构数组实现;UPCGPointData 是为兼容性保留的 FPCGPoint 数组实现。通过上下文分配,以使用项目配置的类:

UPCGBasePointData* Points = FPCGContext::NewPointData_AnyThread(Context);

新代码中不要遍历 GetPoints() / GetMutablePoints()——这些方法仅存在于 UPCGPointData 上,且会强制进行转换。请改用值范围进行读写:

#include "Data/PCGBasePointData.h"

const FConstPCGPointValueRanges ReadRanges(InputPoints);
FPCGPointValueRanges WriteRanges(OutputPoints, /*bAllocate=*/false);

WriteRanges.TransformRange[Index] = ReadRanges.TransformRange[Index];
WriteRanges.DensityRange[Index]   = ReadRanges.DensityRange[Index];

每点原生属性(PCGPointPropertiesTraits.h 中的 EPCGPointNativeProperties):Transform、Density、BoundsMin、BoundsMax、Color、Steepness、Seed、MetadataEntry,以及 All 和 AllProperties。大小和分配:

Output->SetNumPoints(Input->GetNumPoints(), /*bInitializeValues=*/false);
Output->AllocateProperties(Input->GetAllocatedProperties() | EPCGPointNativeProperties::Density);
Output->CopyUnallocatedPropertiesFrom(Input);

其他数据类型:UPCGSpatialData(基类)、UPCGSplineData、UPCGLandscapeData、UPCGVolumeData、UPCGTextureData、UPCGPrimitiveData、UPCGDynamicMeshData,以及用于属性集的 UPCGParamData。UPCGSpatialData::ToBasePointData(FPCGContext*, const FBox&) 会将任意空间数据离散化为点。

C++ 中的自定义 PCG 节点

节点由 UPCGSettings 子类和一个 IPCGElement 组成。设置类保存数据;元素是 const 且无状态的,并从 FPCGContext 读取所有内容。

// MyPCGJitter.h
#pragma once

#include "PCGElement.h"
#include "PCGSettings.h"
#include "MyPCGJitter.generated.h"

UCLASS(BlueprintType, ClassGroup = (Procedural))
class MYGAME_API UMyPCGJitterSettings : public UPCGSettings
{
    GENERATED_BODY()

public:
#if WITH_EDITOR
    virtual FName GetDefaultNodeName() const override { return FName(TEXT("MyJitter")); }
    virtual FText GetDefaultNodeTitle() const override { return NSLOCTEXT("MyPCGJitter", "NodeTitle", "My Jitter"); }
    virtual EPCGSettingsType GetType() const override { return EPCGSettingsType::PointOps; }
#endif

    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta = (PCG_Overridable))
    double JitterRadius = 100.0;

protected:
    virtual TArray<FPCGPinProperties> InputPinProperties() const override { return Super::DefaultPointInputPinProperties(); }
    virtual TArray<FPCGPinProperties> OutputPinProperties() const override { return Super::DefaultPointOutputPinProperties(); }
    virtual FPCGElementPtr CreateElement() const override;
};

class FMyPCGJitterElement : public IPCGElement
{
protected:
    virtual bool ExecuteInternal(FPCGContext* Context) const override;
    virtual bool IsCacheable(const UPCGSettings* InSettings) const override { return true; }
    virtual bool CanExecuteOnlyOnMainThread(FPCGContext* Context) const override { return false; }
    virtual bool SupportsBasePointDataInputs(FPCGContext* InContext) const override { return true; }
    virtual EPCGElementExecutionLoopMode ExecutionLoopMode(const UPCGSettings* Settings) const override { return EPCGElementExecutionLoopMode::SinglePrimaryPin; }
};
// MyPCGJitter.cpp
#include "MyPCGJitter.h"

#include "PCGContext.h"
#include "Data/PCGBasePointData.h"
#include "Data/PCGSpatialData.h"
#include "Math/RandomStream.h"

FPCGElementPtr UMyPCGJitterSettings::CreateElement() const
{
    return MakeShared<FMyPCGJitterElement>();
}

bool FMyPCGJitterElement::ExecuteInternal(FPCGContext* Context) const
{
    const UMyPCGJitterSettings* Settings = Context->GetInputSettings<UMyPCGJitterSettings>();
    check(Settings);

    const double JitterRadius = Settings->JitterRadius;
    const TArray<FPCGTaggedData> Inputs = Context->InputData.GetInputsByPin(PCGPinConstants::DefaultInputLabel);
    TArray<FPCGTaggedData>& Outputs = Context->OutputData.TaggedData;

    for (const FPCGTaggedData& Input : Inputs)
    {
        const UPCGSpatialData* SpatialData = Cast<UPCGSpatialData>(Input.Data);
        if (!SpatialData)
        {
            continue;
        }

        const UPCGBasePointData* InputPoints = SpatialData->ToBasePointData(Context);
        if (!InputPoints)
        {
            continue;
        }

        UPCGBasePointData* OutputPoints = FPCGContext::NewPointData_AnyThread(Context);
        OutputPoints->InitializeFromDataWithParams(FPCGInitializeFromDataParams(InputPoints));
        OutputPoints->SetNumPoints(InputPoints->GetNumPoints(), /*bInitializeValues=*/false);
        OutputPoints->AllocateProperties(InputPoints->GetAllocatedProperties() | EPCGPointNativeProperties::Transform);
        OutputPoints->CopyUnallocatedPropertiesFrom(InputPoints);

        const FConstPCGPointValueRanges ReadRanges(InputPoints);
        FPCGPointValueRanges WriteRanges(OutputPoints, /*bAllocate=*/false);

        for (int32 Index = 0; Index < InputPoints->GetNumPoints(); ++Index)
        {
            WriteRanges.SetFromValueRanges(Index, ReadRanges, Index);

            const FRandomStream Stream(ReadRanges.SeedRange[Index]);
            FTransform Jittered = ReadRanges.TransformRange[Index];
            Jittered.AddToTranslation(Stream.VRand() * (Stream.FRand() * JitterRadius));
            WriteRanges.TransformRange[Index] = Jittered;
        }

        FPCGTaggedData& Output = Outputs.Add_GetRef(Input);
        Output.Data = OutputPoints;
    }

    return true;
}

根据头文件可以得出以下规则:

  • SupportsBasePointDataInputs 返回 false(默认值)时,PCG 会在元素运行前将每个输入转换为 UPCGPointData。返回 true,并使用值范围。
  • 如果节点会生成 Actor 或组件,或读取未跟踪的数据,IsCacheable 必须返回 false。
  • CanExecuteOnlyOnMainThread 返回 true 时,会将节点串行化到游戏线程上;除非需要访问 UWorld 或组件,否则应保持为 false。
  • 长循环应使用 FPCGAsync::AsyncProcessingRangeEx(&Context->AsyncState, NumIterations, Initialize, ProcessRange, MoveDataRange, Finished, bEnableTimeSlicing)(Helpers/PCGAsync.h),它会进行时间切片并多线程处理。
  • 引脚标签来自 PCGPinConstants::DefaultInputLabel("In")、DefaultOutputLabel("Out")、DefaultParamsLabel("Overrides")和 DefaultExecutionDependencyLabel。
  • Blueprint 节点继承自 UPCGBlueprintBaseElement,并重写 Execute(const FPCGDataCollection&, FPCGDataCollection&) BlueprintNativeEvent;种子辅助函数为 GetSeedWithContext(GetContextHandle()) 和 GetRandomStreamWithContext(GetContextHandle())。

节点设置类、引脚行为、元数据属性和 GPU 节点请参阅 PCG 节点参考。

ProceduralMeshComponent

可在运行时控制三角形级别的内容。不支持 Nanite,也不会自动生成 LOD。

PublicDependencyModuleNames.Add("ProceduralMeshComponent");
// Full form (ProceduralMeshComponent.h:190) also takes UV1, UV2 and UV3 between UV0 and VertexColors
// UV0-only convenience overload (ProceduralMeshComponent.h:193)
void CreateMeshSection_LinearColor(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles,
    const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FLinearColor>& VertexColors,
    const TArray<FProcMeshTangent>& Tangents, bool bCreateCollision, bool bSRGBConversion = false);

void UpdateMeshSection_LinearColor(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals,
    const TArray<FVector2D>& UV0, const TArray<FLinearColor>& VertexColors,
    const TArray<FProcMeshTangent>& Tangents, bool bSRGBConversion = true);   // true here, false on Create (ProceduralMeshComponent.h:230)

void ClearMeshSection(int32 SectionIndex);
void ClearAllMeshSections();
void SetMeshSectionVisible(int32 SectionIndex, bool bNewVisibility);
int32 GetNumSections() const;
void AddCollisionConvexMesh(TArray<FVector> ConvexVerts);
void ClearCollisionConvexMeshes();

材质来自 UMeshComponent::SetMaterial(int32 ElementIndex, UMaterialInterface* Material) —— 每个分区索引对应一个材质槽。

碰撞:

  • bUseComplexAsSimpleCollision(默认值为 true)会使用渲染三角形进行碰撞检测。精度高但开销大,且无法模拟;网格需要动态模拟时,将其设为 false 并传入 AddCollisionConvexMesh。
  • bUseAsyncCooking 会将物理烹饪移出游戏线程。碰撞会比可视网格滞后至少一帧;适用于远处的流式几何体。

UpdateMeshSection_LinearColor 会移动现有顶点并刷新碰撞,但无法更改顶点数或三角形数——拓扑发生变化时,请调用 CreateMeshSection_LinearColor。在工作线程上构建数组,然后在游戏线程上调用组件;请参阅异步网格生成。

动态网格和几何脚本

UDynamicMeshComponent(GeometryFramework,头文件 Components/DynamicMeshComponent.h)以及 Geometry Script 库(GeometryScriptingCore)是现代方案:布尔运算、重新网格化、法线重算,以及烘焙为 UStaticMesh 资产(仅限编辑器——在 WITH_EDITOR 之外,CopyMeshToStaticMesh 会报错 "Not currently supported at Runtime",MeshAssetFunctions.cpp:471)。

PublicDependencyModuleNames.AddRange(new string[] { "GeometryFramework", "GeometryScriptingCore" });
库代表性函数
UGeometryScriptLibrary_MeshPrimitiveFunctionsAppendBox、AppendSphereLatLong、AppendSphereBox、AppendCapsule、AppendBoxWithCollision
UGeometryScriptLibrary_MeshBooleanFunctionsApplyMeshBoolean(TargetMesh, TargetTransform, ToolMesh, ToolTransform, Operation, Options, Debug)
UGeometryScriptLibrary_MeshDeformFunctionsApplyPerlinNoiseToMesh2(TargetMesh, Selection, Options, Debug)
UGeometryScriptLibrary_MeshNormalsFunctionsRecomputeNormals(TargetMesh, CalculateOptions, bDeferChangeNotifications, Debug)、SetPerFaceNormals
UGeometryScriptLibrary_StaticMeshFunctionsCopyMeshToStaticMesh、CopyMeshFromStaticMeshV2

使用 Geometry Script 的动态网格中有一个完整示例。

UDynamicMeshComponent 上的碰撞:EnableComplexAsSimpleCollision()、SetComplexAsSimpleCollisionEnabled(bool bEnabled, bool bImmediateUpdate)、SetSimpleCollisionShapes(const FKAggregateGeom&, bool bUpdateCollision)、bDeferCollisionUpdates + UpdateCollision(bool bOnlyIfPending)。ADynamicMeshActor 在根节点附带一个组件,可通过 GetDynamicMeshComponent() 获取。

实例化静态网格

UInstancedStaticMeshComponentUHierarchicalInstancedStaticMeshComponent
头文件Components/InstancedStaticMeshComponent.hComponents/HierarchicalInstancedStaticMeshComponent.h
最适用场景数量较少、经常变更的集合数量较多、基本静态的集合
剔除起始/结束剔除距离层级树加剔除距离
移除开销低会触发树重建(bAutoRebuildTreeOnInstanceChanges、BuildTreeIfOutdated)
virtual int32 AddInstance(const FTransform& InstanceTransform, bool bWorldSpace = false);
virtual TArray<int32> AddInstances(const TArray<FTransform>& InstanceTransforms, bool bShouldReturnIndices,
    bool bWorldSpace = false, bool bUpdateNavigation = true);
virtual bool UpdateInstanceTransform(int32 InstanceIndex, const FTransform& NewInstanceTransform,
    bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
virtual bool BatchUpdateInstancesTransforms(int32 StartInstanceIndex, const TArray<FTransform>& NewInstancesTransforms,
    bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
bool GetInstanceTransform(int32 InstanceIndex, FTransform& OutInstanceTransform, bool bWorldSpace = false) const;
virtual bool RemoveInstance(int32 InstanceIndex);
virtual bool RemoveInstances(const TArray<int32>& InstancesToRemove);
virtual void PreAllocateInstancesMemory(int32 AddedInstanceCount);
int32 GetNumInstances() const;
virtual void SetNumCustomDataFloats(int32 InNumCustomDataFloats);
virtual bool SetCustomDataValue(int32 InstanceIndex, int32 CustomDataIndex, float CustomDataValue,
    bool bMarkRenderStateDirty = false);
void SetCullDistances(int32 StartCullDistance, int32 EndCullDistance);
const TArray<FBodyInstance*>& GetInstanceBodies() const;

通过 SetNumCustomDataFloats / SetCustomDataValue 设置的每实例浮点数,可在材质中通过 PerInstanceCustomData 节点读取。剔除属性:InstanceStartCullDistance、InstanceEndCullDistance、InstanceLODDistanceScale、bUseGpuLodSelection。

批量处理大量实例:先调用 PreAllocateInstancesMemory,构建完整的 TArray<FTransform>,然后调用一次 AddInstances。完整的播种散布示例会向地形发射射线,并填充每实例自定义数据,见植被散布。

植被(模块 Foliage):绘制的植被存储在由 UFoliageInstancedStaticMeshComponent 支持的 AInstancedFoliageActor 上;由模拟驱动的摆放使用带有 UProceduralFoliageSpawner 的 UProceduralFoliageComponent。对于由图驱动的散布,通常更适合使用 PCG 的 Static Mesh Spawner 节点。

样条线

// USplineComponent — Components/SplineComponent.h
void AddSplinePoint(const FVector& Position, ESplineCoordinateSpace::Type CoordinateSpace, bool bUpdateSpline = true);
void SetSplinePoints(const TArray<FVector>& Points, ESplineCoordinateSpace::Type CoordinateSpace, bool bUpdateSpline = true);
void SetSplinePointType(int32 PointIndex, ESplinePointType::Type Type, bool bUpdateSpline = true);
void SetTangentsAtSplinePoint(int32 PointIndex, const FVector& InArriveTangent, const FVector& InLeaveTangent,
    ESplineCoordinateSpace::Type CoordinateSpace, bool bUpdateSpline = true);
void SetClosedLoop(bool bInClosedLoop, bool bUpdateSpline = true);
virtual void UpdateSpline();
float GetSplineLength() const;
FVector GetLocationAtDistanceAlongSpline(float Distance, ESplineCoordinateSpace::Type CoordinateSpace) const;
FTransform GetTransformAtDistanceAlongSpline(float Distance, ESplineCoordinateSpace::Type CoordinateSpace,
    bool bUseScale = false) const;
void GetLocationAndTangentAtSplinePoint(int32 PointIndex, FVector& Location, FVector& Tangent,
    ESplineCoordinateSpace::Type CoordinateSpace) const;
float FindInputKeyClosestToWorldLocation(const FVector& WorldLocation) const;

ESplinePointType::Type:Linear、Curve、Constant、CurveClamped、CurveCustomTangent。ESplineCoordinateSpace::Type:Local、World。

批量编辑时传入 bUpdateSpline = false,并在完成后调用一次 UpdateSpline()——每次更新都会重建重参数化表。沿样条线的距离是弧长;输入键则不是,因此始终应按距离排列实例。

// USplineMeshComponent — one deformed mesh per spline segment
void AMyRoadActor::BuildSegment(USplineComponent* Spline, UStaticMesh* RoadMesh, int32 SegmentIndex)
{
    USplineMeshComponent* SegmentMesh = NewObject<USplineMeshComponent>(this);
    SegmentMesh->SetMobility(EComponentMobility::Movable);
    SegmentMesh->SetupAttachment(Spline);
    SegmentMesh->SetStaticMesh(RoadMesh);
    SegmentMesh->SetForwardAxis(ESplineMeshAxis::X, /*bUpdateMesh=*/false);
    SegmentMesh->RegisterComponent();

    FVector StartPos, StartTangent, EndPos, EndTangent;
    Spline->GetLocationAndTangentAtSplinePoint(SegmentIndex, StartPos, StartTangent, ESplineCoordinateSpace::Local);
    Spline->GetLocationAndTangentAtSplinePoint(SegmentIndex + 1, EndPos, EndTangent, ESplineCoordinateSpace::Local);
    SegmentMesh->SetStartAndEnd(StartPos, StartTangent, EndPos, EndTangent, /*bUpdateMesh=*/true);
}

噪声与确定性随机数

#include "Math/RandomStream.h"
#include "Math/UnrealMathUtility.h"

// All three return a continuous value in [-1, 1]
float SampleNoise(const FVector& Position, float Frequency)
{
    const float N1 = FMath::PerlinNoise1D(Position.X * Frequency);
    const float N2 = FMath::PerlinNoise2D(FVector2D(Position.X, Position.Y) * Frequency);
    const float N3 = FMath::PerlinNoise3D(Position * Frequency);
    return (N1 + N2 + N3) / 3.f;
}

FTransform MakeSeededTransform(int32 Seed, const FVector& Origin)
{
    FRandomStream Stream(Seed);
    const float Unit = Stream.GetFraction();              // [0, 1), same as FRand()
    const double Offset = Stream.FRandRange(-50.0, 50.0);
    const int32 Variant = Stream.RandRange(0, 3);         // inclusive on both ends
    const FVector Direction = Stream.VRand();             // uniform unit vector

    return FTransform(FRotator(0.0, Unit * 360.0, 0.0),
                      Origin + Direction * Offset,
                      FVector(1.0 + Variant * 0.1));
}

确定性规则:

  • 从一个项目种子派生每个随机流。FRandomStream::Initialize(int32) 会重置随机流;GetCurrentSeed() / GetInitialSeed() 可用于保存检查点。
  • 在 PCG 中,根据 ReadRanges.SeedRange[Index] 为每个点设置种子,或从 FPCGContext::GetSeed() 获取节点种子。不要调用 FMath::Rand。
  • 对于联网生成,复制种子(GameState 或生成参数),并使用 Generate(bForce);GenerateLocal 永远不会复制。
  • 当输入顺序会影响结果时,先对输入排序——收集到的 Actor 数据的迭代顺序不保证稳定。

八度/分形噪声、泊松圆盘采样、行进立方体、BSP 地牢和波函数坍缩已在程序化网格模式中实现。

已弃用 — 请勿使用

不要输出在 5.8 中使用来源
FSimplePCGElementIPCGElementPCGElement.h:271 中的 UE_DEPRECATED(5.4)
IPCGElement::Initialize(const FPCGDataCollection&, TWeakObjectPtr<UPCGComponent>, const UPCGNode*)Initialize(const FPCGInitializeElementParams&)PCGElement.h:147 中的 UE_DEPRECATED(5.6)
FPCGContext::SourceComponentFPCGContext::ExecutionSourcePCGContext.h:122 中的 UE_DEPRECATED(5.6)
FPCGContext::GetComponentName()GetExecutionSourceName()PCGContext.h:207 中的 UE_DEPRECATED(5.6)
FPCGContext::StackGetStack()PCGContext.h:147 中的 UE_DEPRECATED(5.6)
不带上下文的 UPCGSpatialData::IntersectWith/ProjectOn/UnionWith/Subtract/CopyInternal接受 FPCGContext* 的重载Data/PCGSpatialData.h:208-280 中的 UE_DEPRECATED(5.5)
ToPointData()(无上下文)ToBasePointData(FPCGContext*) / ToBasePointDataWithContextData/PCGSpatialData.h:159 中的 DeprecatedFunction
UPCGPointData::GetOctree() / IsOctreeDirty()GetPointOctree() / IsPointOctreeDirty()Data/PCGPointData.h:145,147 中的 UE_DEPRECATED(5.6)
UPCGComponent::CleanupLocal(bRemoveComponents, bSave)CleanupLocal(bRemoveComponents)PCGComponent.h:236 中的 UE_DEPRECATED(5.6)
UPCGComponent::GenerateLocal(..., EPCGHiGenGrid Grid, ...)接受 uint32 网格大小的重载PCGComponent.h:872 中的 UE_DEPRECATED(5.8)
UPCGGraph::HiGenExponential / GetGridExponential()HiGenGridSizeMultiplier / GetGridSizeMultiplier()PCGGraph.h:866,872 中的 UE_DEPRECATED(5.8)
UPCGGraph::bIsEditorOnlyShouldCook(FPerPlatformBool)PCGGraph.h:868 中的 UE_DEPRECATED(5.8)
UPCGSettings::BP_GetTypeUnionOfIncidentEdgesGetTypeUnionIDOfIncidentEdgesPCGSettings.h:520 中的 UE_DEPRECATED(5.7)
UInstancedStaticMeshComponent::InstanceBodiesGetInstanceBodies()Components/InstancedStaticMeshComponent.h:524 中的 UE_DEPRECATED(5.8)
UInstancedStaticMeshComponent::InitInstanceBodyInstancePhysicsBodiesComponents/InstancedStaticMeshComponent.h:771 中的 UE_DEPRECATED(5.8)
ApplyPerlinNoiseToMeshApplyPerlinNoiseToMesh2GeometryScript/MeshDeformFunctions.h:401 中的 UE_DEPRECATED(5.7)
不带 bUseSectionMaterials 的 CopyMeshToStaticMesh接受 bUseSectionMaterials 的重载GeometryScript/MeshAssetFunctions.h:311 中的 UE_DEPRECATED(5.5)

常见错误

在自定义元素中迭代 FPCGPoint 数组:GetPoints() 仅存在于 UPCGPointData 中,因此 PCG 会静默转换每个输入,并产生一次完整复制的开销。重写 SupportsBasePointDataInputs 并使其返回 true,然后读取 FConstPCGPointValueRanges。

**写入前忘记调用 AllocateProperties:**使用 bAllocate = false 构建的 FPCGPointValueRanges 会让尚未分配的范围保持为空,因此对其进行索引会触发 checkf 范围检查(Utils/PCGValueRange.h:139)。对打算写入的每个属性,先调用 SetNumPoints,再调用 AllocateProperties。

**在多人游戏中使用 GenerateLocal:**它不是网络函数,因此客户端永远不会生成。请使用 Generate(bool bForce)(NetMulticast, Reliable),并复制随机种子。

**从 Tick 生成:**PCG 生成会调度图任务。请使用 GenerateOnDemand,仅在输入发生变化时调用 Generate;或者使用 GenerateAtRuntime,由调度器分配预算。

**缓存会生成 Actor 的节点:**对于会创建 Actor 或组件的节点,如果仍将 IsCacheable 设为 true,再生成时就会产生重复或缺失的产物。请返回 false。

**以为 UpdateMeshSection_LinearColor 会改变拓扑:**它只会重写现有顶点。要添加或移除三角形,必须使用 CreateMeshSection_LinearColor。

**三角形绕序为顺时针:**正面为逆时针,因此顺时针三角形会在背面剔除时消失。

**在循环中调用 AddSplinePoint 并将 bUpdateSpline = true:**每次调用都会重建整个重参数化表。请传入 false,并仅调用一次 UpdateSpline()。

**按样条输入键间隔放置实例:**该键与弧长不成正比。请按距离步进,并使用 GetTransformAtDistanceAlongSpline。

**每次更新实例都设置 bMarkRenderStateDirty = true:**每次调用都会重新上传实例缓冲区。请在循环中将其设为 false,并仅调用一次 MarkRenderStateDirty()。

**期待 UProceduralMeshComponent 支持 Nanite:**它不支持 Nanite。请在编辑器中使用 CopyMeshToStaticMesh(仅限编辑器)烘焙为 UStaticMesh,或者使用 PCG 和实例化组件散布 Nanite 静态网格体。

相关技能

  • ue-actor-component-architecture — 此处创建的组件所涉及的组件构造、注册、附加和生命周期
  • ue-physics-collision — 碰撞配置文件、主体设置、复杂与简单碰撞,以及用于将散布结果投射到地形上的射线检测
  • ue-materials-rendering — 用于生成几何体的材质实例、PerInstanceCustomData、Nanite 和虚拟纹理
  • ue-world-level-streaming — PCG 分区和运行时生成所依赖的 World Partition、数据层和 HLOD
  • ue-async-threading — 用于后台网格体和点计算的 ParallelFor、Async、任务图及线程安全规则
  • ue-mass-entity — 大规模 Agent 群体;这是人群场景中实例化组件的替代方案
  • ue-data-assets-tables — 用于驱动生成参数以及网格体/道具表的数据资产和数据表
  • ue-niagara-effects — Niagara 系统、用户参数、数据接口和数据通道

评论

支持 Markdown