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. 03C++ による PCG ノード作成
  4. 04ランタイムプロシージャルメッシュ
  5. 05Dynamic Mesh と Geometry Script
  6. 06インスタンス化、スプライン、ノイズ

活用シーン

  1. 01PCG プラグインとモジュールの設定
  2. 02PCG グラフノードの作成や拡張
  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)、Geometry Script と UDynamicMeshComponent(モジュール GeometryFramework および GeometryScriptingCore)、UInstancedStaticMeshComponent/UHierarchicalInstancedStaticMeshComponent によるインスタンス化、スプライン駆動の配置、Core による決定論的なノイズ/乱数を扱います。

コンテキスト

.agents/ue-project-context.md が存在する場合は読み込んでください(モジュール名、規約、有効なプラグイン、GAS/ネットワーキングの設定)。ファイルが見つからなくても作業を中断しないでください。

リクエストとコードベースから対象領域を特定してください。考えられる解釈が 2 つあり、それぞれで異なるコードになる場合に限り、質問してください。

リクエストの内容参照先
PCG の有効化、モジュール/プラグインの接続PCG のセットアップ
アクターからの生成制御、ランタイム生成、パーティショニングPCG コンポーネントとランタイム生成
グラフ内のポイントの読み書きポイントデータ
C++ で新しい PCG ノードを作成するC++ のカスタム PCG ノード
X を行うノード、ピンのラベル、ノード設定のフィールドPCG ノードリファレンス
ランタイムでの三角形の構築ProceduralMeshComponent
ブーリアン演算、プリミティブ、スタティックメッシュへのベイクダイナミックメッシュと Geometry Script
数千個の繰り返しメッシュインスタンス化されたスタティックメッシュ
道路、河川、フェンス、ケーブルスプライン
ハイトフィールド、散布、再現可能な結果ノイズと決定論的な乱数
マーチングキューブ、BSP ダンジョン、WFC、ポアソンディスクプロシージャルメッシュのパターン

PCG のセットアップ

// MyGame.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "PCG" });
{ "Name": "PCG", "Enabled": true }
クラスヘッダー役割
UPCGComponentPCGComponent.hグラフを所有し、生成を制御するアクターコンポーネント
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.hUPCGComponent を持つボリュームアクター

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 を返す場合は、値範囲を使用してください。
  • ノードがアクターやコンポーネントを生成する場合、または追跡されていないデータを読み取る場合、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 を継承し、BlueprintNativeEvent の Execute(const FPCGDataCollection&, FPCGDataCollection&) をオーバーライドします。シードヘルパーは 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) で設定します。セクションインデックスごとにマテリアルスロットが 1 つあります。

コリジョン:

  • bUseComplexAsSimpleCollision(デフォルトは true)は、描画された三角形をコリジョンに使用します。精度は高いものの処理コストが大きく、シミュレーションには使用できません。メッシュを動的にする必要がある場合は false に設定し、AddCollisionConvexMesh にメッシュを渡してください。
  • bUseAsyncCooking を設定すると、物理クッキングをゲームスレッド以外で実行します。コリジョンは視覚メッシュより 1 フレーム以上遅れます。遠方のストリーミングジオメトリで使用してください。

UpdateMeshSection_LinearColor は既存の頂点を移動してコリジョンを更新しますが、頂点数や三角形数は変更できません。トポロジーが変わる場合は CreateMeshSection_LinearColor を呼び出してください。配列はワーカースレッドで構築し、その後ゲームスレッドでコンポーネントを呼び出します。非同期メッシュ生成を参照してください。

ダイナミックメッシュとジオメトリスクリプト

UDynamicMeshComponent(GeometryFramework、ヘッダーは Components/DynamicMeshComponent.h)と Geometry Script ライブラリ(GeometryScriptingCore)が、現代的な方法です。ブーリアン演算、リメッシュ、法線の再計算、UStaticMesh アセットへのベイクに対応しています(エディター専用です。CopyMeshToStaticMesh は WITH_EDITOR の外では「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 は複製されません。
  • 順序が結果に影響する場合は、入力を消費する前にソートしてください。収集したアクターデータの反復順序は安定しているとは限りません。

オクターブ/フラクタルノイズ、ポアソンディスクサンプリング、マーチングキューブ、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/CopyInternalFPCGContext* を受け取るオーバーロード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 を指定しない CopyMeshToStaticMeshbUseSectionMaterials を受け取るオーバーロード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を使用してスケジューラーに処理量を調整させてください。

アクターをスポーンするノードをキャッシュする: アクターやコンポーネントを作成するノードでIsCacheableをtrueのままにすると、再生成時にアーティファクトが重複したり、欠落したりします。falseを返してください。

UpdateMeshSection_LinearColorでトポロジーが変わると期待する: これは既存の頂点を書き換えるだけです。三角形を追加または削除するには、CreateMeshSection_LinearColorを使用してください。

三角形の頂点順序を時計回りにする: 表面の面は反時計回りなので、時計回りの三角形はバックフェースカリングで消えてしまいます。

ループ内でbUpdateSpline = trueを指定してAddSplinePointを呼び出す: 呼び出しごとに再パラメーター化テーブル全体が再構築されます。falseを渡し、最後に一度だけUpdateSpline()を呼び出してください。

スプラインの入力キーでインスタンス間隔を設定する: キーは弧長に比例しません。距離単位で進め、GetTransformAtDistanceAlongSplineを使用してください。

インスタンスを更新するたびにbMarkRenderStateDirty = trueを指定する: 呼び出しのたびにインスタンスバッファーが再アップロードされます。ループ内ではfalseのままにし、最後に一度だけMarkRenderStateDirty()を呼び出してください。

UProceduralMeshComponentでNaniteが使えると期待する: Naniteには対応していません。エディターでCopyMeshToStaticMesh(エディター専用)を使ってUStaticMeshにベイクするか、Nanite対応のスタティックメッシュをPCGとインスタンスコンポーネントで散布してください。

関連スキル

  • 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 — 大規模なエージェント群。群衆向けのインスタンスコンポーネントに代わる方法
  • ue-data-assets-tables — 生成パラメーターやメッシュ/プロップテーブルを駆動するデータアセットとデータテーブル
  • ue-niagara-effects — Niagaraシステム、ユーザーパラメーター、データインターフェース、データチャネル

コメント