超雑訳 Surface Simplification Using Quadric Error Metrics

Share

ども。Pocolです。
今日は,
[Garland 1997] Michael Garland, Paul S.Heckbert, “Surface Simplification Using Quadric Error Metrics”, SIGGRAPH 1997, pp.209-216.
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。

(さらに…)

私的メモ:モーションブラー

Share

A Fast and Stable Feature-Aware Motion Blur Filterの疑似コード。

float cone(float dist, float r) {
	return saturate(1.0f - abs(dist) / r);
}

float cylinder(float dist, float r) {
		return sign(r - abs(dist)) * 0.5f + 0.5f;
}

// linear depth.
float zCompare(float za, float zb) {
		const float SOFT_Z_EXTENT = 0.1f;
		return saturate(1.0 - (za - ab) / SOFT_Z_EXTENT);
}

float3 MotionBlur(float2 p)
{
	// parameter setting (see. 5. Implementation and Results).
	const auto N		= 35;	// sample count.
	const auto eta		= 0.95; // a larger maximum jitter value (in pixel units). (see p.6)
	const auto phi		= 27;	// user-determined constant which affects the "baseline2 jitter level. (see p.6)
	const auto kappa	= 40;	// use-parameter to bais its importance. (see p.6)
	const auto r		= 40;	// a maximum image-space blur radius. (see p.2)
	const auto gamma	= 1.5;	// minimum user threshold (see p.4)

	auto j = Halton(-1, 1);

	// sOffset jitters a tile lookup (but never into a diagonal tile).
	auto vmax = FetchNeighborMax(p/r + sOffset(p, j));
	auto mag_vmax = length(vmax);
	if (mag_vmax <= 0.5f)
	{
		return FetchColor(p);
	}

	auto wn = vmax / mag_vmax;
	auto vc = FetchVelocity(p);
	auto wp = (-wn.y, wn.x); // vmax⊥.

	if (dot(wp, vc) < 0.0)
	{
		wp = -wp;
	}

	auto mag_vc = length(vc);
	auto wc = normalize(lerp(normalize(vc), wp, (mag_vc - 0.5) / gamma); // Eq. (1).

	// First integration samples: p with center weight
	auto totalWeight = N / (kappa * mag_vc);
	auto result = FetchColor(p) * totalWeight;
	auto j_dash = j * eta * phi / N;

	auto z_p = FetchDepth(p);

	for(int i=0; i<N; ++i)
	{
		auto t = lerp(-1.0, 1.0, (i+j_dash + 1)/(N+1)); // jitter sampler

		// Compute point S; split samples between {vmax, vc}
		auto d = (i & 0x1) ? vc : vmax; // iが奇数なら vc, iが偶数なら vmax.
		auto T = t * mag_vmax;
		auto S = int2(t * d) + p;

		// Compute S's velocity and color
		auto vs = FetchVelocity(S);
		auto colorSmaple = FetchColor(S);

		auto z_S = FetchDepth(S);

		// Fore-vs. background classification Y w.r.t p
		auto f = zCompare(z_p, z_S);
		auto b = zCompare(z_S, z_p);

		// Sample weight and velocity-aware factors (Sec.4.1)
		// The length of v_s is clamped to 0.5 minimum during normalization
		auto weight = 0;
		auto wA = dot(wc, d);
		auto wB = dot(normalize(vs), d);

		auto mag_vs = length(vs);

		// 3 phenomenological cases (Sec. 3, 4.1): Object
		// moving over p, p's blurred motion, & their blending.
		weight += dot(f, cone(T, 1 / mag_vs)) * wB;
		weight += dot(b, cone(T, 1 / mag_vc)) * wA;
		weight += cylinder(T, min(mag_vs, mag_vc)) * max(wA, wB) * 2;

		totalWeight += weight; // For normalization
		result += colorSample * weight;
	}

	return result / totalWeight;
}

McGuireのG3Dエンジンでは論文実装よりも変更がある。以下のように内積計算部分がガッツリなくなっている。
https://sourceforge.net/p/g3d/code/HEAD/tree/G3D10/data-files/shader/MotionBlur/MotionBlur_gather.pix

auto f = zCompare(z_p, z_S);
auto b = zCompare(z_S, z_p);

auto weight = 0.0f;
weight += b * cone(T, 1 / mag_vs);
weight += f * cone(T, 1 / mag_vc);
weight += cylinder(T, min(mag_vs, mag_vc)) * 2.0f;

totalWeight += weight;
result += colorSample * weight;

なねぃと調査(2)

Share

こんにちわ。Pocolです。
今日は,先日発表された
[Karis 2021] Brian Karis, Rune Stubbe, Graham Wihlidal, “Nanite A Deep Dive”, SIGGRAPH 2021 Advances in Real-Time Rendering in Games Course.
の資料を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。
また,図はすべて[Karis 2021]から引用しています。

(さらに…)

超雑訳 A Fast and Stable Feature-Aware Motion Blur Filter

Share

押忍!Pocolです。
今日は,前回読んだ論文の改良版である
[Guertin 2014] Jean-Philippe Guertin, Morgan McGuire, Derek Nowrouzezahari, “A Fast and Stable Feature-Aware Motion Blur Filter”, HPG 2014, Vol.33, No.2, 2014.
を読んでみようと思います。

いつもながら,誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。

(さらに…)

超雑訳 Supplemental Material – Physically-Based Real-Time Lens Flare Rendering

Share

こんるるー。Pocoldです。
今日は,前回読んだ[Hulin 2011a]のサプリメンタルマテリアルである[Hulin 2011b]を読むことにします。

[Hullin 2011a] Matthias Hullin, Elmar Eisemann, Hans-Peter Seidel, Sungkil Lee, “Physically-Based Real-Time Lens Flare Redering”, SIGGPRAH 2011, No.108, pp.1-10.
[Hullin 2011b] Matthias Hullin, Elmar Eisemann, Hans-Peter Seidel, Sungkil Lee, “Supplemental Material – Physically Based Rea-Time Lens Flare Rendering”, SIGGRAPH 2011.

いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。

(さらに…)

超雑訳 Physically-Based Real-Time Lens Flare Rendering

Share

わふー。Pocolです。
twitterみてたら,
Physically-Based Real-Time Lens Flare Renderingの特許が切れていると話題を見て,
https://patents.google.com/patent/US20140210844A1/enを見てみたら,確かに
Abandonedとなっていたので,ちょっと実装する気が湧いてきたので,まずは論文を読むことにしました。

[Hullin 2011] Matthias Hullin, Elmar Eisemann, Hans-Peter Seidel, Sungkil Lee, “Physically-Based Real-Time Lens Flare Redering”, SIGGPRAH 2011, No.108, pp.1-10.

いつもながら、誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。

(さらに…)

なねぃと調査(1)

Share

こんばんわ。Pocolです。
もうすぐSIGGRAPHですね。SIGGRAPHのコースで「なねぃと」についての話があるそうで,そのコースを受ける前にある程度は調査しておこうと思いました。Sさん、問題あればご連絡を。

個人的に知りたいこと

今回の調査では下記のようなことを知りたいなと思ったので,調査してみました。

  • どんなレンダリングフローなのか?
  • 実際にどんなシェーダ使っているの?
  • どうやってストリーミングするものを決めてるの?
  • ストリーミングデータはどうやって作るのか?

大雑把な流れ

なねぃとは仮想化マイクロポリゴンジオメトリシステムです。いわゆるVirtual Textureみたいなテクスチャストリーミングのメッシュ版という感じのやつです。細かいポリゴンを扱えるのが売りになっていて,ものすごくいい感じのディテールが表現できます。
なねぃとを実現するためのキーだと思っているものは次の通りです。

  • GPU駆動描画
  • 高速なソフトウェアラスタライズ
  • Deferred Material(Visibility Buffer)
  • トライアングルデータの圧縮
  • 階層LODの構築

まずGPU駆動描画はその名の通り,GPU上で描画するかどうかを判定を行い,その結果で描画が駆動する手法のことを言ったりします。これはカプコンさんだったり,アサクリだったり,Trialsだったりと色々な会社さんがすでに取り組まれています。内容について知らない方がいたら下記の資料などを読むと良いと思います。

UE5はバウンディングのスケールに応じてソフトウェアラスタライズとハードウェアラスタライズの分岐がコンピュートシェーダ上で決定されます。
小さな三角形はコンピュートシェーダを用いたソフトウェアラスタライズが実行され,大きな三角形に対してハードウェアラスタライザが実行されます。
ソフトウェアラスタライザですが,小さな三角形に対しては平均で3倍高速化したと「Nanite | Inside Unreal」の動画でKarisが言っていました。恐るべき速度ですね。

※図は”Nainte | Inside Unreal”より引用

あとは,この高速なソフトウェアラスタライズを支える技術として,Deferred Materialを使用しています。いわゆるVisibility Bufferというやつです。
これの何が良いかというと,深度とマテリアルインデックスをバッファに出力してしまい,マテリアル評価を遅延実行できるというメリットがあります。つまり,いちいちシェーダの切り替えをしなくて良いということです。これによりUE5は不透明物体の描画を1ドローで実現しています。

※図は”Nainte | Inside Unreal”より引用

さらにNaniteを見ていてがんばっているなーと思うのがデータの圧縮です。1トライアングルにつき平均14.4Byteだそうです。一応自分でもソースコードを追ってみて,確かにそうだなということを確認しました。

※図は”Nainte | Inside Unreal”より引用

どんなレンダリングフローなのか?

まずは,レンダラーの流れをつかみ大雑把にどんなことをやっているのかを把握してみました。
レンダラーの実装は,Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cppにあります。これがディファードレンダリングの実装になっており,Render()メソッドに実装があるので,これを地味に読み解いていきました。なんとこの関数1600行ほどあります。

この関数をところどころを端折った疑似コードが下記のような感じになります。

ざっくりですが,処理をまとめておくと

  • グローバルリソースを更新
  • 非同期でファイル読み込みを開始
  • 読み込みタスクの完了待ちをして,GPUにデータを転送
  • Visibility Bufferをクリア
  • ビュー情報をパッキングしてカリングとラスタライズを実行
  • 深度バッファに出力
  • マテリアルごとにドローコールを発行して,タイル描画を行うことによりG-Bufferを構築

…という感じです。

実際にどんなシェーダ使っているの?

CullRasterize()

下記のシェーダが実行されるようです。

  • InstanceCull :パス Engine/Private/Nanite/InstanceCulling.usf
  • InitArgs :パス Engine/Private/Nanite/ClusterCulling.usf
  • InstanceCullVSM :パス Engine/Private/Nanite/InstanceCulling.usf
  • PersistentClusterCull : パス Engine/Private/Nanite/ClusterCulling.usf
  • CalculateSafeRasterizerArgs : パス Engine/Private/Nanite/ClusterCulling.usf
  • HWRasterizerVS : パス Engine/Private/Nanite/Rasterizer.usf
  • HWRasterizerPS : パス Engine/Private/Nanite/Rasterizer.usf
  • MicropolyRasterize : パス Engine/Private/Nanite/Rasterizer.usf

左側は実行される関数で,右側はその関数が実装されているファイルパスを表します。
PersistentCull()がおそらく一番巨大なシェーダコードになると思うのですが,クラスタ階層のトラバーサルとかカリング,あとはソフトウェアラスタライズかハードウェアラスタライズかどうかの切り分けなんかも行っています。
MicroPolyRasterize()がソフトウェアラスタライズを行っているのですが,R32G32のバッファに書き込みをします。Rチャンネルの25bit分がVisibleIndexで,残りの7bitがTriangleIDになっています。GチャンネルはDepthに割り当てられているようです。

EmitDepthTargets()

下記のシェーダが実行されるようです。

  • DepthExport : パス Engine/Private/Nanite/DepthExport.usf
  • EmitSceneDepthStencilPS : パス Engine/Private/Nanite/ExportGBuffer.usf
  • EmitSceneSStencilPS : パス Engine/Private/Nanite/ExportGBuffer.usf

名前通りの処理っぽいです。

DrawBasePass()

下記のシェーダが実行されるようです。

  • ClassifyMaterials : パス Engine/Private/Nanite/MaterialCulling.usf
  • FullScreenVS : パス Engine/Private/Nanite/ExportGBuffer.usf

ClassifyMaterials()の中では,VisibleなMaterialとMaterial Rangeを決定するようです。Material Rangeにはマテリアルのマスクビットが格納されています。
大雑把な理解として,描画対象となるマテリアルの矩形範囲を決めているようです。
FullScreenVS()では,決定したマテリアルの矩形範囲にあるタイルで,本当にそのマテリアルの描画必要かどうかを判定して,要らないところはNaNを頂点シェーダで設定して,タイルをカリングするという処理を行うようです。タイルカリングには5つのモードがあるようです。

ストリーミングの仕組みは?

ストリーミングの管理はFStreamingManagerというクラスで管理されているようです。
Engine/Source/Runtime/Engine/Public/Rendering/NaniteStreamingManager.hにクラスの宣言があります。
・クラスタページデータ
・クラスタページヘッダ
・クラスタ修正更新バッファ
・ストリーミングリクエストバッファ
・ストリーミングリクエストリードバックバッファ
・ペンディングページ
・リクエストハッシュテーブル
などを保持しているようですが,まだ理解しきれていないので,理解できるようになったら別の記事として書くことにします。
ページリクエストは,PersistentClusterCull()というシェーダが実行されてRequestPageRange()という関数内でリクエストが書き込まれるようです。
このシェーダで書き込んだページリクエストの取得はディファードレンダラー内のAsyncUpdate()内でバッファをmapすることでCPU側で取得されるようです。
リクエストハッシュテーブルにGPUから取得したものを登録して,被るものがあるかどうかをチェック。チェックにヒットしたらストリーミングするページとしてプッシュし,優先度でソートして,LRUを更新しています。
一方,検索にヒットしない場合は,優先度付きリクエストヒープにいったんプッシュするようです。その後、このヒープからポップしてストリーミングするページを選択しています。
ストリーミングするものは読み込みされている状態なので,送ればいいのですが,これから読み込みしないといけないペンディング状態のものについては,ペンディング状態のページを収集し,ランタイムリソースIDがコンピュートシェーダで書き込まれるので,これを利用してFbyteBulkDataを取得するようです。取得したデータをFIORequestTaskに登録し,ParallelFor文を用いて,ファイルの非同期読み込みが実行さるようです。
ストリーミングマネージャのEndAsyncUpdate()という関数で,読み込み完了待ちをしてからResourceUplodTo()関数を使ってGPUに転送しているみたいです。

ストリーミングデータはどうやってつくるのか?

メッシュデータの構築

FStaticMeshBuilder::Build()経由でBuildNaniteFromHiResourceModel()が呼び出されて,データが作成されるようです。
BuildDAG(), ReduceDAG(), FindDAGCut()などの内部の処理がまだ全然理解できていないので,鋭意調査中です。

エンコード

ジオメトリデータはEncodeGeometryData()という関数でエンコードされるようです。
Positionデータは63bitに。
法線ベクトルはOctrahedronで表現し,XYで18bitに。
テクスチャ座標はXYで32bitに。
ここまで合計14byteになるので,確かにKarisが言っていた平均14.4Bという数字は納得できます。
ちなみに頂点カラーやUV数を増やす場合はさらにデータ容量が増えていく感じです。

おわりに

今回は,えらくざっくりですが,どんな感じで動くのかについて調査しました。
レンダリングフローについては理解できたのですが,実際のストリーミングの詳しい処理内容や,階層LODの作成方法など,まだまだわからない箇所もあるので次回調査してみたいと思います。

順調です。

Share

こんばんわ、Pocolです。
アレですが,順調に進んでおります。機が熟したら書きますので、もう少しお待ちください。