C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

UE4: ProceduralMeshComponent 入門

はてなブログよ、わたしは帰ってきた・w・ と、いうわけで早速本題に入りましょ。

UE-4.14 から導入された事になっている ProcedualMeshComponent ですが、いまひとつ公式ドキュメントでも最低限のエグザンプルとブループリントの情報しかまだ整理されていないようなので、C++による日本語の入門まとめを書いておこうと思います。

必要最小限の使い方: C++

↓とりあえずこんなのを出すエグザンプルとtips。

cppf:id:USAGI-WRP:20170526231022p:plain

1. MyProject.Build.cs

  1. PublicDependencyModuleNames.AddRange"ProceduralMeshComponent" を追加。

2. MyActor.h

一般的に Actor 系のヘッダーで UProceduralMeshComponentポインター型を使いたいだけならクラスの宣言だけ AActor クラスの宣言の手前に書いておけば十分。

class UProceduralMeshComponent;

class AActor で保持して使いたい事が多いと思うので UProceduralMeshComponent * のメンバー変数を宣言しておく。

UProceduralMeshComponent* mesh;

3. MyActor.cpp

とりあえず必要最小限、"さんかく1枚を実行時に作る"実装例。

ProceduralMeshComponent の中身を使うので素直にヘッダーを include

#include "ProceduralMeshComponent.h"

とりあえず、という事なら以下の実装を AMyActor::AMyActor でまとめて行っても「はじめてのさんかく」は表示できる。

AMyActor::AMyActor()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  PrimaryActorTick.bCanEverTick = true;
  
  // mesh の中身を作る。
  mesh = CreateDefaultSubobject<UProceduralMeshComponent>( TEXT( "generated-mesh" ) );
  // とりあえず RootComponent として mesh をつっこむ。
  RootComponent = mesh;
  
  // ここから ProceduralMeshComponent のオブジェクトにメッシュの中身のデータを作らせるコード
  // 頂点群
  TArray<FVector> vertices;
  // インデックス群
  TArray<int32> indices;
  // 法線群(今回は空っぽのまま)
  TArray<FVector> normals;
  // テクスチャー座標群
  TArray<FVector2D> texcoords0;
  // 頂点カラー群(今回は空っぽのまま)
  TArray<FLinearColor> vertex_colors;
  // 接線群(今回は空っぽのまま)
  TArray<FProcMeshTangent> tangents;

  // 「はじめてのさんかっけい」に必要な3点の頂点座標を生成。
  vertices.Emplace( 0.0e+0, 0.0e+0, 0.0e+0 );
  vertices.Emplace( 0.0e+0, 1.0e+2, 0.0e+0 );
  vertices.Emplace( 1.0e+2, 0.0e+0, 0.0e+0 );

  // UE は「左手座標系Z-top」の「反時計回り面生成」なので↑の頂点をこの順序で繋ぐと+Z向きに三角面1つを生成できる。
  indices.Emplace( 0 );
  indices.Emplace( 1 );
  indices.Emplace( 2 );

  // テクスチャー座標を設定しておけばこんなエグザンプルでも適当なマテリアルをセットして模様出し確認はできます。
  texcoords0.Emplace( 0, 0 );
  texcoords0.Emplace( 0, 0 );
  texcoords0.Emplace( 0, 0 );

  // UProceduralMeshComponent::CreateMeshSection_LinearColor でメッシュを生成。
  // 第1引数: セクション番号; 0, 1, 2, ... を与える事で1つの UProceduralMeshComponent に複数のメッシュを内部的に同時に生成できます。
  // 第2引数: 頂点群
  // 第3引数: インデックス群
  // 第4引数: 法線群
  // 第5引数: テクスチャー座標群
  // 第6引数: 頂点カラー群
  // 第7引数: 法線群
  // 第8引数: コリジョン生成フラグ
  mesh->CreateMeshSection_LinearColor( 0, vertices, indices, normals, texcoords0, vertex_colors, tangents, true );

実用化にあたって手始めにすると良いかもしれないこと

  1. CreateMeshSection_LinearColor 周りのメッシュ生成コードを generate_mesh メンバー関数に分離。
  2. AActor::AActor ではなく AActor::PostLoad AActor::PostActorCreated などを override して↑の generate_mesh を呼ぶようにする。
  3. 適当なイベントまたは Tick などでアニメーションさせてみる。

執筆現在の残念なところ

  1. インデックスバッファーを TRIANGLES でしか扱えないところ。
    • せめて TRIANGLE-STRIP には対応して欲しいなー。

その他 tips

  1. CreateMeshSection_LinearColor の他に CreateMeshSection でも同様にメッシュ生成できる。
    • CreateMeshSection_LinearColor: 第6引数 を TArray<FLinearColor> で与える版
    • CreateMeshSection: 第6引数を TArray<FColor> で与える版
  2. ここまでの知識だけでも実行時に頂点やインデックスが複雑に変形するメッシュは作れる。
    1. AActor::Tickoverride してアニメーションを定義可能にし、
    2. 毎フレーム目的の状態のメッシュを CreateMeshSection_LinearColor 等で生成する。
  3. UProceduralMeshComponent の応用機能(上記までの「必要最小限の使い方」で紹介していない、という意味の)
    1. UpdateMeshSection_LinearColor または UpdateMeshSection
      • トポロジーの変更を伴わないメッシュの更新」をするための機能。
      • アニメーションを実現したい場合は Create 系に代えて Update 系を使った方が高速に動作する。
    2. ClearMeshSection: セクション番号を指定してメッシュを消す。
    3. ClearAllMeshSections: すべてのメッシュを消す。
    4. SetMeshSectionVisible: セクション番号のメッシュの仮死状態の boolean な切り替えを行う。
      • この機能を応用すると単純な表示/非表示として使うほか、予め複数のセクションに生成したメッシュを順に切り替えるアニメーションなども実装できる。いちおう。
    5. IsMeshSectionVisible: セクション番号のメッシュの可視状態を取得できる。
    6. GetNumSections: メッシュが生成されているセクションの数を取得できる。
    7. AddCollisionConvexMesh: 凸形状コリジョンを追加できる。
    8. ClearCollisionConvexMeshes: 凸形状コリジョンを消せる。
    9. SetCollisionConvexMeshes: 凸形状コリジョン群を TArray<TArray<FVector>> で一度にまとめて設定(既存の設定群とは置き換え)できる。
    10. ProcMeshBodySetup: コリジョンデータ。
    11. GetProcMeshSection: セクション番号のメッシュの内部データを取得する。
    12. SetProcMeshSection: セクション番号のメッシュの内部データを直接設定する。
    13. Interface_CollisionDataProvider から継承した機能群: GetPhysicsTriMeshData ContainsPhysicsTriMeshData WantsNegXTriMesh
    14. bUseComplexAsSimpleCollision: シンプルな凸形状コリジョンを使う場合は false 、複雑なジオメトリーを使う場合は true を設定するフラグ。
    15. UPrimitiveComponent から継承した機能群: CreateSceneProxy GetBodySetup GetMaterialFromCollisionFaceIndex
    16. UMeshComponent から継承した機能群: GetNumMaterials
    17. UObject から継承した機能: PostLoad

参考

  1. Procedural Mesh Component in C++:Getting Started - Epic Wiki
    • 「さいしょのさんかっけい」の原型
  2. GitHub - SiggiG/ProceduralMeshes: Plugin with example procedural mesh actors and components
    • ProceduralMeshComponent の応用実装の参考になる
  3. ProceduralMeshComponent | Unreal Engine API Reference
    • ブループリントのリファレンスだけどC++erにも無いよりまし
  4. https://github.com/EpicGames/UnrealEngine/tree/release/Engine/Plugins/Runtime/ProceduralMeshComponent
    • 結局ソース読むのがいちばんはやい(特に執筆現在まだ丁寧なC++er向けの解説は無いようなものなので)