UE4でダイナミックメッシュ

  • 23
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

2016/08/28 追記

  • 4.10以降であれば ProceduralMeshComponent が標準で実装されているので(ただしExperimental)、そっちを用いるのが良いと思います。 https://docs.unrealengine.com/latest/INT/BlueprintAPI/Components/ProceduralMesh/index.html
  • いずれかのバージョン以降で正常に動作しないことについてご指摘を受けました。 FMySceneProxy::GetViewRelevance 関数に const 修飾子を付加する必要があります(オーバーライド元にそういう変更があったようです)。
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
{

概要

表題の通り、UE4でダイナミックメッシュを表示する方法について、ポイントをシェアします。

必要な知識

参考成果物

凝ったものはありませんが。こんな感じということで載せておきます。
shell02.PNG

https://www.youtube.com/watch?v=oKxBs0qrE6o
https://www.youtube.com/watch?v=jJyDjgWkLF4
https://www.youtube.com/watch?v=9BVyNwI3IMc

導入(ダイナミックメッシュとは)

ここで言うダイナミックメッシュとはAssetとしてではなくアプリケーションの実行中に動的に生成され、更新されるメッシュのことです。
例としてパーティクルシステムのオーソドックスな実装にはこれを用います(複数の粒子がまとめて一つのメッシュとして扱われている)。
所謂Skeletalアニメーションで動作するものについてはメッシュデータそのものが更新されているわけではないので、これに該当しません。

  • メリット
    • 実行中にデータを更新するためのコストが低い
      • 滑らかに変形させるのに適する
    • 通常のアニメーションでは困難なタイプの変形を実現できる
    • 「プログラマーとしてのグラフィックス表現」手段
  • デメリット
    • プログラミングの素養が必要
    • ロジックで表現しづらい形状は作りづらい
    • 出力を見るためにコンパイルが必要(パラメータの変更のみであれば不要にできる)
      • 固定の形状を作るのには向かない

UE4での実装(ver 4.7.2で確認)

UE4には既に、ダイナミックメッシュを実現するためのクラスが用意されています。
FDynamicMeshBuilderというのがそれです。
欲しい機能そのまんまの名前のついたクラスですが、
使うにあたっていくつかのお約束があるので解説しておきます。

FDynamicMeshBuilderでメッシュを出力するためのインターフェイス

FDynamicMeshBuilderはAssetのようなメッシュデータをそのまま吐き出すというインターフェイスを持っているわけではなく、レンダリング時に用いられるFMeshElementCollectorにデータを渡す機能を提供しています。
宣言はこんな感じです。最後の引数が件のものです:

ENGINE_API void GetMesh(const FMatrix& LocalToWorld, const FMaterialRenderProxy* MaterialRenderProxy, uint8 DepthPriorityGroup, bool bDisableBackfaceCulling, bool bReceivesDecals, int32 ViewIndex, FMeshElementCollector& Collector);

FMeshElementCollectorの参照

FMeshElementCollectorが使用されるのはFPrimitiveSceneProxyを用いてレンダリングが行われる時です。
つまりFPrimitiveSceneProxyを継承したクラスを作成し、以下の関数をオーバーライドします。

virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override

この関数内でFDynamicMeshBuilder::GetMeshを呼び出すことで、描画したい内容を渡すことができるということになります。

自分で定義したSceneProxyを使用する

UPrimitiveComponent::CreateSceneProxy()をオーバーライドし、定義したSceneProxyをnewして返すようにします。
これでFDynamicMeshBuilderを用いる部分までの経路を作るのに必要なものが出そろいました。
あとは実際に表示を行うために必要なオーバーライドをいくつか定義すればOKです。

メッシュの更新されるタイミングについて

CreateSceneProxyで返したオブジェクトを通して表示内容をレンダラーへ送るわけですが、これが呼び出されるタイミングについては注意が必要です。
無駄をなくすため、毎フレームではなく表示すべき内容に変更があった場合にのみ呼び出されます。
UActorComponent::MarkRenderStateDirty()
を用いて更新の通知を行えます。
自分で定義したパラメータによってメッシュを変形させたい場合など、
これを忘れるとビューポートに変更が反映されず戸惑うことになります。

手順まとめ

作る必要があるクラス(末尾にスケルトンコードを掲載してあります)

1 UPrimitiveComponentを継承したクラス(表示に関するいくつかの関数をオーバーライド)
2 FPrimitiveSceneProxyを継承したクラス(GetDynamicMeshElementsをオーバーライド)

BluePrintでの作業

ActorベースでBluePrintを作成
1.で作成したComponentをアタッチ
マテリアルを設定

できあがり(スケルトンコードをそのまま使った場合)
ue4_prim.PNG

スケルトンコード

MyPrimitiveComponent.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Components/PrimitiveComponent.h"
#include "MyPrimitiveComponent.generated.h"

/**
 * 
 */
UCLASS(ClassGroup = Rendering, collapsecategories, hidecategories = (Object, Activation, "Components|Activation", Physics, LOD, Mesh, PhysicsVolume), editinlinenew, meta = (BlueprintSpawnableComponent))
class MYPROJECT_API UMyPrimitiveComponent : public UPrimitiveComponent
{
    GENERATED_BODY()

public:

    // Begin UPrimitiveComponent interface.
    virtual FPrimitiveSceneProxy* CreateSceneProxy() override;

    // End UPrimitiveComponent interface.

    // Begin UMeshComponent interface.
    virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override;
    virtual void GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials) const override;
    virtual int32 GetNumMaterials() const override;
    // End UMeshComponent interface.

    // Begin USceneComponent interface.
    virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
    // Begin USceneComponent interface.

public:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Material)
        UMaterialInterface* Material;
};

MyPrimitiveComponent.cpp
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyProject.h"
#include "MyPrimitiveComponent.h"

#include "DynamicMeshBuilder.h"

class FMySceneProxy : public FPrimitiveSceneProxy
{
public:
    FMySceneProxy(const UMyPrimitiveComponent* InComponent)
        : FPrimitiveSceneProxy(InComponent)
        , Material(InComponent->Material)
    {
    }

    virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
    {
        if (!Material)
        {
            return;
        }
        FDynamicMeshBuilder MeshBuilder;

        // build mesh
        CreateMesh(MeshBuilder);

        FMaterialRenderProxy *matProxy = Material->GetRenderProxy(false);

        MeshBuilder.GetMesh(GetLocalToWorld(), matProxy, 0, false, false, 0, Collector);
    }

    virtual void CreateMesh(FDynamicMeshBuilder &meshBuilder)const
    {
    // ここにメッシュ生成コードを記述(またはオーバーライド)
        meshBuilder.AddVertex(FVector(0, 0, 0), FVector2D(0, 0), FVector(1, 0, 0), FVector(1, 1, 0), FVector(0, 0, 1), FColor::White);
        meshBuilder.AddVertex(FVector(0, 100, 0), FVector2D(1, 0), FVector(1, 0, 0), FVector(1, 1, 0), FVector(0, 0, 1), FColor::White);
        meshBuilder.AddVertex(FVector(100, 0, 0), FVector2D(0, 1), FVector(1, 0, 0), FVector(1, 1, 0), FVector(0, 0, 1), FColor::White);

        meshBuilder.AddTriangle(0, 1, 2);
    }

    virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View)
    {
        bool bVisible = true;
        FPrimitiveViewRelevance Result;
        Result.bDrawRelevance = IsShown(View);
        Result.bDynamicRelevance = true;
        Result.bShadowRelevance = IsShadowCast(View);

        return Result;
    }
    virtual uint32 GetMemoryFootprint() const { return sizeof(*this) + GetAllocatedSize(); }
    uint32 GetAllocatedSize() const { return FPrimitiveSceneProxy::GetAllocatedSize(); }

protected:
    UMaterialInterface *Material;
};

FPrimitiveSceneProxy* UMyPrimitiveComponent::CreateSceneProxy()
{
    return new FMySceneProxy(this);
}

UMaterialInterface* UMyPrimitiveComponent::GetMaterial(int32 ElementIndex) const
{
    if (ElementIndex == 0)
    {
        return Material;
    }
    else
    {
        return nullptr;
    }
}

void UMyPrimitiveComponent::GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials) const
{
    if (Material)
    {
        OutMaterials.Add(Material);
    }
}

int32 UMyPrimitiveComponent::GetNumMaterials()const
{
    if (Material)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}


FBoxSphereBounds UMyPrimitiveComponent::CalcBounds(const FTransform& LocalToWorld) const
{
    return FBoxSphereBounds(LocalToWorld.GetLocation(), FVector(10, 10, 10), 10);
}