2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[UE5] 新機能でよりリアルなペンライトを振る群衆を作ってみた Part 3:MegaLightsで動的ライトを大量配置する

2
Posted at

1 はじめに

Part 1とPart 2はこちら:

前回は、Unreal EngineのAnimToTextureプラグインを使ってVAT(Vertex Animation Texture)の作成方法を記録しました。VATを活用することで、Instanced Actor(Static Mesh)化したキャラクターでも、ペンライトを振って動けるようになりました。これにより、大規模の観客の実装におけるCPUへの負担を軽減できます。

今回は、ペンライト演出に重要なライティング実に注目します。多くのペンライト実装では、ペンライトの色は見えますが、処理速度を保つため、直接・間接照明による周囲への照明効果がありません。しかし、Unreal Engine 5.5で新たに導入されたMegaLightsシステムを活用すると、GPUの計算を抑えながら、数千、数万本規模のペンライトのような、大量かつ大規模な動的ライティングを配置できるようになります。この機能を用いて、よりリアルな観客の群衆とダイナミックな照明演出の実装方法を紹介したいと思います。

環境

Windows 11 25H2
Unreal Engine 5.7.2/5.7.4

プロジェクトのソースコードとアセットはこちら

2 MegaLightsについて

2.1 MegaLightsとは

MegaLightsは、Unreal Engine 5.5で初めて導入された新しいライティング機能です。MegaLightsの最大の特長は、従来のライティングシステムでは、Movable Light(リアルタイムで計算する動的ライト)を追加すればするほどパフォーマンスが著しく低下するのに対し、MegaLightsでは比較的一定のコストで数千個規模のライトの直接照明と影をレンダリングできる点です。

具体的なテクニカル仕様はEpic Gamesのドキュメントをご参照ください。

MegaLightsを利用するには特に高い性能が必要のわけではありませんが、Epic Gamesより、本機能は本世代のゲーム機をターゲットし、レイトレーシング機能を活用しているため、ハードウェアレイトレーシング機能に対応していない古いGPUだとパフォーマンスが低下したり、動作しなかったりする可能性があります。

なぜLumenを使わないのか

Unreal Engine 5には、リアルタイムGIシステムのLumenがあります。これを使って、Emissive Materialだけでもライティング効果が作れるのじゃないか、という疑問があるかもしれません。
今回はLumenをメインとして使用しない理由は、Lumenの仕組みにより、発光メッシュのサイズが小さすぎ、明るすぎると、大量のノイズが発生してしまうためです。ペンライトみたいな小さい光源は、Lumenにとっては最悪のシナリオだとも言えます。

そのため、間接照明のLumenシステムではなく、直接ライティングパスで作動するMegaLightsを活用するのは妥当だと思います。

2.2 MegaLightsを有効化する

UE5.7時点では、MegaLights機能はExperimentalからBeta状態になりました。まだ本番環境での使用は推奨されていませんが、従来よりパフォーマンスと安定性の向上が期待できます。

では早速、プロジェクトのMegaLights機能を有効にします。Project Settingsを開き、Engine→Renderingの、Direct Lighting部分のMegaLightsチェックボックスを有効にします。そして、その下Hardware Ray Tracing部分のSupport Hardware Ray Tracingも有効にします。Editorを再起動すると、MegaLightsが適用されるようになります。Project Settingsから設定するほか、Post Process Volume上でもMegaLightsのオン・オフを切り替えられます。

レベル内のライトを見ると、Light→Advancedの部分で、Allow MegaLightsとMegaLights Shadow Methodの設定があります。デフォルトはすべてのライトがオンで、Shadow MethodはDefault(Ray Tracing)です。Virtual Shadow Map方式(MegaLightsなしの状態でのUE5のデフォルトのシャドウ方式)だとシャドウの質が高いが、コストも上がります。

3 ライティング実装

まずは全体の構成を分析します。個別のライトを使うと、ペンライトと観客の顔の距離が近いため、顔に当たる細かな光と影の変化を表現できます。しかし、観客は既にInstanced Actorとして実装されているため、距離を離れるとLight Componentが表示されなくなります。つまり、遠距離、広範囲用のライティングと、近距離用のディテールライトを2つ用意し、前回のVATとSkeletal Mesh使い分けの仕組みと同じように、カメラとの距離を基に、切り替えるシステムが必要です。

3.1 ペンライトにライトを追加

はじめに、まだ光っていないペンライトを完成します。

ペンライトの光源として、Point Lightを選択します。Light Componentをペン
ライトのSocketにAttachし、ライトのMobilityをMovableにします。

デフォルト状態のPoint Lightには形がありませんが、Unreal EngineではPoint Lightの大きさ(半径)と長さを設定でき、電球や蛍光管などのライティングを表現する機能も提供しています。ペンライトの半径に合わせて、Point Lightの形を調整します。ここでは、Radius 1.5cm, Length 15cmを使用しています。ライトの強度は後で制御するので、ここで一旦10lmに設定します。

また、今回はLumenを主要のライティング方式として使わないですが、見た目のため、ペンライトのマテリアルにも少しだけの発光効果が必要です。Material Parameter Collectionを活用して、PenLight Managerから動的制御できるマテリアルを作ります。Emissive Valueは1くらいにすれば十分です。

3.2 MegaLightsの効果と制限

先に述べたように、最終的には、Point Lightは同時で全体に使用しません。にもかかわらず、このPoint Lightを用いて、MegaLightsのパフォーマンス改善効果と制限点をうまく示すことができるので、ここで紹介したいと思います。

ライトが付いたペンライトを普通のActorとしてレベル内に配置して、MegaLightsオン/オフの効果を確認しましょう。

600個くらいのPoint Lightがあると、当然ですが、MegaLightsは無効の状態ではほぼ動かないほど重いです。

そしてMegaLightsを有効にすると、一気にパフォーマンスが上がりました。手動の最適化が一切不要で、これほど劇的に改善できるのは、やはりMegaLightsはすごいです。

パフォーマンスの問題は解消できますが、まだ気になる点があります。画面内の陰影部分には、激しいノイズがあります。
これはMegaLightsの最大の制限です。MegaLightsはライト数にかかわらず一定のコストでライティング計算ができますが、その分ライティングは複雑になるほど全体の品質が悪くなります。つまり、MegaLightsさえあれば、複雑なライトを使用し放題のわけではありません。


MegaLights ON

MegaLights OFF

MegaLightsはレイトレース方式なので、単一のピクセルからトレースするレイの数量は一定です。1ピクセルあたりのライティングの複雑度により(例えば、影響するライトの数量、ライトごとのコスト)、レンダリングの品質が左右されますので、コスト面の心配がなくても、ライティング構成をシンプルにする必要があります。

3.3 Rect Lightでの近似法

本題に戻りますが、未hydrate状態のInstanced Actorのライティングを表現するため、観客Actorと独立した、広範囲ライトのActorが必要だと分かります。

ペンライト単体はPoint Lightで表現していますが、観客席全体で見ると、ペンライトの分布が比較的均一で、床からの高さもだいたい同じレベルなので、Rect Lightで長方形の区域内すべてのペンライトを近似できるかもしれません。
これにより、アニメーション制御が必要なActor数を控えつつ、ライティングを簡素化できるため、MegaLightsによるノイズの改善も期待でき、一石二鳥です。早速やってみましょう。


まずはRect LightのActorを作成します。上向きと下向きの2つのRect Lightを追加して、複数Point Lightでの全体効果を再現します。

Rect Lightのサイズは、カバーするエリアと同じにし、カバーする座席数 * 座席間距離 (nX, nY) * (SeatVector.X, |SeatVector.YZ|) になります。回転は (atan(SeatVector.Z, SeatVector.Y), 0, 0) にして観客席の斜面に沿います。

光量については、ペンライトからの光は上下方向のみとみなす場合は、Rect Lightの光量は上下いずれも全てペンライト光量合計の半分 (nX * nY * L) / 2 です。実際の場合はペンライトの光線はそんなに集中しないので、Intensity Scaleで調整します。さらに、最適化のため、Attenuation RadiusをRect Lightの外接球の半径に合わせて設定します。

色やアニメーションなどのパラメータは、ペンライト(Audience Actor)と同じ方法で設定すれば基本的に大丈夫ので省略します。


/* PenLightAreaLight.h */
#pragma once

#include "CoreMinimal.h"
#include "PenLightInterface.h"
#include "GameFramework/Actor.h"
#include "PenLightAreaLight.generated.h"

class URectLightComponent;
class UPenLightPCGData;
class APenLightManager;

UCLASS()
class PENLIGHTCROWD_API APenLightAreaLight : public AActor, public IPenLightInterface
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	APenLightAreaLight();
	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	UFUNCTION(BlueprintCallable, CallInEditor, Category="PenLight Area Light")
	void SavePCGData();
	UFUNCTION(BlueprintCallable, CallInEditor, Category="PenLight Area Light")
	void SetupFromPCGData();
	
	/** ~Begin IPenLightInterface interface */
	void SetEnabled_Implementation(const bool bNewIsEnabled) override;
	void SetPenLightIntensity_Implementation(const float NewPenLightIntensity) override;
	void SetPenLightColor_Implementation(const FLinearColor NewPenLightColor) override;
	/** ~End IPenLightInterface interface */

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

private:
	UFUNCTION()
	void OnPenLightManagerInitialized(TWeakObjectPtr<APenLightManager> PenLightManagerInstance);
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	TObjectPtr<USceneComponent> Parent;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	TObjectPtr<URectLightComponent> RectLightDownward;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	TObjectPtr<URectLightComponent> RectLightUpward;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	TObjectPtr<UPenLightPCGData> PenLightPCGData;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	TWeakObjectPtr<APenLightManager> PenLightManager;
	
	// Variables controlled by IPenLightInterface (PenLightManager)
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	bool bIsEnabled;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float PenLightIntensity;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	FLinearColor PenLightColor;
	
	// Rect light properties
	// The "actual" scale inferred from light dimension, which might differ from the scale set in Audience Section.
	// Used for intensity calculation.
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	FVector2D RectLightScale = FVector2D(8.0f, 8.0f);
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float BaseIntensity = 0.0f;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float RectLightOffset = 5.0f;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float UpwardRelativeIntensityInverse = 8.0f;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float DownwardRelativeIntensityInverse = 8.0f;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float RectLightHeightFromGround = 170.0f;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PenLight Area Light", meta=(AllowPrivateAccess="true"))
	float RectLightWaveAmplitude = 30.0f;
	
	// Variables used to receive data from PCG graph 
	// Should NOT be used at runtime, persistent data are saved in PenLightPCGData component
	UPROPERTY()
	FVector TempSeatVector;
	UPROPERTY()
	FVector TempLightVector;
	UPROPERTY()
	FRotator TempSpawnRotation;
	UPROPERTY()
	AActor* TempAudienceSection;
	
};
/* PenLightAreaLight.cpp */
#include "PenLightAreaLight.h"
#include "Components/RectLightComponent.h"
#include "PenLightPCGData.h"
#include "PenLightManager.h"


// Sets default values
APenLightAreaLight::APenLightAreaLight()
{
	// 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;
	PrimaryActorTick.TickInterval = 0.0333f;
	
	Parent = CreateDefaultSubobject<USceneComponent>("Parent");
	RootComponent = Parent;
	RectLightDownward = CreateDefaultSubobject<URectLightComponent>("RectLightDownward");
	RectLightDownward->SetupAttachment(Parent);
	RectLightUpward = CreateDefaultSubobject<URectLightComponent>("RectLightUpward");
	RectLightUpward->SetupAttachment(Parent);
	PenLightPCGData = CreateDefaultSubobject<UPenLightPCGData>("PenLightPCGData");
	
	RectLightDownward->BarnDoorAngle = 90.0f;
	RectLightDownward->BarnDoorLength = 0.0f;
	RectLightDownward->SetIntensityUnits(ELightUnits::Lumens);
	RectLightDownward->SetIntensity(10.0f);
	RectLightUpward->BarnDoorAngle = 90.0f;
	RectLightUpward->BarnDoorLength = 0.0f;
	RectLightUpward->SetIntensityUnits(ELightUnits::Lumens);
	RectLightUpward->SetIntensity(10.0f);
	
	RectLightOffset = 0.0f;
	RectLightWaveAmplitude = 0.0f;
	RectLightHeightFromGround = 250.0f;
	UpwardRelativeIntensityInverse = 8.0f;
	DownwardRelativeIntensityInverse = 8.0f;
	
	RectLightDownward->SetRelativeLocation(FVector(0.0f, 0.0f, RectLightHeightFromGround + RectLightOffset));
	RectLightUpward->SetRelativeLocation(FVector(0.0f, 0.0f, RectLightHeightFromGround - RectLightOffset));
}

// Called when the game starts or when spawned
void APenLightAreaLight::BeginPlay()
{
	Super::BeginPlay();
	
	// Construct audience from saved PCG data
	SetupFromPCGData();
	
	// Get PenLightManager
	TWeakObjectPtr<APenLightManager> PenLightManagerInstance = APenLightManager::GetPenLightManager(this);
	if (PenLightManagerInstance.IsValid() && PenLightManagerInstance->GetIsInitialized())
	{
		// If initialized register this area light
		PenLightManager = PenLightManagerInstance;
		PenLightManager->RegisterAreaLight(this);
	}
	else
	{
		// If not initialized, subscribe to delegate and handle registration in the callback
		APenLightManager::OnPenLightManagerInitialized().AddUObject(this, &APenLightAreaLight::OnPenLightManagerInitialized);
	}

}

void APenLightAreaLight::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	// Unregister area light
	PenLightManager->UnregisterAreaLight(this);
	
	Super::EndPlay(EndPlayReason);
}

void APenLightAreaLight::OnPenLightManagerInitialized(TWeakObjectPtr<APenLightManager> PenLightManagerInstance)
{
	if (PenLightManagerInstance.IsValid() && PenLightManagerInstance->GetIsInitialized())
	{
		PenLightManager = PenLightManagerInstance;
		PenLightManager->RegisterAreaLight(this);
	}
}

// Called every frame
void APenLightAreaLight::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	const int32 CurrentFrame = PenLightManager->GetCurrentMasterAnimFrame();
	float Phase = FMath::Frac(static_cast<float>(CurrentFrame) / static_cast<float>(PenLightManager->GetPenLightAnimFrameRate()))
					* 2.0f * PI;
	float IntensityFactor = FMath::Lerp(0.25f, 1.0f, -0.5f * FMath::Cos(Phase) + 0.5f);
	RectLightDownward->SetIntensity(IntensityFactor * BaseIntensity / DownwardRelativeIntensityInverse);
	RectLightUpward->SetIntensity(IntensityFactor * BaseIntensity / UpwardRelativeIntensityInverse);
}

void APenLightAreaLight::SavePCGData()
{
	PenLightPCGData->SetSeatVector(TempSeatVector);
	PenLightPCGData->SetLightVector(TempLightVector);
	PenLightPCGData->SetSpawnRotation(TempSpawnRotation);
	PenLightPCGData->SetAudienceSection(TempAudienceSection);
	
	SetupFromPCGData();
}

void APenLightAreaLight::SetupFromPCGData()
{
	// Set up area light from saved PCG data
	SetActorRotation(PenLightPCGData->SpawnRotation);
	RectLightDownward->SetSourceWidth(PenLightPCGData->LightVector.X);
	RectLightUpward->SetSourceWidth(PenLightPCGData->LightVector.X);
	
	RectLightDownward->SetSourceHeight(FMath::Sqrt(FMath::Pow(PenLightPCGData->LightVector.Y, 2) + FMath::Pow(PenLightPCGData->LightVector.Z, 2)));
	RectLightUpward->SetSourceHeight(FMath::Sqrt(FMath::Pow(PenLightPCGData->LightVector.Y, 2) + FMath::Pow(PenLightPCGData->LightVector.Z, 2)));
	
	RectLightDownward->SetRelativeRotation(FRotator(-90.0f, 90.0f, 0.0f));
	RectLightUpward->SetRelativeRotation(FRotator(90.0f, 90.0f, 0.0f));
	
	RectLightDownward->AddRelativeRotation(FRotator(-FMath::RadiansToDegrees(FMath::Atan2(PenLightPCGData->LightVector.Z, PenLightPCGData->LightVector.Y)), 0.0f, 0.0f));
	RectLightUpward->AddRelativeRotation(FRotator(-FMath::RadiansToDegrees(FMath::Atan2(PenLightPCGData->LightVector.Z, PenLightPCGData->LightVector.Y)), 0.0f, 0.0f));
	
	RectLightDownward->SetAttenuationRadius(PenLightPCGData->LightVector.Length() / 2.0f + 100.0f);
	RectLightUpward->SetAttenuationRadius(PenLightPCGData->LightVector.Length() / 2.0f + 100.0f);
	
	const FVector Scale = PenLightPCGData->LightVector / PenLightPCGData->SeatVector.ComponentMax(FVector(0.0001f));
	RectLightScale = FVector2D(Scale.X, Scale.Y);
}

void APenLightAreaLight::SetEnabled_Implementation(const bool bNewIsEnabled)
{
	bIsEnabled = bNewIsEnabled;
	RectLightDownward->SetVisibility(bIsEnabled);
	RectLightUpward->SetVisibility(bIsEnabled);
}

void APenLightAreaLight::SetPenLightIntensity_Implementation(const float NewPenLightIntensity)
{
	PenLightIntensity = NewPenLightIntensity;
	BaseIntensity = PenLightIntensity * RectLightScale.X * RectLightScale.Y;
}

void APenLightAreaLight::SetPenLightColor_Implementation(const FLinearColor NewPenLightColor)
{
	PenLightColor = NewPenLightColor;
	RectLightDownward->SetLightColor(PenLightColor);
	RectLightUpward->SetLightColor(PenLightColor);
}

3.4 PCGでRect Lightを配置

そして次の課題は、前で作成した観客を生成するPCG Graphを拡張して、既存のSplineデータを用いてRect Light Actorを正しい位置に配置して、そして3.3で計算したRect Lightの数値をActorに設定することです。

現時点のPCG Graphはこんな感じです。Sub Graph内でSplineを1列ずつ移動し、繰り返して観客を生成します。このSub Graphを少し変更して再利用すれば、Rect Lightを生成できます。


メインPCG Graph

PCG Sub Graph

Audience GenerationのSub Graphをコピーして、新しいSub GraphをRect Light Generationと命名します。観客を生成するノードをSpawn Actorノードに置き換え、Rect Light Actorを生成します。



Rect Light Actorを生成するPCG Sub Graph

Sub GraphにRectLightScaleの入力パラメータ(Vector2)を追加します。これは、Rect Light一つあたり (nX, nY) 個のペンライトをカバーすることを示しています。この変数を利用して、Rect Light GenerationのSpline移動距離を従来のnY倍、Splineサンプリング距離を従来のnX倍にすると、相応数量のRect Lightを生成できます。

3.4.1 Rect Light Scale

しかし、ここには問題があります。Scale値はユーザー指定なので、この数値で観客の行列数が割り切れる保証がありません。例えば、Scaleは (8, 8) で、観客席が10列ある場合は、1列目のRect Lightは1-8列をカバーしますが、2列目のRect Lightには余りの9列、10列しかありません。その際、2列目のRect Lightが観客席の範囲外にはみ出してしまい、光量の数値も不正確になってしまいます。

この問題を解消するため、「指定スケール」と「実際スケール」の数値を分けます。つまり、入力のRectLightScaleはあくまで目安にしていきます。具体的に、例えばY方向が10列でScaleが8のときは、Rect Lightは合計1.25列必要なので、四捨五入して、1列のRect Lightが全体をちょうどカバーできるように再配置し、実際のスケール(ここは10列分)を計算します。


同じように、14列で同じScale = 8の際、四捨五入後は2列あります。このときの実際スケールは7です。

これはY方向(列)の調整で、Sub GraphにあるX方向のサンプリングでも、同じようにスケールを調整します。

そもそもなぜScale調整が必要か

ユーザー入力に対応するため追加の処理が必要なら、スケールを固定するか、自動的に決定する方が楽じゃないかと思うかもしれませんが、ここで調整できる機能を提供する理由は、多様な観客席配列の形に適応するためです。

例えば、観客席の「セクション」が湾曲も長さの変化もないときは、長方形のRect Light一つでうまく覆えます。使用するライト数が最小限で済むので、パフォーマンス上も理想です。

しかし、実際の会場によくある、セクションが台形になる場合、Rect Lightがどうしてもぴったりに合わない状態になります。


Scale = (32, 32)

この誤差を補正するため、Scaleを下げることができます。Rect Lightの行列数を増やすと、より良く各列の長さを合わせられます。


Scale = (8, 8)

Scale = (4, 4)

また、湾曲がある観客席にも、平らなRect Lightと地面の距離が変わってしまうので、Rect Light Scaleを小さくする必要があります。


Scale = (32, 32)

Scale = (32, 32)

Scale = (8, 8)

このような、実際の環境に合わせ、Rect Light Scaleを介してパフォーマンスと見た目のバランスを取ることができます。

3.4.2 PCGからActorにデータを渡す

PCGにある情報でRect Lightの設定を計算しましたが、PCG Graph内ではRect Lightのサイズや強度などを直接変更できません。そのため、計算したデータを生成したRect Light Actorに渡す必要があります。

PCGシステムのSpawn Actorノードは、「Spawned Actor Property Override Descriptions」機能があり、PCGポイントのAttributeでActorのデフォルト変数値を変更することができます。これを使って、回転や、実際スケール、Rect Lightが所属しているAudience Sectionなどの情報をRect Light Actorに渡せます。

ですが、これだけはまだ足りません。今回使用していたPCGは、Runtimeでの生成ではないので、例え生成するときにパラメータを変更したとしても、Play Modeに入るとActorがReinstanceされるため、パラメータ値が保存されません。そのため、渡されたデータを消えない場所に保存する必要があります。

Rect LightのActorに、新しいActorComponent UPenLightPCGData を作成して追加します。このComponentに、渡したいデータと同じタイプのパラメータを定義します。このComponentにデータを保存すれば、ランタイムでも保持できます。

PCG GraphのSpawn Actorに、「Post Spawn Function Names」の設定があります。これは、Actorが生成された後、すぐに指定された関数を実行させる機能です。

Componentに、SavePCGData() SetupFromPCGData() 2つのpublic関数を定義します。Runtimeでなくでも関数を実行できるよう、UFUNCTION() macroにCallInEditor を指定します。

SavePCGData() に、渡したActor上のパラメータ値をComponentにコピーします。PCG Graphの「Post Spawn Function Names」にこの関数を設定します。

SavePCGData()BeginPlay()関数の最後に、SetupFromPCGData()を実行してRect Lightを設定すると、EditorとRuntimeどちらでも正しい数値が反映されます。

3.5 Rect LightとPoint Lightの切り替え

最後のステップは、Part 2.5の観客Actorと同様に、Rect Light ActorをManagerと接続します。実装方法はだいたい変わらないのでここは省略します。なお、PenLight ManagerからAudienceとRect Lightへの指令は共通しているので、ここは UInterface を定義し、両者の関数を統一しました。

ペンライトのPoint Lightは、Part 2.5で実装した、Low LODのVATからHigh LODのSkeletal Meshへの切り替えと同時に有効にします。

最終効果はこんな感じです。


Point Light OFF

一定の距離でPoint Light ON

Player Pawn(カメラ)周囲のPoint Light

4 おわりに

今回実装した、観客・会場生成とペンライト演出のシステムについての紹介は、ここで一旦締めたいと思います。このプロジェクトを通して、Unreal Engineのいくつかの新機能に触れることができました。PCG、AnimToTexture、MegaLightsは、いずれもパワフルな機能と思うので、活用の可能性の一つとしてご参考になれば幸いです。

また、多色のペンライトや、VATメッシュに関するバウンドの問題など、まだまだ追加・改善したい点がありますので、何かの進捗があったら続編を更新したいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?