ばーばらいず

綺麗に書くことをあきらめた自分用メモ

Houdini NiagaraでHoudiniのモジュールを追加しようとしても出てこない場合

UE5.4

 

www.youtube.com

これを真似しようとしたけど、パーティクルの更新で一覧の中を検索してもSample Houdini Point Cacheが出てこない

 

解決法:

UE_5.4\Engine\Plugins\FX にHoudiniNiagaraを入れる

 

https://github.com/sideeffects/HoudiniNiagara/releases

上記の記載を見るに、プロジェクト以下のPluginsに入れればいいのかと思ってたけどそれだとうまくいかなかった。

 

エンジン側に入れたら動きました♨

 

Blenderでは正しく動作しているシェイプキーがUEだとうまく動かない問題

 

Blenderで見るとシェイプキーもアニメーションも全部正しく動くのに

 

 

UEに持ってくるとシェイプキーを動かしたタイミングで顔の位置が
Tポーズ時の位置に戻ってしまう。。。



 

 

解決方法:

 UEの座標系に併せて前方と上を設定する

 

これでアニメーション中にシェイプキーを変更しても顔自体が動くことはなくなった。

 

 

Unity→VRMBlender でモデルを持ってくれば行ける・・・はず。

 

GameFeatureでDLC作ろうや 後編

つづき

 

 

とりあえずDLCのパッケージができたっぽいわけなんだけど、最初に書いた通り
コマンドからパッケージを作りたい!って話があると思うのでそれを見ていくよ

 

コマンドからパッケージを作れると、Jenkinsとかで定期的にパッケージを
作ったりできるのが旨味だね

 

その後、パッケージの中身を確認して正しくDLCコンテンツが含まれているかを
確認するよ

ここまでやってベースリリースにDLCLevelも入ってました~じゃ笑い種だからね
(だとしたらDLCを外したときDLCLevelに遷移できないのがおかしいので
大丈夫だとは思うけど)

 

 

 

まずはコマンドラインパッケージ作成について

1つ前の記事でエディタ上からプロジェクトランチャーを使ってパッケージ作成を
行ったわけなんだけど、結局プロジェクトランチャーも内部的には
コマンドを叩いているだけなんだよね

それを確認するよ

 

プロジェクトランチャーでパッケージを作成した時、そのログを保存する

 

中になんだかんだ書いてあるけど、今回必要なのはハイライト下部分

(赤く塗った所にはPCのユーザ名が入ってるよ)

ここがパッケージ作成を開始するコマンドになっているので、これを
PowerShellで打ってみるよ

 

パッケージ作成をソフトはエンジンの
UE_5.5\Engine\Build\BatchFiles\RunUAT.bat
なので、それを起動してパラメータにハイライトした内容を与えてみる

 

通らない・・・

 

バージョン名は""で囲もうね

あとstagingdirectory以降のセッション情報とかは消してみた

 

はい~

 

最終的に使ったコマンド

E:\UE_5.5\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=E:\..\..\..\DLCTest\DLCTest.uproject -noP4 -clientconfig=Development -serverconfig=Development -nocompile -nocompileeditor -installed -unrealexe=E:\UE_5.5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe -utf8output -platform=Win64 -build -cook -map=BaseLevel+ThirdPersonMap -CookCultures=ja -pak -compressed -createreleaseversion="1.0" -iterativecooking -SkipCookingEditorContent -stage -package -stagingdirectory=E:/DLCTest/DLC/

 

各種パスは皆の環境によって変えてね

 

 

同じようにDLCもコマンドから作る

こっちも作ったプロファイルを起動した時に保存できるログからコマンドを作る

 

はい~

 

最終的に使ったコマンド

E:\UE_5.5\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=E:\..\..\..\DLCTest\DLCTest.uproject -noP4 -clientconfig=Development -serverconfig=Development -nocompile -nocompileeditor -installed -unrealexe=E:\UE_5.5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe -utf8output -platform=Win64 -build -cook -CookCultures=ja -pak -compressed -dlcname=DLCTest2 -DLCIncludeEngineContent -basedonreleaseversion="1.0" -SkipCookingEditorContent -stage -package -stagingdirectory=E:/DLCTest/DLC/DLC/

 

作ったパッケージを投げ込んで動作チェック 

 

exe実行結果

DLCなし

問題なくDLCに遷移できない

 

DLCあり

DLCレベルに遷移できました

 

あとは皆さんの環境に応じてJenkinsにこのコマンドを食わせれば大勝利ですわ

 

 

 

 

最後にパッケージの中身確認

パッケージの解凍は、エンジンに入っているUnrealPak.exeを使用するよ

https://note.com/game_mediation/n/n72cf4ed94170

こちらを参考に、UnrealPak.exeのファイルパス、解凍するpakファイルパス、-Extract 解凍したものを格納するパス

を入れると解凍できる

pak、ucas、utocファイルを全て解凍し、Contentの中にDLCLevelが入っていなければ
問題ない・・・はず!

 

ベースリリースのContent以下

DLC関係のファイルは入っていなさそう

 

DLC側の3ファイルを解凍した結果

DLC系のファイルだけが入っている

ちなみに1つずつ解凍していくとわかるけど

pakファイルにはエンジンデータとか.uprojectとか.upluginとか、パッケージの
基礎になるものが入っていて

ucasファイルとutocファイルには実際にUEで作ったコンテンツが入っている
っぽい?(ucasとutocどちらを解凍しても大体同じようなデータが出てきた・・)

 

 

ちなみにソースコードは、実行ファイルに組み込まれている・・でいいんだよね?

多分

 

 

 

 

 

 

こういうゲームの内容以外の部分で悩むのは普通にアホらしいので、
なるべくメモ書きして同じ時間を過ごさないようにしよう

 

おわり

 

 

GameFeatureでDLC作ろうや 中編

つづき

 


ベースのレベルからモジュールに含まれるレベルに遷移するところまで作ったので
ついにパッケージを作っていくわよ

 


最終的にはコマンドラインから叩けたらいいんだけど、一旦はプロジェクトランチャーからパッケージを作成していくわ

 


その前に前準備

あとでパッケージの中身を確認する際に見れるようにするため、
プロジェクトの暗号化がされていたら、全て切っておく

多分初期状態ではなにもチェックが入ってないんじゃないかな

 

続いて、作成したモジュールのupluginをいじっていく

今回こんな感じ

{
	"FileVersion": 3,
	"Version": 1,
	"VersionName": "1.0",
	"FriendlyName": "DLCTest2",
	"Description": "",
	"Category": "Game Features",
	"CreatedBy": "",
	"CreatedByURL": "",
	"DocsURL": "",
	"MarketplaceURL": "",
	"SupportURL": "",
	"EnabledByDefault": false,
	"CanContainContent": true,
	"IsBetaVersion": false,
	"IsExperimentalVersion": false,
	"Installed": false,
	"Modules": [
		{
			"Name": "DLCTest2Runtime",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		}
	],
	"ExplicitlyLoaded": true,
	"BuiltInInitialFeatureState": "Registered"
}

重要なのは、

"EnabledByDefault": false" (初期状態で読み込むかどうか)

"BuiltInInitialFeatureState": "Registered" (モジュールの初期状態)

"ExplicitlyLoaded": true, (明示的にロードを行うかどうか?)

モジュールの

"Type": "Runtime", (モジュールが動作するタイミング)
"LoadingPhase": "Default" (モジュールがロードされるタイミング)
このあたりが上と同じになっていれば多分OK
 
 
最後にuproject
プラグイン一覧に入っているモジュールのEnableはTrueにしておこう
 
 
 
ここでついにパッケージ作成
上に記載した通り、まずはプロジェクトランチャーから起動していく・・・前に
DefaultGame.iniをベースリリース用に変更しておく
最初に作るベースリリースにDLCのContent以下は含めたくないため、
DefaultGame.iniの
[/Script/Engine.AssetManagerSettings]

の中に

+DirectoriesToNeverCook=(Path="../Plugins/GameFeatures/DLCTest2/Content")

という一文を入れる

これは、そのフォルダ以下にあるアセットはパッケージ化時にクック
(動作プラットフォームで動くようにアセット類を変換すること)されないように
指定する処理になる

 

設定後エディタからプロジェクトランチャーを起動

 

カスタム起動プロファイルを作成していく

 

プロパティ名を、ベースリリースであることがわかるように設定

ビルドしますか? → 自動的に検出

ビルドコンフィギュレーション → 一旦Development

クックの方法 → バイザブック(デプロイ前に全てクックする設定)

クックしたプラットフォーム → 今回はWindows

 

 

 

クックしたマップ → ベースリリース側に入っているレベルを指定

ディストリビューションようにゲームのリリースバージョンを作成 にチェック

作成する新しいリリースの名前 → 今回は1.0

その他チェックを入れる

 

ビルドのパッケージ方法 → ローカルに格納

パッケージの保存先設定

デプロイ → 行わない



ここまで設定したら戻るを押して、作成したプロファイルの右にあるボタンから
パッケージ作成を開始する

押せない場合、プロファイルのどこかの設定が間違っていると思うよ

 

 

終わった

 

 

指定したフォルダ内にexeができているので実行する

www.youtube.com

キーを入力しても、レベル遷移が行われなければOK

(本来は球があるレベルに遷移するはず)

モジュールがパッケージに含まれていないため次のレベルが見つからない

 

ちなみにログを見ると

DLCLevel見つからんからデフォルトのレベルいくわw って書いてあるね

これでOK

(ログはexeファイルがあるディレクトリ/プロジェクト名/Saved/Logsに入ってるよ)

 

 

 

ここまででベースのパッケージ作成はOK

続いてDLCパッケージを作るよ

 

まずはDefaultGame.iniを開いて、さっきDirectoriesToNeverCookを書いた部分を

+DirectoriesToAlwaysCook=(Path="../Plugins/GameFeatures/DLCTest2/Content")

に変更する

DLCがパッケージに入るはず

 

続いて作成したモジュールのupluginの

"EnabledByDefault": false"

をtrueに変えておく。

upluginが後で置き換わるので、DLC側だけtrueにしておく。

 

 

終わったらベースと同じようにプロジェクトランチャーからパッケージを作る

もう一つプロファイルを作成し、

 

プロパティ名を、DLCであることがわかるように設定

ビルドしますか? → 自動的に検出

ビルドコンフィギュレーション → 一旦Development

クックの方法 → バイザブック

クックしたプラットフォーム → 今回はWindows

クックしたマップ → 何もチェックを入れない(モジュールの中身は勝手に入る)

 

 

ディストリビューションように~ のチェックを外す

作成する新しいリリースの名前 → 空欄

これが基づくリリースバージョン → ベースリリースのリリース名と同じに

DLCをビルド にチェック

ビルドするDLCの名前 に作成したモジュールの名前

エンジンコンテンツを含む にチェック

イテラティブクッキング のチェックを外す

後はベースリリースと同じにして再度パッケージ作成

 



できた

 

 

 

もちろんDLCだけを直接起動することはできないので、ベースリリースの

exeファイルがあるところ/モジュール名/Content/Paks に

作成したPakファイル、ucasファイル、utocファイルを投げ込む

 

 

いざ起動!

www.youtube.com

 

やったぜ。

 

あとはこのpakファイル等をSteamにでも登録すれば、君も立派なDLC使いだ!

しらんけど

 

 

 

本編はここで終わり

あとは細かい検証とかCMDからの起動とかをするよ

 

つづく

 

GameFeatureでDLC作ろうや 前編

御託はいいからGameFeatureでDLCつくろうや

 

 

こんな要件で行きたい

・GameFeatureでモジュールを作成し、それをDLCとしてPakファイルにしたい

・GameFeatureのモジュールにはContentとC++ソースを含める

・(多分)DLCにはソースファイルは含められないので、ソースはベースに入ってても良しとする

・GameFeatureでモジュール起動時、そのモジュールが持つレベルに遷移する

 

UEのバージョンは5.5でいく。

 

 

とりあえず、GameFeatureとモジュール、遷移処理を作る。

 

GameFeatureとModular Gameplayをオンに

 

再起動なりなんなり後、モジュールを作成する

今回はC++も含めるのでC++版で

 

モジュールのデータアセットを作る

(モジュール作成時に作りますか?的なログが出るから、そっちからでも大丈夫)

 

もちろんGameFeatureDatra

 

ファイル名はモジュール名と同じにしておこう

 

 

次に、遷移元レベルと遷移先レベルを作っていく

ファイル→新規レベル→基本 でレベルを作り、プロジェクトのContent直下に保存する

 

ぱっと見でわかるようにCube置いといた

 


同様に作成したモジュールの方にもレベルを作っておく

 

こっちは球にした



次に、レベル間の遷移処理を作る

まず、GameFeatureで特定のモジュールがActivateになったタイミングで遷移してほしいので、それ用のActionを作る

 

UGameFeatureActionを継承してDLCTest_OpenLevelを作成し、各タイミングで呼ばれる関数を継承して処理を書いていく

 

DLCTest_OpenLevel.h


UCLASS()
class UDLCTest_OpenLevel : public UGameFeatureAction
{
	GENERATED_BODY()
	
	virtual void OnGameFeatureRegistering();

	virtual void OnGameFeatureLoading();

	virtual void OnGameFeatureActivated();
};

  

 

DLCTest_OpenLevel.cpp


#include "DLCTest_OpenLevel.h"
#include "Kismet/GameplayStatics.h"
#include "GameFeaturesSubsystem.h"

void UDLCTest_OpenLevel::OnGameFeatureRegistering() 
{
	Super::OnGameFeatureRegistering();

	FString Str = FString::Printf(TEXT("OnGameFeatureRegistering. DLC Level Transition."));
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, Str);
}

void UDLCTest_OpenLevel::OnGameFeatureLoading() {

	Super::OnGameFeatureLoading();

	FString Str = FString::Printf(TEXT("OnGameFeatureLoading."));
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, Str);
}

void UDLCTest_OpenLevel::OnGameFeatureActivated() {

	Super::OnGameFeatureActivating();

	FString Str = FString::Printf(TEXT("OnGameFeatureActivated."));
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, Str);

	UWorld* world = nullptr;

	for (const FWorldContext& Context : GEngine->GetWorldContexts())
	{
		if (Context.World() &&
			(Context.WorldType == EWorldType::Game || Context.WorldType == EWorldType::PIE))
		{
			world = Context.World();
		}
	}

	if (IsValid(world)) {

		Str = FString::Printf(TEXT("world found."));
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, Str);

		UGameplayStatics::OpenLevel(world, TEXT("DLCLevel"), true);
	}
	else {
		Str = FString::Printf(TEXT("world not found."));
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, Str);
	}
}

  

GameFeatureをActivateにされるとOnGameFeatureActivatedがコールされるので、

その中でOpenLevelで特定のレベルに遷移しようという魂胆

 

クラス作成後モジュールのデータアセットを開き、Actionsのところに作成したOpenLevelクラスを登録する


これにより、DLCTest2がActivateになったタイミングで作成したActionが呼ばれるようになる

 

 

続いてGameFeatureでモジュールをActivateにする処理を書く

今回はアクターを1つ作ってそこに処理を書くことにする

 

Actorを継承してクラスを作成し、GameFeatureSubsystemからモジュールを
アクティベートしていくわよ

 

BaseActor.cpp


UCLASS()
class DLCTEST_API ABaseActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ABaseActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UFUNCTION(BlueprintCallable)
	void DLCActivate();

	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	FString TransitionModuleName;
};
  

BaseActor.cpp


void ABaseActor::DLCActivate() {

	FString Str = FString::Printf(TEXT("Base Actor DLCActivate"));
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, Str);

	FString pluginURL;
	FStringView string = TransitionModuleName;
	UGameFeaturesSubsystem::Get().GetPluginURLByName(string, pluginURL);

	UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(pluginURL, FGameFeaturePluginLoadComplete());
}
  

TransitionModuleNameを後でエディタ上で指定して、そのモジュールを
アクティベートする処理をDLCActivateに書いた

これを入力に応じて呼ぶことでアクティベートさせるよ

良い子は遷移に失敗した時のログとかも書いておこうね



ついでにモジュール側にも1つアクターを作成し、レベル遷移時にBeginPlayで
ログが出るようにする

Actorを継承してクラスを作成(作成時保存先をプラグインにするのを結構忘れがち)


DLCレベルに遷移したことがわかればいいので処理はこれだけ


void ADLCActor::BeginPlay()
{
	Super::BeginPlay();
	
	FString Str = FString::Printf(TEXT("DLC Actor BeginPlay"));
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, Str);
}
  

 

最後にGameFeatureをアクティベートする処理を入力で呼ぶ
作成した各アクターをBPにしてレベルに置く
アクティベートするモジュール名をエディタ上で設定しておくのを忘れずに

 

ベースの方のレベルBPに、入力に応じてアクティベート関数を呼ぶ処理を実装

 

 

 

 

こっちはレベル上に置くだけ



 

これで入力を入れると遷移ができるようになる

やったね

youtu.be



ちなみに、一度遷移するとPIEを止めてもモジュールがアクティベートされたままに
なるよ

データアセットを開くとこうなっているので、Registerにでも戻しておくと
再度PIEで遷移できるようになるよ

 

 

つづく

HLSL魔導書 メモ

読んだことのメモ

 

Chapter1 

・3Dゲームを作るにあたって沢山のポリゴンを持つモデルを表示する必要がある

・ポリゴンの計算をCPUにやらせるとアホほど時間がかかるわ

・ポリゴン処理専門のGPUにそれをやらせよう

 GPUは大量の計算を行うためにコアが大量に入っているぞ

・ポリゴン処理のためには、カメラの位置や光源の位置、テクスチャ情報など

 いくつかの情報が必要になるが、その位置を保持しているのは

 メインメモリやね

GPUはグラボが持つグラフィックスメモリにしかアクセスできず、メインの

 メモリには直接アクセスできないよ

・なんで、DirectMemoryAccessの機能などを生かしてメインメモリの内容を

 コピーしておく必要があるぞ

・なんでそもそもメモリ分かれてるのかというと、CPUとGPUでメモリに対する

 要件が違うからなんだな

 CPUのメモリは様々な機能を持たせる必要があるが、GPUの方はただただ

 速度のみを追求しているからね

・とはいえ実際メモリが分かれているとめんどくさいのはめんどくさい

 なので、ユニファイドメモリアーキテクチャという仮想メモリ技術があるよ

 これを使うと直接値のやりとりを行うことができるようになるけど、

 やっぱり処理は遅くなるからDirectX12ではメモリが分かれている前提で

 処理をするよ

 

・実際に3Dモデルを表示するまでの処理には以下のようなものがあるぞ

ドローコール

 実際に絵を表示する処理になる

 これの前に、どのような絵を表示するかを設定する必要がある

レンダリングパイプライン

 どのような絵を描くかの設定処理のこと

 どのような光の当たり方で、影がどこについて~とかを設定する

 この中にさらに以下4つの処理手順があるよ

・入力アセンブラ

 各ポリゴンの頂点の位置とかバッファの値をレジスタに持っていく処理

 直接いじることはあまりないかも?

・頂点シェーダー

 3Dモデルのポリゴンの頂点座標をスクリーン空間に変換する処理

 ここはプログラミングを行うことでカスタムできるよ

 大体、以下のような流れの計算を行う必要があるよ

 ・モデル座標系をワールド座標系に変換

  モデル座標系:BlenderやMayaでモデルを作成した際の、そのモデルだけの

          座標系

  ワールド座標系:UEなどで実際にモデルを置いた時のモデルの位置座標

 ・ワールド座標系をカメラ座標系に変換

  カメラ座標系:カメラから見たワールド座標系内のモデルの位置

 ・カメラ座標系をスクリーン座標系に変換

  スクリーン座標系:カメラに写っているモデルをディスプレイのどの位置に

           表示するかの座標系

・ラスタライザー

 計算結果を用いてディスプレイ上のどのピクセルを塗りつぶすかを

 確定させる処理のこと

 この処理は基本的にはプログラミングできないよ

 ただ、ピクセルの塗りつぶし方などいくつか設定できることはあるよ

 ワイヤーフレームだけ表示~とか、ポリゴンの表だけ表示~とか

ピクセルシェーダー

 ラスタライザーで決定したピクセルを何色で塗りつぶすかを決める処理

 ここがシェーダープログラミングの花形だよ

 テクスチャとか影の付け方とか光の当たり方などはここの

 プログラミングによって決定されるよ

 

 

Chapter2

・続いて実際のプログラミングの流れを見ていくよ

・ざっくりとしたCPU側の流れは以下のようになるよ

 ・ルートシグネチャの作成

 ・シェーダーのロード

 ・パイプラインステートの作成

 ・三角形の頂点バッファの作成

 ・三角形のインデックスバッファーの作成

・そのあとドローコールをするよ

 

・続いてGPU側を見ていくよ

DirectXでは、HLSLという言語を使ってシェーダーのプログラミングを

 していくよ

・HLSLはCにかなり似ているよ

 

・頂点シェーダーは3Dモデルの全ての頂点に対して実行されるよ

・三角形であれば頂点の数は3だけど、実際のモデルには10000とか普通にあるよ

 前に作ったゲームのメインキャラ1体の頂点数は約87000だったよ

・それだけコールされる箇所だから、丁寧にプログラミングする必要があるよ

・頂点シェーダーのエントリーポイントはVSMain()だよ

・引数で頂点データに関するデータ構造体を持ってくるよ

・返り値で処理結果を返すよ

・Cには見られなかった変数として、float3,float4やfloat3x3などがあるよ

・また、メンバ名の後に付く「: POSITION」というのもHLSL特有だね

 これはセマンティクスと呼ばれていて、データがどういった使われ方を

 するのかを決めているよ

・頂点データが持っているのは座標だけじゃくて、色や法線、UV座標なんかも

 持たせることができるよ

・頂点データは上記いくつかの情報の集合体で、それが頂点の数だけ並んだ

 ものを頂点バッファというよ

・例として、C++側で頂点の構造体として座標、法線、色、UV座標を持たせて、

 シェーダー側では構造体にfloat3 hoge : POSITION とだけ書いておくよ

・じゃあ実際にhogeに対してアクセスした場合、C++で設定したどの値が

 返ってくるか?

・それは座標になるよ

 セマンティクスで設定した:POSITIONによって、座標の値を取ってくる

 処理が行われるよ

・もちろんセマンティクスは座標以外にもあるよ

・シェーダーにおける出力用の構造体は入力用の構造体と同じである

 必要はないよ

・出力時のセマンティクスは、入力時とは違う点に注意する必要があるよ

 :POSITION → :SV_POSITION など

 

・続いてピクセルシェーダーに移るよ

ピクセルシェーダーでは、1つ前にラスタライザーで決定したピクセル

 何色を塗りつぶすかを決定するよ

・画面解像度が640 x 480だった場合、307200回呼ばれることになるよ

・エントリーポイントはPSMain()になるよ

・引数には頂点シェーダーで処理した頂点情報を元に計算されたデータが

 入っているよ

・戻り値はピクセルの色になるよ

・頂点シェーダーで処理した数が3点だったからと言って、ピクセル

 シェーダーで処理する数も3つになるとは限らないよ

・頂点数以上のピクセルに塗りつぶしを行う場合、それらは頂点を用いて

 補間された色になるよ

・三角形の各頂点の色が赤、緑、青の場合、それぞれがグラデーション

 された色が各ピクセルに表示されることになるよ

・具体的な補間処理はラスタライザーが行ってくれるよ

 

 

Chapter3

・続いて座標変換についてみていくよ

・座標変換は、座標と行列の乗算によって行われるよ

・変換の流れは前述のとおり、モデル→ワールド→カメラ→スクリーンだよ

 

・モデル座標系からワールド座標系に変換するための行列を

 ワールド行列というよ

・まずそもそも、モデル座標系は3Dソフトでデザイナーが作ったモデルの

 ための座標系だよ

 なので、デザイナーによって原点の位置が変わってくるよ

・モデル座標系に対し、座標、クォータニオン、拡大率などから作成された

 ワールド行列を乗算することで変換するよ

 

・ワールド座標系からカメラ座標系に変換するための行列を

 カメラ行列というよ

・カメラ座標系はカメラの位置を原点とするよ

 

・カメラ座標系からスクリーン座標系に変換するための行列を

 透視変換行列というよ

 

・行列の計算はGPUでやるけど、その行列を作るのはCPUになるよ

・ただ、その場合GPUのメモリに行列をコピーする必要があるよ

・DirectX12では、メモリの値をコピーするために定数バッファという

 仕組みが用意されているよ

・定数バッファは作成しただけだとGPUで使用できず、使用前に設定を

 行う必要があるよ

・そのあたりをまとめて設定するためにディスクリプタヒープというものがあるよ

ディスクリプタヒープに定数バッファ等を設定し、ドローコールを

 行うという流れになるよ

 

・続いてテクスチャマッピングについてみていくよ

・テクスチャは3Dのモデルに張り付ける画像データのことだよ

・ポリゴンにテクスチャを張り付ける場合はUV座標というものを使うよ

・UV座標はテクスチャ座標とも呼ばれているよ

 左上を(0,0)、右下を(1,1)とする座標系だよ

・UV座標で示された場所にある色のことをテクセル(画素)というよ

・テクスチャもグラフィックスメモリに置く必要があるよ

・定数バッファをコピーするためには、セマンティクスとして

 レジスタのID?を設定する必要があるよ

 

・定数バッファはbレジスタに置いていたけど、テクスチャは

 tバッファに置くよ

 

・今後は.tkmファイルを利用してモデル表示を行っていくよ

・.tkmファイルはデザイナーが作ったモデルデータから頂点バッファ、

 インデックスバッファ、マテリアル情報を抽出したファイルだよ

 

 

Chapter4

・光は物を美しく見せるのに重要だよ

・何も考えずモデルを読み込んで表示させても、モデルの形をした

 なにかが見えるだけだよ

・このモデルの形をしたなにかに対しテクスチャを張り付けることで

 キャラクターになるよ

・テクスチャに光が当たることで目に見えるようになるよ

・続いて光の種類についてだよ

 光にはディレクションライトポイントライトスポットライト

 3種類があるよ

 (ほかにもあるけど一旦ここでは上の3つを取り上げるよ)

 

ディレクションライト光の色向きだけを持つライトだよ

ディレクションライトは位置情報を持たないので現実には存在しないよ

 でもディレクションライトによく似たライトはあるよ

 それは太陽だよ

・太陽は遠くにありかつ強い光を放っているため、人間が多少移動した

 程度じゃ光の強さや影の方向はほぼ変わらないよ

 なので太陽の代わりとして使われるよ

 

ポイントライト位置影響範囲を持っているライトだよ

・距離を考慮する必要があるため、ディレクションライトより処理が重い

 昔は大量に設置することはできなかったけど、TBRなどの技術により

 最近では大量に使用することができるようになったよ

 

スポットライト位置影響範囲放射方向放射角度を持つ

 ライトだよ

・シンプルなものであれば、スポットライトをちょっと改造することで

 実装できるよ

 

・ライトは物体に反射し、それが目に入ることで意味を持つよ

 ここからは、ライトが物体に反射して目に入る量の計算を見ていくよ

・具体的には、Phongの反射モデルというものを見ていくよ

・Phongの反射モデルは、拡散反射光鏡面反射光環境光を合わせたものだよ

・1つ1つみていくけど、今回拡散反射光はLambert拡散反射モデル

 鏡面反射光はPhong鏡面反射モデル、環境光は近似モデルを使っていくよ

 

Lambert拡散反射モデルは、光が強く当たっている表面は明るくなり

 あまり当たっていない表面は暗くなるモデルだよ

・光の強さには法線という情報を使用するよ

 法線とは、表面の面の向きを表すベクトルのことだよ

・Lambert拡散反射モデルでは、法線とライトの方向との内積を取ることで

 光の強さを算出するよ

 

内積とはベクトル同士の計算の一つで、「同じ向き、大きさ1のベクトル

 同士の内積は1になる」、「2つのベクトルの向きが異なるほど

 数値が小さくなっていき、逆向きになると-1になる」といった

 特徴があるよ

・Lambert拡散反射モデルはこの内積の性質を使ってモデル表面の

 反射光の強さを計算するよ

・ちなみに内積時の性質はベクトルの大きさが1であることが前提に

 なっていたりするよ

 ベクトルの大きさを1にすることを正規化というよ

 

・光のベクトルと法線の内積を算出した結果について、そのままだと

 一番強く光が当たっている箇所が-1、逆側が1になってしまうよ

 なので、内積の結果に-1をかけることで光の強さが-1~1で表現できる

 これがLambert拡散反射モデルだよ

 

・実装の中で、C++側構造体には場合によって間にパディングと呼ばれる

 一見無駄な変数を挟む必要があるよ

HLSLの仕様として、float3などの定数データは16の倍数のアドレスに

 配置されるようになっているよ

 なので、HLSL側でのデータを複数並べると間に4バイト分の隙間

 生じてしまうよ

C++側は4の倍数アドレスに値が格納されていくため、そのままC++

 HLSLで値をコピーしていくとズレが発生してしまうよ

 それを防ぐために間にパディングを挟む必要があるよ

 

・Phong鏡面反射モデルは金属のような反射を表現するモデルだよ

・物体の表面に光が当たって反射した光について、どれだけ多くが

 目に入ってくるかを計算する方法だよ

・具体的には、「ライトが表面に入射して反射したベクトルを求める」、

 「ライトが入射した位置から視点に向かって伸びるベクトルを求める」、

 「前2つで求めたベクトルの内積から、反射光の強さを求める」、

 「鏡面反射の強さを絞って具体的な強さを設定する」になるよ

 

・ライトが表面に入射した際の反射ベクトルは以下式で求められるよ

 R = L + 2 x (-N * L) x N (L:入射光ベクトル N:表面の法線ベクトル)

・光が入射した位置から視点までのベクトルは、両者の座標を

 引くことで求められるよ

 欲しいのはベクトルの向きだけなので、正規化しておくよ

・2つのベクトルの向きが近ければ近いほど、反射した光が多く目に

 入ってくるということになるよ

 それはつまり、内積の結果が目に入ってくる光の量ということになるよ

・最後に内積した値の結果を累乗することで絞りを実現するよ

 累乗数が大きいほど、少しだけ目に入ってくる光の量は減っていくよ

 

・最後に環境光について記載するよ

 環境光は様々な物体からの反射光が関係していて、まともに計算すると

 アホほど時間がかかってしまうよ

・なので、「物体は一律で同じ間接光を受けている」という近似を用いて

 環境光を再現するよ

・計算としては、一律で特定の値をライトに加算するだけだよ

 シェーダに定数を書いてもいいし、定数バッファに書き込んで値を

 渡してもいいかもね

 

 

 

Chapter5

・続いてポイントライトについて説明していくよ

・基本的にはポイントライトもディレクションライトと同じ考え方を

 していくけど、追加で「入射してくる光の方向」と「光源との距離による

 光の減衰」についても考える必要があるよ

・入射してくる光の方向は、

 光が入ってくる表面位置の座標 - ポイントライトの座標

 で求められるよ

・光源との距離による光の減衰 については、以下式で求められるよ

 A = 1 - 1 / R * D (R: ポイントライトの影響範囲 D: 2点間の距離)

・影響力がマイナスにはならないことと、影響力を累乗することで

 指数関数的にすることに注意してね

 

・続いてスポットライトについて説明していくよ

・現実世界のスポットライトは空気中の塵を照らすから光の筋が見えるけど

 今回はそこは実装せず物体に照射された光だけを算出するよ

・基本的な実装はポイントライトと変わらないよ

 ポイントライトとの違いは、計算後追加でスポットライトから表面までの

 ベクトルとライトの射出方向の内積から角度を求めて、その角度に応じて

 ライトの影響率を計算する必要があることだよ

 

・続いてリムライトについてだよ

・リムライトは逆光ライトとも呼ばれているよ

 被写体の後ろから被写体を照らしていて、輪郭が光るのが特徴だよ

・輪郭部分を光らせるために表面の法線と内積を利用するよ

 2つの正規化されたベクトルの内積は、なす角に応じて-1~1で変化する

 性質を使うよ

 今回はライトと法線のなす角が90度の箇所(= 物体の輪郭部分)の光を

 一番強くしたいので、以下の計算式でリムライトの強さを求めるよ

 1 - max(0, ライトの方向と法線の内積)

・リムライトはライトの方向と表面の法線の内積で決まると書いたけど、

 追加でそれを見るカメラの位置も重要になるよ

 被写体の輪郭部分の光が一番強くなるから、カメラとライトの方向の

 なす角が180度だと一番光が強くなるよ

・それも踏まえると最終的な式は

 1 - max(0, ライトの方向と法線の内積 x -1)

 となるよ

 

・続いて半球ライトについてだよ

・今までは環境光として一定の方向から同じ間接光を受けていると近似して

 ライトの計算を行ってきたよ

 本当は2次反射なども計算したいんだけど、全部まともに計算すると

 アホみたいな時間がかかるよ

 なので今回は、地面と天球の色を考慮して少しリアルになった

 半球ライトを考えていくよ

・半球ライトを計算するために必要な情報は、地面の色、天球の色、

 地面の法線、光が当たる面の法線だよ

 地面の法線と表面の法線の内積を取ると、空に向いている面が1、

 地面に向いている面が-1となるのでこれを線形補間していくことで

 求められるよ

 

 

 

Chapter6

・今までは物体の模様を表現するためにテクスチャを使ってきたよ

 このような使い方をディフューズマップというよ

・テクスチャには、これ以外にも多くの使われ方をしているので、

 ここではそれを見ていくよ

 

・物体の凹凸を表現するテクスチャとして法線マップというものがあるよ

・ポリゴン上は平らになっている面を凹凸があるように見せることができるよ

・そもそも凹凸があるように見えるのはそこに影ができるからだよ

 ということは、平面であっても影ができていれば凹凸に見える

 ということだよ

・ライトのところで見てきた通り、影はライトの方向と表面の法線から

 決まる

 なので表面の法線の方向を変えることで影の付き方が変わってくるよ

・つまり、法線の方向を予めテクスチャに書き込んでおいて、それを使って

 影を計算する というのが法線マップの使い方になるよ

 

・法線マップにも2つの種類があるよ

・1つはオブジェクトスペース法線マップというよ

 これは単純で、オブジェクトの法線の向きをそのままテクスチャの

 RGBに書き込んでいる

 テクスチャに書き込まれている値は0~1の範囲なので、これを-1~1に

 変換した上で使用するよ

 

・もう一つはタンジェントスペース法線マップというよ

・こちらは、法線マップを張り付けるポリゴンの法線座標系から見た法線

 書き込まれているテクスチャのことだよ

法線座標系とは、ポリゴンの法線ベクトルポリゴンの法線と直行している

 接ベクトル法線と説ベクトルの外積で求めた従法線ベクトルからなる

 空間のことだよ

 この空間をタンジェントスペースというよ

 三角形がポリゴンでNが法線Uが接ベクトルVが従法線ベクトルだよ

 (たぶん)

・もちろんタンジェントスペースのままだと使用できないので、ワールド

 空間へ変換する必要があるよ

 この変換を行うには、空間の基底軸を知る必要があるよ

・ある空間にあるベクトルは

 V = Vx * ex + Vy * ey (Vx,Vy: ベクトルのX,Y要素、ex,ey:基底軸のX,Y要素)

 で表せるよ

 VxとVyはわかっているので、変換先の基底軸がわかれば変換を行う

 ことが出来そうね

タンジェントスペースでの基底軸はexが接ベクトル、eyが従法線ベクトル、

 ezが表面の法線になるよ

・変換した結果を使用して影を付けるのがタンジェントスペース法線マップ

 の使用方法になるよ

 

・現状、使い方として主流なのはタンジェントスペース法線マップになるよ

 なぜこんな小難しいことをしているのかというと、オブジェクトスペース

 法線マップには欠陥があるからだよ

・ゲーム中に頂点の位置を変更することになった場合、テクスチャに設定した

 法線をそのまま使い続けていると本来取ってほしい法線と噛み合わなく

 なってしまい、見た目がおかしくなってしまうことがあるよ

タンジェントスペース法線マップであれば、毎回頂点情報を使って

 法線の方向を計算しているので問題が解決できるよ

 

 

・続いてスペキュラマップについてだよ

・スペキュラマップには、金属のような鏡面反射情報が書き込まれている

・今までにも鏡面反射についてはみてきたけど、そのまま使用すると

 その物体全てに鏡面反射が付けられてしまうよ

 木でできているところと金属でできているところがある場合、金属で

 できている部分だけ鏡面反射させたいよね

・なので、反射させたいテクスチャ部分にだけ値を書き込んでおくのが

 スペキュラマップだよ

 

・最後にアンビエントオクルージョンマップ(AOマップ)だよ

アンビエントオクルージョンマップには環境光の値を書き込むよ

・今まで、環境光は一律で同じ光を受けていると近似して計算を行ってきたよ

 でも実際には環境光が当たりやすいところと当たりにくいところがあるよ

 (人間でいえば首の下とか)

・とはいえこれをいちいち計算しているとあほくさい時間がかかるよ

 なので、予めテクスチャに環境光がどれだけあたるかを書き込んでおいて

 それを使う手法がとられているよ

・それが書き込まれているのがアンビエントオクルージョンマップになるよ

 

 

 

Chapter7

・ここではハイエンドゲームのデファクトスタンダードになっている

 PBR(物理ベースレンダリング)についてみていくよ

・PBRを一言でいうならば「物理法則に従った光の計算をしましょ

 ということになるよ

・物理法則に従った(物理的に正しい)計算をするということは、以下2つの

 法則を満たすことを指しているよ

 「エネルギー保存の法則」、「ヘルムホルツの相反性

エネルギー保存の法則は、入射時の光より強い光を反射時に返すことはない

 ということを指しているよ

ヘルムホルツの相反性は、光が入ってくる方向と出ていく方向が

 入れ替わっても光の量は変わらないという性質のことを指しているよ

・前に見たLambert拡散反射はヘルムホルツの相反性は満たしているけど

 エネルギー保存の法則は満たしていない

 実際にエネルギーの総量をみると、入射時の光のエネルギー量のPi倍が

 反射時の光のエネルギー量になっているらしいよ

・なので、Lambert拡散反射の結果をPiで割った正規化Lambert拡散反射

 PBRでは採用されているよ

・いうてもPiで割っただけだからただ通常のLambert拡散反射と比べて

 ただ暗くなっただけなんだけど、大事なのは反射光が入射光の強さを

 超えていないというところだから

 

・じゃあなぜ反射光が入射光の強さを超えていないのが大事なん?という

 ところをディズニー社が作ったPBRを例に見ていくよ

・ディズニーはラプンツェルを作ったとおきに物理ベースのシェーダーを

 開発したよ

・これによって、今までは物体毎にシェーダーを作っていたのが1つの

 シェーダーでパラメータをいじるだけで様々な表現を行うことが

 できるようになった

・今までは、物体毎にシェーダーを作っていたためパラメータによっては

 同じ強さの光を当てているのに違う挙動をしてしまう~なんてことが

 あったよ

・各シェーダーごとのパラメータで気合で挙動の制御をするのは面倒なので、

 これ以降全ての物体に同じシェーダーを適用できるようにしていったよ

・このディズニー社のPBRシェーダーがこれ以降のPBRのベースに

 なっているよ

・1つのシェーダーで様々な表現を行うには多くのパラメータが

 必要になるよ

 でもパラメータを増やし過ぎると調整がめんどくさくなるよ

 

 

 

 

 

遊びと人間 メモ

読んだことを忘れないようにメモする

 

序章

・遊びって一言でいってもいろんな意味あるよね

 遊戯的な意味もそうだし、歯車の遊びとかもあるね

・遊びを表面的に見ればそれは意味のないことだよ

 遊びの結果金が溜まるわけでもなく、世界を前に進めさせるわけでもないしね

・だからといってただ無意味に時間を浪費するだけのものというわけではなく

 様々なことを学ばせてくれるよ

・世界で起こっている様々なものやことにはルールがあるよ

 国内の法律もそうだし、戦争時の国際法もそうだし、絵を描くときの遠近法も

 そうだし、音楽の和音なんかもそうだね

・そのルールを守る必要があるということを、遊びから学ぶことができるよ

 遊びとはルールを守ることで成り立っているからね

・ルールを破ることもあるけど、それがまた新しい創造につながることもあるよ

・また、ルールの中で相手に勝つために最大限努力することも学べるよ

 どうしようもない運命的なものもあるけど、遊びを通じてどういった心持ち、

 どういった姿勢で生きていけばよいかを知ることができるよ

・とはいえ、遊びにも弱点はあるよ

・大前提として、遊びそのものが人生で役に立つことは少ないよ

 ボールを足で蹴る行為そのものでお金を稼ぐことは基本出来ないからね

・また、遊びを通じて努力することを学べるとは言ったけど、

 人生はそんなに甘くないよ

 遊びの中で超える壁というのは、楽しめるために恣意的に作られた

 丁度良い高さの壁だからね

 人生が丁度よい壁だけだとは思わないことだね

・上記が遊びの欠点だけど、それを失くしたら遊びの良さもなくなっちゃうよね

 

 

第一部

ホイジンガ氏の主張はちょっとイマイチやね

・でも、「遊びは物質的な利害を一切欠いた行為である」ってのはわかる

 カジノとかはお金増えたりするやん!と思うだろうけど、あれはお金の

 所持者を運ゲーで変えてるだけで全体として増えてはないからノーカン

・要するに遊びは純粋な消費だね

・ちなみにスポーツは遊びだけどスポーツのプロ選手はスポーツを

 遊びだと思ってやってないから

・続いて「遊びは自由で自発的な活動、喜びと楽しみの源泉」ってのもわかる

 遊ぶ人はやなことを忘れるために自発的に遊んでるからね

 いやならどっかいけよな

・実際、大体の遊びは遊ぶエリアが決まっててそこから出ると失格になるよね

・遊びにはエリア外に出たら負け~とかそういう決まったルールがあるからね

・その絶対的なルールに基づいて遊びは存在するらしいわ

 ルールに文句があるなら遊びをやめな

・あと、遊びは結果がわからない活動ともいえるよね

 勝負がわかりきっていたらなんもおもんないよ

・ルールの中で考えて行動することが遊びなのかもね

・でも逆に、ルールがない遊びもあるよね

 ~ごっこ遊びとかね

・これはぱっと見ルールがなさそうに見えるけど、それらになりきるという

 感情がルールになってるよ

・他にもまだ説明できてない遊びがあるけど、一旦遊びの前提を下のように

 してみるよ

・自由意志、特定のエリア、結果がわからない、非生産的、

 ルール厳守、なりきり

・ルール厳守となりきりはどちらかだけだし、ちょっとわかりにくいから

 分類分けでもするか

 

 

第二部

・遊びの数は無数にあってうまいこと分類分けするのは無理そうかも

・一先ず以下4つの区分による分類分けを提案してみるよ

アゴン(競争)、アレア(賭け)、ミミクリ(真似)、イリンクス(眩暈)

・各区分の中をさらにパイディア(気晴らし、即興)、ルドゥス(闘技、試合)に

 区分するよ

・下図のようなイメージ(ちょっと違うかも?)

アゴンは競争だよ

・開始前に得られるチャンスが平等だよ

 具体的には、チェスなどでお互いが同じ数のコマを持っていることもそうだし

 相手との力量差に応じてハンデを付けたりすることもそれに該当するよ

・完全な平等というのは実現できないけど、どちらかが有利な状況があったら

 その次は不利な状況から始まる~などでバランスを取っているよ

アゴンにおいて遊戯者の原動力は、その分野において他の人より自分が

 優れていると認められたいという欲望だよ

 そのために人は訓練するよ

・動物には殺し合いの時にルールなんてないし、アゴンなんて知らないと

 思われがちだが実はそんなことはないよ

 野生の孔雀とか、馬とか、結構ルールありの戦いやってるよ

・子供がにらめっこゲームとかいって忍耐ゲームやってたりするけど

 あれはアゴンとは少し離れているね

 

・アレアは運ゲーだよ

 対戦相手というよりは運命に勝つことが大事になるね

・訓練も専門的能力も一切関係なくなるよ

 逆に自分以外の要素(兆候、外部の出来事)を頼りにすることになるよ

アゴンと違ってアレアは遊戯者は開始時平等じゃないかもしれないよ

 でもそれを許容しているよ

・その代わり、リスクと利益が均等になるようにしているよ

・トランプゲームなど、アレアとアゴンが組み合わさったものもあるね

 盲目的に受け取った手を最大限活用して勝つ必要があるよ

アゴンは開始時のチャンスが平等なので、その結果は不確かになるよ

 ということはその結果は偶然ということになり、アレアの対象になるよ

・アレアは人間的な遊びと言えるよ

 動物は自身の衝動に囚われているため、自分以外の要素に服従するという

 ことは考えにくいね

 

アゴンとアレアは相反してはいるが、同一の掟を持っているよ

 それは、現実ではありえない平等な条件を作り出すという点だよ

アゴンもアレアも、チャンスを平等にすることで現実世界から脱出する

 ことを行っているよ

・同じように、自身を他者とすることでも現実世界から脱出することが

 できるよ

 これをミミクリというよ