移动/游戏
在虚幻引擎中做程序化生成时,如何选择并正确使用 PCG…
当需要在 Unreal Engine 中生成世界布局、地形、地牢、植被或运行时网格时,按生成类型选用 PCG、ProceduralMeshComponent、ISM/HISM、噪声函数或样条并规避常见性能与确定性陷阱。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在虚幻引擎中做程序化生成时,如何选择并正特定
解决思路
- 先读取 `.agents/ue-project-context.md`,确认 PCG 插件是否启用、生成类型(世界布局/地形/地牢/植被/运行时网格)、性能约束(移动端/主机/Nanite)以及多人需求(服务器权威 vs 确定性种子)。
- 向用户澄清五点:生成类型(PCG 世界填充 / ProceduralMeshComponent 运行时网格 / ISM-HISM 实例化几何 / 样条驱动)、时机(编辑器烘焙还是运行时动态)、实例数量(数百用 ISM,数万用 HISM)、是否需要物理碰撞、是否需要确定性(同种子跨会话或跨网络客户端结果一致)。
- PCG 插件接入:在 Build.cs 的 PublicDependencyModuleNames 加入 "PCG",并在 .uproject 的 Plugins 数组中启用 { "Name": "PCG", "Enabled": true }。
- PCG 核心类:UPCGComponent(驱动生成的 Actor 组件)、UPCGGraph(节点+边的资产)、UPCGGraphInstance(带参数覆盖的图实例)、UPCGPointData(节点间点云)、UPCGSettings(节点设置基类)、UPCGBlueprintBaseElement(自定义蓝图节点基类)。
- PCG 组件 API:SetGraph 指定图(NetMulticast)、Generate(bForce) 触发生成(NetMulticast, Reliable,多人用)、GenerateLocal(bForce) 本地非复制生成、Cleanup/CleanupLocal 清理、NotifyPropertiesChangedFromBlueprint 在蓝图属性变更后重新求值、GetGeneratedGraphOutput 读取生成输出。
- PCG 生成触发模式(EPCGComponentGenerationTrigger):GenerateOnLoad(BeginPlay 一次性)、GenerateOnDemand(仅显式调用 Generate())、GenerateAtRuntime(由 UPCGSubsystem 按预算调度)。
- PCG 图节点 API:AddNodeOfType 按设置类添加节点、AddEdge 连接两个节点、GetGraphParameter/SetGraphParameter 读写图参数。
- 自定义蓝图 PCG 节点:继承 UPCGBlueprintBaseElement,实现 Execute(Input, Output);用 GetRandomStreamWithContext(GetContextHandle()) 获取确定性随机流;遍历 Input.GetInputsByPin(PCGPinConstants::DefaultInputLabel),Cast 到 UPCGPointData,用 NewObject<UPCGPointData>() 创建输出并逐点修改后写入 Output.TaggedData。
- 自定义 PCG 节点属性:节点生成 Actor 或组件时设 bIsCacheable = false;需要 Actor 生成、组件添加时设 bRequiresGameThread = true;用 CustomInputPins/CustomOutputPins 添加额外类型引脚。
- PCG 确定性:PCG 图默认确定性,同种子产生相同输出,每个节点通过 GetRandomStreamWithContext() 获得带种子的随机流;要跨实例变化就设置 PCG 组件的 Seed 属性;多人时确保所有客户端使用同一种子(通过 GameState 复制或作为生成参数传入),运行时设置 PCG->Seed 后调用 PCG->Generate() 重新生成。
- PCG 数据类型选择:FPCGPoint/Point Data(位置、旋转、缩放、密度、颜色,用于散布放置、植被、实例定位)、UPCGSplineData(样条点+切线,用于道路、河流、路径、边界)、UPCGLandscapeData(高度+层权重采样,用于地形感知放置、生物群系查询)、UPCGVolumeData(3D 边界,用于体积过滤与生成);另有 UPCGTextureData、UPCGPrimitiveData、UPCGDynamicMeshData。
- ProceduralMeshComponent 接入:在 Build.cs 的 PublicDependencyModuleNames 加入 "ProceduralMeshComponent"。
- ProceduralMesh 核心 API:CreateMeshSection(顶点、三角形 CCW 为正面、法线、UV、顶点色、切线、bCreateCollision)、UpdateMeshSection(更新顶点位置与碰撞,但不能改变拓扑)、ClearMeshSection、ClearAllMeshSections、SetMeshSectionVisible、SetMaterial。
- 地形网格生成:双重循环按 Grid 采样八度噪声(SampleOctaveNoise)生成顶点、法线、UV,再按 BL/TL/TR 与 BL/TR/BR 两组三角形构建索引,最后调用 CreateMeshSection(0, Verts, Tris, Norms, UVs, Colors, Tangs, true)。
- ProceduralMesh 性能:每个 CreateMeshSection 一个 draw call,每段顶点数保持 < 65K;UpdateMeshSection 不能改变拓扑,新增三角形必须调用 CreateMeshSection;ProceduralMesh 不支持 Nanite;顶点数据在后台线程计算,CreateMeshSection 只能在游戏线程调用。
- 异步网格生成:用 FNonAbandonableTask 子类在后台线程计算顶点与三角形,DoWork 中做 marching cubes、噪声采样等;用 FAsyncTask(不要用 FAutoDeleteAsyncTask,否则完成后调用 IsDone() 是 use-after-free),StartBackgroundTask 后轮询 IsDone(),完成后取 Task->GetTask().Vertices 并 delete Task。
- ProceduralMesh 碰撞:设 UProceduralMeshComponent::bUseComplexAsSimpleCollision = true 直接用渲染三角形做碰撞,精确但昂贵,仅用于静态几何;动态或高面数网格应生成简化凸包。
- ISM/HISM 选择:< 1,000 动态实例用 ISM(仅距离剔除),> 1,000 且基本静态用 HISM(层级 BVH + 距离剔除、内置 LOD 过渡、异步 BVH 重建)。
- ISM 核心 API:AddInstance、AddInstances(返回索引)、UpdateInstanceTransform、BatchUpdateInstancesTransforms、GetInstanceTransform、RemoveInstance(ISM 为 O(n),HISM 触发异步 BVH 重建)、PreAllocateInstancesMemory、GetNumInstances;每实例自定义浮点数据用 SetNumCustomDataFloats、SetCustomDataValue、SetCustomData(材质中通过 PerInstanceCustomData 读取)。
- ISM 剔除属性:InstanceStartCullDistance、InstanceEndCullDistance、InstanceLODDistanceScale、bUseGpuLodSelection。
- 植被散布(HISM + 地形射线):SetStaticMesh、SetNumCustomDataFloats(1)、PreAllocateInstancesMemory(Count);用 FRandomStream(Seed) 在范围内随机 XY,从 +5000 到 -5000 做 LineTraceSingleByChannel(ECC_WorldStatic) 取地面 Z;构造 FTransform(随机 Y 旋转、随机 0.8~1.3 缩放)批量 AddInstances(Transforms, true, true),再对返回索引逐个 SetCustomDataValue(i, 0, Rand.FRand(), false),最后 MarkRenderStateDirty()。
- 植被系统:编辑器 Foliage 绘制模式使用 AInstancedFoliageActor(内部包装 UHierarchicalInstancedStaticMeshComponent);大规模程序化植被用 UProceduralFoliageComponent 配合 UProceduralFoliageSpawner,通过模拟(物种竞争、耐阴性)分布而非手动绘制。
- 每实例碰撞:在 ISM 组件上启用 bUseDefaultCollision,每个实例继承静态网格的碰撞;ISM 不支持每实例独立碰撞形状,需要时改用独立 Actor。
- 平台限制:HISM GPU 缓冲上限因平台而异(桌面约 1M,移动端约 100K),用 stat Foliage 监控,超限时把大种群拆分到多个 HISM 组件。
- 噪声与数学:FMath::PerlinNoise1D/2D/3D 输出范围 [-1, 1];八度噪声按 V += PerlinNoise2D(X,Y)*F * A、Max += A、A *= Persist、F *= Lacu 累加后除以 Max 归一化;用 FRandomStream(Seed) 的 FRandRange、RandRange、VRand 做确定性随机。
- 高度/密度图:通过 FTexturePlatformData 采样 UTexture2D 像素数据驱动地形高度或放置密度,用 BulkData.Lock(LOCK_READ_ONLY) 加锁读取后解锁;自然散布的最小间距用 Poisson 圆盘采样(Bridson 算法,完整实现见 references/procedural-mesh-patterns.md)。
- 样条构建:AddSplinePoint、SetSplinePoints、ClearSplinePoints 批量时传 bUpdateSpline=false,最后调用一次 UpdateSpline() 重建重参数化表。
- 样条按弧长查询:GetLocationAtDistanceAlongSpline、GetDirectionAtDistanceAlongSpline、GetRightVectorAtDistanceAlongSpline、GetRotationAtDistanceAlongSpline、GetTransformAtDistanceAlongSpline、GetSplineLength;用 FindInputKeyClosestToWorldLocation 把世界位置吸附到样条。
- 样条点编辑:GetNumberOfSplinePoints、SetSplinePointType(Linear/Curve/Constant/CurveClamped/CurveCustomTangent)、SetClosedLoop、SetTangentsAtSplinePoint;运行时修改后调用 UpdateSpline() 重建,批量修改后再调用一次。
- 样条放置实例:取 GetSplineLength(),按 Spacing 步长循环,用 GetTransformAtDistanceAlongSpline(D, World) 得到变换并 HISM->AddInstance(T, true)。
- USplineMeshComponent 网格变形:NewObject<USplineMeshComponent>、SetStaticMesh、RegisterComponent;用 GetLocationAndTangentAtSplinePoint 取相邻两点的位置与切线,调用 SetStartAndEnd(SP, ST, EP, ET, true),再 SetForwardAxis(ESplineMeshAxis::X)。
- 运行时网格生成模式(完整实现见 references/procedural-mesh-patterns.md):Marching Cubes(3D 密度标量场等值面)、Dungeon BSP(BSP 划分房间、L 形走廊雕刻、瓦片转网格)、L-System(字符串重写+海龟解释器生成 HISM 分支)、Wave Function Collapse(约束传播瓦片网格布局)、异步网格生成(后台线程算顶点、游戏线程 CreateMeshSection)、样条道路挤出(沿 USplineComponent 扫掠截面轮廓)。
- 避免 PCG 常见错误:不要在 Tick 中调用 GenerateLocal()(生成不廉价,用 GenerateOnDemand 仅在数据变化时重新生成);多人中不要用 GenerateLocal()(不复制,用 Generate(bForce));重自定义节点不要设 bIsCacheable = true(仅当输出只依赖输入+种子时才缓存);bIsEditorOnly = true 的图无法烘焙进打包版本。
- 避免 ProceduralMesh 常见错误:CreateMeshSection 不要传 bCreateCollision=false(角色会穿透);不要指望 UpdateMeshSection 改变拓扑(顶点数必须匹配,新增三角形用 CreateMeshSection);不要用 ProceduralMesh 做 Nanite 级地形(不支持,改用 Landscape 或 PCG+ISM);注意三角形绕序必须 CCW,否则背面剔除导致不可见。
- 避免 ISM/HISM 常见错误:超过约 500 实例不要用 ISM(改用 HISM 获得 BVH 剔除);循环中不要每次 UpdateInstanceTransform 都设 bMarkRenderStateDirty=true(只在最后一次设 true);批量添加前不要跳过 PreAllocateInstancesMemory(反复重分配会降低性能)。
- 避免样条常见错误:循环中不要用 AddSplinePoint(bUpdateSpline=true)(每次都会重建重参数化表,应传 false 最后调用一次 UpdateSpline());均匀间距不要用样条输入 key(key 与弧长不成比例,应用距离)。
- 多人注意:程序化内容必须确定性(同种子)或服务器权威;GenerateLocal() 不复制,Generate(bool) 是 NetMulticast, Reliable。
适用边界 · 注意事项
- 项目未启用 PCG 插件或引擎版本低于 UE 5.2 时,不能使用 PCG 框架相关做法。
- 需要 Nanite 级别地形或高面数动态网格时,不要用 ProceduralMeshComponent(不支持 Nanite),应改用 Landscape 或 PCG+ISM。
- 需要每实例独立碰撞形状时不要用 ISM/HISM(不支持),应改用独立 Actor。