TechBlogリンクメモ

海外ゲームスタジオやR&D系のTechBlog系リンク.
Twitterだと後で見るのが不便(以下略).
見つけられたものを列挙しているだけなので選出に特に理由は無い.

The Coalition Studio

Embark Studios

Guerrilla Games

Traverse Research

Treyarch

その他

更新情報

ProjectRoot直下のShadersディレクトリをShaderIncludePathに追加するだけのPlugin [UE4]

これはなに

プロジェクトルート/Shaders をUEのShaderIncludePathに自動追加するプラグイン.
UE4.25辺りからマテリアルのCustomノードで Include File Paths を指定して外部シェーダファイルをインクルードして利用できるようになったが,インクルードパスの設定が面倒だったので勉強ついでに自分用自動化プラグインを作成した.

置き場所

github.com

使い方

1. zipを解凍してできたNglShaderIncludePluginを ProjectRoot/Plugins にコピー.
2. ProjectRoot/Shaders ディレクトリにCustomノードで使いたい ush を配置.

// テストで用意した IncludeTest.ush というファイル
#pragma once
float3 Func00()
{
    return float3(1.0, 0.0, 0.0);
}

3. マテリアルのCustomノードの Include File Paths に "/Project/filename.ush" を入力.

f:id:nagakagachi:20210818012316p:plain
Customノード例

4. Customノードでインクルードしたushの関数などが利用できるようになる.

エラーメッセージ

UEプロジェクト起動時に Shaders ディレクトリが存在しないと以下のような警告がログに出力されるのでディレクトリを作成するように.

LogTemp: Warning: [FNglShaderIncludePluginModule] directory [ProjectRoot/Shaders] does not exist.

f:id:nagakagachi:20210818003947p:plain

Physics Simulation 資料メモ

Twitterではあとから探すのが大変なためこちらにまとめる.

Constraints Derivation for Rigid Body Simulation in 3D

  • Daniel Chappuis,

https://danielchappuis.ch/download/ConstraintsDerivationRigidBody3D.pdf
拘束力の定義から丁寧に式変形をしていてわかりやすい.
2剛体の状態ベクトル(  R^{14} )と変位ベクトル(  R^{14} )の回転成分はQuaternionで表現.
2剛体の速度ベクトル(  R^{12} )の回転成分は角速度ベクトルで表現.
変位ベクトルと速度ベクトルの回転成分の表現の差異やそれを考慮した変形についても説明がある.
Ball-And-Socket-JointやHinge-Joint等、各種のジョイント系の拘束条件やそのJacobianの導出の説明がある.
Baumgarte Stabilizationの項の追加有り.

3D Constraint Derivations for Impulse Solvers (July 1, 2015 v1.00)

  • Marijn Tamis,

http://www.mft-spirit.nl/files/MTamis_Constraints.pdf
Point DIstanse, Contact, Hinge, Quaternion Constraint等多数の拘束条件とそのJacobianが記載されている.

Constraint based physics solver (June 15, 2015 (v1.02))

  • Marijn Tamis, Giuseppe Maggiore,

http://mft-spirit.nl/files/MTamis_ConstraintBasedPhysicsSolver.pdf
Contact Constraint や Distance Constraint 等のシンプルな拘束についてはこちらがわかりやすいかもしれない.
Baumgarte Stabilizationの項の追加有り.
反復ソルバの Projected Gauss-Seidel の擬似コード有り.
ただしDistance ConstraintのLinearVelocityに対応する部分が正規化ベクトルになっていない点が少々怪しい気がする.

Position Based Dynamics

  • Matthias Müller, Bruno Heidelberger, Marcus Hennix, John Ratcliff,

https://matthias-research.github.io/pages/publications/posBasedDyn.pdf
Matthias Müller氏のPBDの大元.

XPBD: Position-Based Simulation of Compliant Constrained Dynamics

  • Miles Macklin, Matthias M ̈uller, Nuttapong Chentanez,

http://mmacklin.com/xpbd.pdf
拡張PBD.
反復数によって剛性が変わってしまう問題を解決する.

Detailed Rigid Body Simulation with Extended Position Based Dynamics

  • Matthias Müller, Miles Macklin, Nuttapong Chentanez, Stefan Jeschke, Tae-Yong Kim,

https://matthias-research.github.io/pages/publications/PBDBodies.pdf
PBDの大元の著者の最近のもの.
PBDの反復ソルバで制約条件の更新タイミングを変えることでより効率的且つ安定した結果になるというものらしい.
個人的にはPBDでのAngular Constraints等の説明がある部分が嬉しい.

Game Physics : Utrecht University - Game and Media Technology Master Program

  • Amir Vaxman

http://www.cs.uu.nl/docs/vakken/mgp/2018-2019/
ユトレヒト大学(Utrecht University)の2019年のGame Physics講座のSlide資料.
11のLectureでBasic PhysicsからConstraint Based, Position Based, 有限要素法, Fluidまで幅広くカバーしていてとても気になる.

行優先と列優先のはなし(row-major, column-major)

今ひとつ完全理解に至っていなかったので自分の言葉で残しておく
間違ってたら妖精さんが直してくれる

(WEB上にたくさんある有用な記事を読んでわかる方は当記事をこれ以上読む必要は無い)
Per-Component 算術演算 - Win32 apps | Microsoft Docs

行列の要素と演算

行列の行と列の意味や行列要素の位置はrow-majorでもcolumn-majorでも変わることはない
行とは行列の横方向のラインであるし、列は縦方向のラインである
mat._11は1行目1列目の要素であり、mat._42は4行目2列目の要素である

行列とベクトルの乗算のルールもrow-majorでもcolumn-majorで変わることはない
(ただしHLSL等ではベクトルは演算の位置によって適宜列ベクトルや行ベクトルに読み替えられる)

V1 = M * V0; // Matrix4x4とVec4

V1.x = M._11 * V0.x + M._12 * V0.y + M._13 * V0.y + M._14 * V0.w
V1.y = M._21 * V0.x + M._22 * V0.y + M._23 * V0.y + M._24 * V0.w
V1.z = M._31 * V0.x + M._32 * V0.y + M._33 * V0.y + M._34 * V0.w
V1.w = M._41 * V0.x + M._42 * V0.y + M._43 * V0.y + M._44 * V0.w

であり、

V1 = V0 * M; // Matrix4x4とVec4

V1.x = M._11 * V0.x + M._21 * V0.y + M._31 * V0.y + M._41 * V0.w
V1.y = M._12 * V0.x + M._22 * V0.y + M._32 * V0.y + M._42 * V0.w
V1.z = M._13 * V0.x + M._23 * V0.y + M._33 * V0.y + M._43 * V0.w
V1.w = M._14 * V0.x + M._24 * V0.y + M._34 * V0.y + M._44 * V0.w

である

シェーダ側の行列のメモリレイアウト

定数バッファ等でCPUから送られてきた行列のメモリ領域をどのように解釈するか
row-majorとして解釈するのか、column-majorとして解釈するのかを指定する

Matrix4x4を考えた場合、メモリ上では連続する16個の値として扱われる

このメモリをrow-majorな行列として解釈すると
0番目から4つを _11, _12, _13, _14 に読み取り
4番目から4つを _21, _22, _23, _24 に読み取り
8番目から4つを _31, _32, _33, _34 に読み取り
12番目から4つを _41, _42, _43, _44 に読み取る

row-major の場合は先頭から4つずつの行ベクトルとして解釈される


対してこのメモリをcolumn-majorな行列として解釈すると
0番目から4つを _11, _21, _31, _41 に読み取り
4番目から4つを _12, _22, _32, _42 に読み取り
8番目から4つを _13, _23, _33, _43 に読み取り
12番目から4つを _14, _24, _34, _44 に読み取る

column-major の場合は先頭から4つずつの列ベクトルとして解釈される

問題になる場合

CPU側の行列型データのメモリレイアウトと、シェーダ側定数バッファの行列型のメモリレイアウトが一致していない場合に転置された行列として解釈されてしまう

DirectxMathのMatrixはrow-majorな定義となっており、HLSLのデフォルトはcolumn-majorとなっているらしいので注意が必要

行列を左から掛けるのか右から掛けるのか問題

row-majorとcolumn-majorはあくまでメモリレイアウトの問題であり、ベクトルに対して行列を右から掛けるのか左から掛けるのかというのは別問題

例えば回転行列を考えて、

  • 基底ベクトルを列ベクトルとして配置した行列
    • 左から掛けるべき
  • 基底ベクトルを行ベクトルとして配置した行列
    • 右から掛けるべき

これはそれぞれの思想に則って決めることだと思う
row-major/colum-majorとは関係なくその行列がどのような情報を表現しているかという問題

(個人的には基底ベクトルは列ベクトルとして配置したい派)

その他

row-major/column-majorはメモリレイアウトのはなしと行列の表現のはなしが混ざり合って混沌とするが、自分なりにまとめてスッキリしたとおもう

劇場版スタァライトはいいぞ(ステマ)

InstancedStaticMeshとCustomDataとTextureで大量オブジェクト (UE4 Advent Calendar 2020)

UE4 AdventCalendar2020 15日目の記事となります
Unreal Engine 4 (UE4) Advent Calendar 2020 - Qiita

サンプルプロジェクトは以下(UE4.26)
github.com

やりたいこと

  • 大量のインスタンスメッシュについて個別のパラメータをBPからマテリアルへ渡す
  • 大量のインスタンスメッシュのパラメータ更新をGPGPU(マテリアル)で高速に実行する
  • BPのみでつくる (C++つかわない)

このような要素を含んだデモとして以下のようなサンプルを作成しました。

  • 基準位置とバネで接続された物体のシミュレーション
    • バネによる速度の変化
    • 速度による位置の変化
    • プレイヤーとのインタラクション

youtu.be
youtu.be
以降の説明は基本的にこのデモをベースにしていきます。

仕組みの概要

今回紹介する方法は大まかに以下のような流れになります。

  • InstancedStaticMeshのCustomDataにInstance番号を設定する
  • テクスチャを配列データに見立ててInstanceパラメータを格納する
  • Instanceパラメータテクスチャをマテリアルで更新する
  • マテリアルでInstance番号に対応したパラメータをテクスチャから取得して使う

事前情報

大量のメッシュを描画する場合にInstancedStaticMeshを使うことが多いと思います。
ただし、UE4のマテリアルではSV_InstanceID的な情報は取れないので、そのままではInstance毎にマテリアルで色を変えるといったことはできません。
(格子状にInstanceを配置して頂点座標からInstance番号を計算するという技もありますが...)

CustomData

InstancedStaticMeshComponentのCustomDataを利用すると、Instance毎の個別情報を設定してマテリアルから参照できます。
f:id:nagakagachi:20201202234548p:plain
今回はここにInstanceの番号を設定し、テクスチャに格納したInstanceの速度情報等を取り出す際に利用します。

CanvasObject

CanvasObjectのDrawLine等によってBPからRenderTargetの任意の場所に色を書き込むことができます。
f:id:nagakagachi:20201203185207p:plain
これを利用して、マテリアルパラメータでは難しい大量の情報をテクスチャ経由でマテリアルに渡します。

TextureRenderTarget2Dの準備

バネのシミュレーションをするにあたって、Instanceの情報を格納する4つのTextureRenderTarget2Dを用意しておきます。

  • 基準位置
  • 基準位置からの変位(前フレーム)
  • 基準位置からの変位(今フレーム)
  • 速度

今回は位置と速度の情報なのでFormatには RGBA16f を使います。
f:id:nagakagachi:20201206210820p:plain

これらのテクスチャは下図のように左から右、上から下へ順番に番号付けをした配列データのように扱います。

f:id:nagakagachi:20201206210318p:plain
データ格納テクスチャ例(幅8高さ8)

ここから分かるとおり、テクスチャの幅高さは表示したいInstanceすべてを格納できる十分なものにしておく必要があります。

InstancedStaticMeshComponentの設定

InstancedStaticMeshComponentの Num Custom Data Floats に 1 を設定してInstance毎にfloat1つのCustomDataを使えるようにします。

f:id:nagakagachi:20201121204146p:plain
InstancedStaticMeshのNumCustomDataFloats

Instance番号をCustomDataに設定

InstancedStaticMeshComponentのSetCustomDataValueで各InstanceのCustomDataにInstance番号を設定します。
マテリアル側からはこのCustomDataに入ったInstance番号を利用して、対応するテクスチャ位置から情報を読み取ります。
f:id:nagakagachi:20201203214608p:plain

Instanceの基準位置をテクスチャに書き込み

先に用意した4つのテクスチャの内、基準位置テクスチャについてはBPから書き込みを行います。
まずInstance番号に対応するテクセル座標を以下のように求めます。
f:id:nagakagachi:20201203212604p:plain
 pos.y = int(Index / Width)
 pos.x = Index - pos.y * Width

実際の書き込みではまずBeginDrawCanvastoRenderTargetで開始を宣言し、
f:id:nagakagachi:20201203215135p:plain
Instanceの数だけDrawLineで対応する情報を色として書き込んでいきます。
開始位置は先に説明した計算で求め、終了位置は 0.5,0.5 だけオフセットした位置にします。
(これでうまくいってますがもっと良い方法があるかも)
今回は基準位置(XYZ座標)を書き込みたいのでColorのRGBに座標をそのまま設定します。
f:id:nagakagachi:20201203215016p:plain
全部終わったらEndDrawCanvastoRenderTargetで完了します。
f:id:nagakagachi:20201203215155p:plain

ここまででInstanceの基準位置が左上から右下へ順番に書き込まれたテクスチャが完成します。

f:id:nagakagachi:20201214235009p:plain
100個のInstance位置を書き込んだ16x16基準位置テクスチャ可視化

*1

変位テクスチャによってメッシュを動かすマテリアル

InstancedStaticMeshに設定して頂点を動かすマテリアルです。
TextureParameterとして基準位置からの変位テクスチャを設定しています。
InstancedStaticMeshのCustomDataに設定したInstance番号を元に、変位テクスチャから対応する変位量を取得してWorldPositionOffsetで頂点を動かします。
f:id:nagakagachi:20201206234848p:plain

まず PerInstanceCustomDataでInstancedStaticMeshComponentに設定したInstance毎のCustomDataの値を取得できます。
このCustomDataには先に設定したInstance番号が入っています。
f:id:nagakagachi:20201206220229p:plain

Instance番号とテクスチャサイズから対応するUV座標を計算し、このInstanceに対応する変位情報をテクスチャから取得します。

f:id:nagakagachi:20201206234926p:plain
Instance番号に対応するUVでテクスチャから情報取得

UV座標の計算は以下のようなネットワークになります。Canvasに書き込むときの座標計算の同じです(こちらはテクセル座標ではなくUVであることにだけ注意)。

f:id:nagakagachi:20201206220813p:plain
Instance番号とテクスチャサイズから対応するUV計算

ここまででメッシュのマテリアルと変位テクスチャをつなぎましたが、まだ変位テクスチャを更新していないのでメッシュは動きません。
このあとプレイヤーとのインタラクションを含めたバネのシミュレーションをマテリアルで計算して変位テクスチャを更新することでメッシュが動くようになります。
(まだ変位テクスチャの更新処理を作ってないので動きませんが)

変位テクスチャ更新マテリアル

変位テクスチャを更新するマテリアルを用意します。
このマテリアルはMeshに設定するのではなく、Draw Material To RenderTarget でRenderTargetTextureを更新するために使います。
このマテリアルはInstance番号などを気にする必要はなく、描画UVと同じUVでテクスチャから情報を取り出して計算するだけです。
(同じテクセル位置には同じInstanceの情報が格納されているので)
速度テクスチャと前回の変位テクスチャを元にバネ-ダンパモデルの計算をして新しい変位を求めます。

f:id:nagakagachi:20201206224233p:plain
速度と変位からバネ-ダンパモデルで新しい変位を計算

プレイヤーとのインタラクションは単純な球と点の当たり判定としています。
注意点として、変位テクスチャは基準位置からの変位量の情報なので、ワールド座標であるプレイヤー位置との比較のためには同じワールド座標に変換する必要があります。
そのために基準位置テクスチャの値と変位を足し合わせてからプレイヤー位置との比較をしています。

f:id:nagakagachi:20201206224521p:plain
プレイヤー球とのヒット

また、変位はマイナスを取る可能性があるのでマテリアルの設定でマイナスのEmissiveを許可するようにしておきます。

f:id:nagakagachi:20201123000813p:plain
Emissive出力の負値を許可

サンプルプロジェクトを見ていただくのが早いと思いますが、このマテリアルにはTextureParameterとして「基準位置からの変位(前フレーム)」や「速度」のテクスチャを設定し、Draw Material to RenderTargetのターゲットとして「基準位置からの変位(今フレーム)」を指定することで変位テクスチャを更新します。

速度テクスチャ更新マテリアル

変位テクスチャが更新されたので新たに速度テクスチャを計算し直します。
このマテリアルも変位テクスチャ更新のものと同様に Draw Material to Render Target で利用するため、Instance番号などを気にする必要はありません。
前回の変位と今回の変位の差分から速度を計算して出力する単純なものになります。
 vel_ = \frac {pos_{t} - pos_{t-1}} {\Delta t}

f:id:nagakagachi:20201206225008p:plain
速度テクスチャ更新マテリアル

このマテリアルも変位テクスチャ更新と同様にマイナスのEmissiveを許可するようにしておきます。

このマテリアルにはTextureParameterとして「基準位置からの変位(前フレーム)」と「基準位置からの変位(今フレーム)」のテクスチャを設定し、Draw Material to RenderTargetのターゲットとして「速度」のテクスチャを指定することで速度テクスチャを更新します。


これでようやくマテリアルによる変位と速度の更新, Instance毎の変位の取得と頂点移動表現がつながって動くようになります。
この記事では省略している部分もありますが、実際の処理の流れはサンプルプロジェクトの方を参考にしていただければ幸いです。

まとめ

以上でInstancedStaticMeshのInstance毎にテクスチャにパラメータを格納、更新して個別の振る舞いをさせることができるようになりました。
今回はInstanceの基準位置をCanvasを利用してテクスチャとしてマテリアルに渡していますが、この方法は色々な使い方ができるのではないかと思います。
また、ただのCubeではなく植物のメッシュを大量に表示しつつ個別に揺れたりさせたい場合にも使えると思います。
youtu.be

Twitterでこんなことをしていますのでフォローしていただけるとうれしいです
なが (@nagakagachi) | Twitter


明日は Dv7Pavilion さんの Influence Map に関する記事とのことです!
qiita.com






f:id:nagakagachi:20201215000807p:plain
サムネイル用

*1:基本的にこの処理はマテリアルへ渡したい情報に変更があったときに実行することになります。 今回はInstanceの位置は生成時点から変化しないので初期化時の一回のみです。

論文メモ Fast Eulerian Fluid Simulation In Games Using Poisson Filters (SCA 2020 Showcase)

www.youtube.com

概要

2次元Euler流体を用いた2.5次元インタラクティブ流体エフェクトに関するShowcase。
圧力Poisson方程式のJacobi法ソルバの反復数を大幅に削減し高速化するPoisson (Separable) Filterを提案。

Poisson Filter

圧力Poisson方程式のJacobi反復は、前回の反復での圧力値に対して再帰的に定数フィルタ3x3をかけ合わせているものとみなせる(3次元なら3x3x3)。
N回のJacobi反復を1回の畳み込みフィルタに解析的に変換することで反復数を削減することができる。
事前計算でこの畳込みフィルタを求めておくことでランタイムでの圧力反復計算を大幅に高速化する。

f:id:nagakagachi:20201210213053p:plain
引用1

近傍の初期値にN回のフィルタを掛けた結果とおなじになるようなフィルタを事前計算して利用するということ。
定数重みを掛けて足し合わせるフィルタをN回掛けた場合の、近傍初期値の足し合わせ重みは地道に手計算することでも確認ができそう。

Poisson Separable Filter

畳み込みフィルタ化によって反復数を1回にすることができるが、N回分をまとめたことにより広範囲をサンプリングするフィルタとなり、計算量は非常に多くなってしまう。
canonical polyadic decomposition (CPD) でRank1のMatrixの和に変換し、それぞれを特異値分解(SVD)で Nx1. 1x1, 1xN の行列積に変換する(1x1は特異値(Scalar)になる)。
ここで特異値が最大のものだけを選ぶことで、元の行列の性質をある程度残しながらNx1と1xNの縦横に分離された Poisson Separable Filter が得られる。

f:id:nagakagachi:20201210220549p:plain
引用

このようにして得られた Filter は Total variance explained が83%以下にはならないらしい(これが元の性質をある程度残しているということ?)

このフィルタは中心部から離れた要素の重みが小さいため、それらを切り捨てることでさらにフィルタサイズを小さくすることができる(~80% redundant)。
また、重みが対称的であるため実際に必要とする重みデータは更に圧縮することができる。

反復数が1回になったこととフィルタサイズの削減により、反復数が小さい場合には4.5倍、100反復では30倍程度高速化することができた。

2.5Dシミュレーション

シミュレーションは2D-Gridで実行しているが、カメラの向きに応じて高さや傾きを補正することであたかも3D空間でシミュレーションされているようにみせている。
外力や風は3Dから2Dへ投影することでシミュレーションに取り込まれる。

f:id:nagakagachi:20201210221945p:plain
引用
f:id:nagakagachi:20201210222014p:plain
引用

3Dオブジェクトとのコリジョンは深度バッファベースの手法を利用している。

アーティストワークフロー

マスク画像によって炎や煙の生成部をコントロールする。

f:id:nagakagachi:20201210222216p:plain
引用

What is Next?

Poisson Filterの3次元への拡張
Multi-Runk + Rank Selector ( 負荷と品質のトレードオフができるように )
粘性拡散項への対応

感想

Jacobi法の再帰的構造に注目して1回の畳み込みフィルタに変換するというのは目からウロコだった。
他の分野でも使えそう(使われていそう)なので身につけたい。
フィルタを特異値分解して次元削減+Separableにするというのも応用が効きそう。
実装してみたいがまず解析的に畳み込みフィルタに変換する部分があやふやなので実際にPythonあたりで試してみたい。

Voxel Mesh化手法についてメモ( Voxel Meshing )

SDFなどのVoxelからMeshを生成する手法のメモ。
もとのSDFのセルと、Mesh化で管理されるVoxelは一致しないので注意。
もとのSDFのセル中心を頂点とするようなVoxel上でMesh化が実行される。

Marching Cubes

Polygonising a scalar field (Marching Cubes)

Voxelの頂点のパターンからそのVoxelのポリゴンパターンをLookupしてMesh化する。
シャープな形状が作りにくいらしい。

Dual Contour

https://people.eecs.berkeley.edu/~jrs/meshpapers/SchaeferWarren2.pdf

あるVoxelを構成する辺について、SDFをの符号が異なる両端を持つ辺に注目し、辺を横切るような等値面の交差位置と法線をSDFの勾配等から計算する。
一つのVoxelについて境界面は複数存在する可能性があり、すべての境界面からの距離の自乗和が最小になるような一点をそのVoxel内の頂点として最小二乗法で推定する。
すべてのVoxelについて上記の点を計算し、隣接Cellで点同士をつなげてMeshを構成する。
シャープな形状を作ることができるらしい。
Voxel毎に最小二乗法を解く必要があるためコストが高い可能性。
また自己交差メッシュができてしまう可能性もある。

SurfaceNets

https://www.merl.com/publications/docs/TR99-24.pdf
Smooth Voxel Mapping: a Technical Deep Dive on Real-time Surface Nets and Texturing | by DreamCat Games | Aug, 2020 | Medium
NaiveSurfaceNets/NaiveSurfaceNets.cs at master · TomaszFoster/NaiveSurfaceNets · GitHub


Meshを構成するVoxel内の頂点を推定することはDual Contourと同様だが、複数ある辺と等値面の交差位置を平均した地点を頂点位置とするところが異なる。