WaveActiveLerp()について

こんちゃわ。Pocolです。
Wave組み込み命令の記事を漁っていたら,GithubにWaveActiveLerp()の実装を書いている人がいたので紹介しようと思います。
下記に説明の記事があります。
https://github.com/AlexSabourinDev/cranberry_blog/blob/master/WaveActiveLerp.md

実装は,https://github.com/AlexSabourinDev/cranberry_blog/blob/master/WaveActiveLerp_Shaders/WaveActiveLerp.hlslにあって,次のような感じみたいです。

[hlsl]
uint WaveGetLastLaneIndex()
{
uint4 ballot = WaveActiveBallot(true);
uint4 bits = firstbithigh(ballot); // Returns -1 (0xFFFFFFFF) if no bits set.

// For reasons unclear to me, firstbithigh causes us to consider `bits` as a vector when compiling for RDNA
// This then causes us to generate a waterfall loop later on in WaveReadLaneAt 🙁
// Force scalarization here. See: https://godbolt.org/z/barT3rM3W
bits = WaveReadLaneFirst(bits);
bits = select(bits == 0xFFFFFFFF, 0, bits + uint4(0, 32, 64, 96));

return max(max(max(bits.x, bits.y), bits.z), bits.w);
}

float WaveReadLaneLast(float t)
{
uint lastLane = WaveGetLastLaneIndex();
return WaveReadLaneAt(t, lastLane);
}

// Interpolates as lerp(lerp(Lane2, Lane1, t1), Lane0, t0), etc
//
// NOTE: Values need to be sorted in order of last interpolant to first interpolant.
//
// As an example, say we have the loop:
// for(int i = 0; i < 4; i++)
// result = lerp(result, values[i], interpolations[i]);
//
// Lane0 should hold the last value, i.e. values[3]. NOT values[0].
//
// WaveActiveLerp instead implements the loop as a reverse loop:
// for(int i = 3; i >= 0; i–)
// result = lerp(result, values[i], interpolations[i]);
//
// return.x == result of the wave's interpolation
// return.y == product of all the wave's (1-t) for continued interpolation.
float2 WaveActiveLerp(float value, float t)
{
// lerp(v1, v0, t0) = v1 * (1 – t0) + v0 * t0
// lerp(lerp(v2, v1, t1), v0, t0)
// = (v2 * (1 – t1) + v1 * t1) * (1 – t0) + v0 * t0
// = v2 * (1 – t1) * (1 – t0) + v1 * t1 * (1 – t0) + v0 * t0

// We can then split the elements of our sum for each thread.
// Lane0 = v0 * t0
// Lane1 = v1 * t1 * (1 – t0)
// Lane2 = v2 * (1 – t1) * (1 – t0)

// As you can see, each thread's (1 – tn) term is simply the product of the previous thread's terms.
// We can achieve this result by using WavePrefixProduct

float prefixProduct = WavePrefixProduct(1.0f – t);
float laneValue = value * t * prefixProduct;
float interpolation = WaveActiveSum(laneValue);

// If you don't need this for a continued interpolation, you can simply remove this part.
float postfixProduct = prefixProduct * (1.0f – t);
float oneMinusT = WaveReadLaneLast(postfixProduct);

return float2(interpolation, oneMinusT);
}
[/hlsl]

いまのところで,使いどころがパッと浮かばないのですが,知っていればどこかで使えそうな気がしています。
…というわけで,WaveActiveLerp()の実装紹介でした。

WaveCompactValue()について

最近、最適化で忙しいPocolです。
皆さん、お元気でしょうか?

今日は,WaveCompactValue()を勉強しようかなと思いましたので,そのメモを残しておこうと思います。
この関数は,[Drobot 2017]で紹介された手法です。

スライドに掲載されている実装は下記のよう感じです。
[hlsl]
uint WaveCompactValue( uint checkValue )
{
ulong mask; // lane unique compaction mask
for ( ; ; ) // Loop until all active lanes removed
{
uint firstValue = WaveReadFirstLane( checkValue );
mask = WaveBallot( firstValue == checkValue ); // mask is only updated for remaining active lanes
if ( firstValue == checkValue ) break; // exclude all lanes with firstValue from next iteration
}
// At this point, each lane of mask should contain a bit mask of all other lanes with the same value.
uint index = WavePrefixSum( mask ); // Note this is performed independently on a different mask for each lane.
return index;
}
[/hlsl]

これをHLSLに書き直すと次のような感じになるかとおもいます。
[hlsl]
uint WaveCompactValue(uint checkValue)
{
  // レーンのユニークなコンパクションマスク.
uint4 mask;

// すべてのアクティブレーンが取り除かれるまでループ.
for (;;)
{
// アクティブレーンの最初の値を読み取る.
uint firstValue = WaveReadLaneFirst(checkValue);

// mask は残っているアクティブレーンに対してのみ更新される.
mask = WaveActiveBallot(firstValue == checkValue);

// firstValue を持つすべてのレーンを次のイテレーションから除外する。
if (firstValue == checkValue)
break;
}
// この時点で、マスクの各レーンは、同じ値を持つ他のレーンのすべてのビットマスクを含んでいなければならない。
uint index = WavePrefixSum(mask); // これはレーンごとに異なるマスクで独立して行われる。
return index;
}
[/hlsl]

さて,このWaveCompactValue()ですが,どういった使い道があるかというと,分類分けに使用することができます。
元々の[Drobot 2017]では色々なスレッドからAtomic操作をすると重くなるため,Atomic操作を減らす目的のために使われていました。
詳細な説明は,[Drobot 2017]のスライド51にアニメーション付きで載っていますので,そちらを参照してください。
軽く図の説明だけ載せておきます。




ちなみにグループ分けのよう番号を別途作りたい場合は,
[hlsl]
uint2 WaveCompactValue(uint checkValue)
{
  // レーンのユニークなコンパクションマスク.
uint4 mask;

// グループ分け番号.
uint groupIndex = 0;

// すべてのアクティブレーンが取り除かれるまでループ.
for (uint i=0; ; ++i)
{
// アクティブレーンの最初の値を読み取る.
uint firstValue = WaveReadLaneFirst(checkValue);

// mask は残っているアクティブレーンに対してのみ更新される.
mask = WaveActiveBallot(firstValue == checkValue);

// グループ分け番号を更新.
groupIndex = i;

// firstValue を持つすべてのレーンを次のイテレーションから除外する。
if (firstValue == checkValue)
break;
}
// この時点で、マスクの各レーンは、同じ値を持つ他のレーンのすべてのビットマスクを含んでいなければならない。
uint index = WavePrefixSum(mask); // これはレーンごとに異なるマスクで独立して行われる。
return uint2(index, groupIndex);
}
[/hlsl]
のように実装すると良いみたいです。
WaveCompactValue()はタイルの分類分けやマテリアルの分類分けなんかの場面で有効活用できそうな気がしています。
…というわけで,WaveCompactValue()を使って分類分けすれば,無駄なAtomic操作を減らせるので,高速化できるよ!という話でした。

参考文献

・[Drobot 2017] Michal Drobot, “Improved Culling for Tiled and Clustered Rendering Call of Duty Infinite Warfare”, SIGGRAPH 2017 Advances in Real-time Rendering and Games course, https://advances.realtimerendering.com/s2017/index.html

Wave組み込み命令を使ったテクニック

今週の,Graphics Programming Weekly Issue 347 – July 7th, 2024で,Wave組み込み命令を使ったテクニックの記事が記載されていました。
非常に参考になるので,目を通しておいた方が良いかと思います。
Compute shader wave intrinsics tricks

ライティングに関係するものはSIGGRAPH 2017 Advances in Real-time Rendering Courseの”Improved Culling for Tiled and Clustered Rendering”に記載されています。
ゲーム開発者であれば,とあるプラットフォームSDKのサンプルプログラムにも記述があるので見てみると良いかと思います。

コースティクスシェーダ

ShaderToyにいい実装があったので,自分なりにパラメータ調整したやつ。

[hlsl]
// Based on: https://www.shadertoy.com/view/3d3yRj
// See also: KdotJPG's https://www.shadertoy.com/view/wlc3zr

float water_caustics(vec3 pos, float thickness) {
vec4 n = snoise( pos );

pos -= thickness*n.xyz;
n = snoise( pos );

pos -= thickness*n.xyz;
n = snoise( pos );

pos -= thickness*n.xyz;
n = snoise( pos );
return n.w;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (-iResolution.xy + 2.0*fragCoord) / iResolution.y;

// camera matrix
vec3 ww = normalize(-vec3(0., 1., 0.8));
vec3 uu = normalize(cross(ww, vec3(0., 1., 0.)));
vec3 vv = normalize(cross(uu,ww));

vec3 rd = p.x*uu + p.y*vv + 1.5*ww; // view ray
vec3 pos = -ww + rd*(ww.y/rd.y); // raytrace plane

pos.y = iTime*0.25; // animate noise slice
pos *= 3.; // tiling frequency

float w = mix(water_caustics(pos, 0.085), water_caustics(pos + 1.0, 0.085), 0.5);

// noise [-1..+1] -> color
float intensity = exp(w*2.5 – 1.);
fragColor = vec4(vec3(intensity), 1.);
}
[/hlsl]

ちなみに元にしたコードは,https://www.shadertoy.com/view/ssfBDfにあります。

バジェット&パフォーマンス メモ

開発の為に各社のパフォーマンスバジェット数などのメモを残しておきます。
基本はPS5世代のオープンワールド関連で,メモは随時更新します。
また、ここに載っていないものでご存じのものがあれば、ぜひコメント等にて紹介して頂けると幸いです。

Cyberpunk 2077

・GDC 2023, “Building Night City: The Technology of Cyberpunk 2077”.
エンティティは15 millionなので,15 * 100万 = 1500万エンティティ。
ワールドセクターは256m単位。



Spider-Man 2

GDC 2024, “Applied Mesh Analysis: Automating Distant City LODs in Marvel’s Spider-Man 2”.

Hogwarts Legacy

GDC 2024, “Open World Rendering Techniques in ‘Hogwarts Legacy'”.
TODO : 後で追記。

Alan Wake2

GDC 2024, “Large Scale GPU-Based Skinning for Vegetation in Alan Wake”.





Call of Duty:MW2



God of War Ragnarok

GDC 2023, “Rendering “God of War: Ragnarok””.



Horizon Forbidden West

GDC 2023, “Space-Efficient Packaging For Horizon Forbidden West”.

GDC 2022, “Adventures with Deferred Texturing in Horizon Forbidden West”.

Ghost of Tsushima

GDC 2021, “Zen of Streaming: Building and Loading Ghost of Tsushima”.


Final Fantasy 16

CEDEC 2023, “Final Fantasy XVI:大規模ゲーム開発に向けて開発環境の取り組み”.

Skull and Bones

CEDEC 2023, “Skull and Bones カメラ依存のテクスチャストリーミング実例紹介”.


Tom Clancy’s The Division

GDC 2016, “Global Illumination in Tom Clancy’s The Division”.


接線空間データの圧縮について

知識というのは更新しないダメですね。
法線データのOctahedral Encodingをよく用いています。

※図は[Cigolle 2014]より引用。

ここで,Octahedron を45度回転させて,符号をビットを別に持てば,表現する面積領域が増えるから精度が倍に上がるのと,OctWrap()の演算も無くなるので,デコードも高速できるそうです。

これが,John White 3Dというブログの,”Signed Octahedron Normal Encoding”という記事にのっていました。これをみて「ほぇぇ~」と感心しました。頭いい!
ただ、符号ビットを別に持つの何かやだなーと思っていたのですが,R10G10B10A2なら10bit分が残っているので,この32bitでNormal, Tangent, Bitangentを表現しちゃいたいところです。

以前に書いたかもしれませんが,SIGGRAPH 2020のDOOMで使っている,接線空間表現を使おうかなと思っていました。

※図は[Geffroy 2020]より引用。

しかし、今日もっといいやつを見つけました。
Jeremy Ongさんの”Tangent Spaces and Diamond Encoding”というブログ記事。この中でも上記のDOOMの方式が紹介されていますが,もっといいのがダイヤモンドエンコーディングというやつ。

※図は[Ong 2023]より引用

ここでも,Octahedronの考えを利用しています。ちなみにサンプルコードは下記の様です。

※コードは[Ong 2023]より引用

ダイヤモンドエンコードで得られた接線を表すデータを残りの10bit部分に入れてしまえば,うまくR10G10B10A2に収めることができます。

ちなみに,Octahedron Encodingですが,Z+の半球が約束されているのであれば符号ビットもいらないので,下記のHemi-Octで実現できるそうです。

※コードは[Cigolle 2014]より引用

…ということで接線空間データの圧縮についてでした。

参考文献

  • [Cigolle 2014] Zina H. Cigolle, Sam Donow, Daniel Envangelakos, Michael Mara, Morgan McGuire, Quirin Meyer, “A Survey of Efficient Representations for Independent Unit Vectors”, Journal of Computer Graphics Techniques Vol.3, No.2, 2014.
  • [JohnWhite3D 2017] John White 3D, “Signed Octahedron Normal Encoding”, https://johnwhite3d.blogspot.com/2017/10/signed-octahedron-normal-encoding.html, 2017.
  • [Geffroy 2020] Jean Geffroy, Axel Gneiting, Yixin Wang, “Rendering The Hellscape of DOOM Eternal”, SIGGRAPH 2020 Advances in Real-Time Rendering course.
  • [Ong 2023] Jeremy Ong, “Tangent Spaces and Diamond Encoding”, https://www.jeremyong.com/graphics/2023/01/09/tangent-spaces-and-diamond-encoding/, 2023.

Screen Space Reflection関連の資料

実装用メモです。

SSRを実装すると,エッジ部分どうすんのよ?っていう問題が付きまとうのですが,下記の資料で解決方法の1つが示されています。
[Genin 2017] Remi Genin, “Screen Space Planer Reflections in Ghost Recon Wildlands”, https://remi-genin.github.io/posts/screen-space-planar-reflections-in-ghost-recon-wildlands/

SSRの実装自体は下記記事が参考になります。

また,ナイーブな実装だと,下に伸びるようなアーティファクトが発生することがあります。この問題への対策の一つとして,深度の厚みを考慮することによってある程度回避する方法が述べられています(Notes On Screen Space HIZ Tracing)。

また、上記のブログでは,深度によるアーティファクトの問題が指摘されていますが,GDC 2016のTHE RENDERING OF INSIDEの中で紹介されている隣接セルの深度を補間するのが良いでアイデアではないかと触れられています。

最適化については,“Screen Space Reflections in The Surge”の中で触れられているInterleave化を試すのが良いかもしれません。SSAOなどでは実際に実装してみた経験があり,かなり処理負荷削減できました。ただ、この手法はHi-Zには相性が悪いかもしれません。

色々と試して思うのは,HYBRID SCREEN-SPACE REFLECTIONSにあるように,近い点をSSRで解決してしまって,遠い箇所についてはレイトレしてしまった方が,良いかもしれないと思っていますが,実際にHi-Z使うよりもいい結果とパフォーマンスが得られるかどうかまでは試していないので,どこかで試してみるのはありかもしれないです。

また深度の厚みに関しては,”Screen Space Reflection Techniques”という文献の中で,Min-Max Hi-Zという手法が紹介されており,これを使うとパフォーマンスやアーティファクトの問題が解決できるかもしれないです。こちらもまだ試せていないです。

Recreating Naniteの記事

JGLRXAVPOKさんという方のブログで,Recreating Naniteという記事があります。
先日紹介した,MeshoptimizerとMEITZを使ってLODを生成するような話も載っています。

Recreating Nanite: The Plan
Recreating Nanite: Visibility buffer
Recreating Nanite: Cluster rendering
Recreating Nanite: LOD generation
Recreating Nanite: A basic material pass
Recreating Nanite: LOD generation -faster, better, simpler
Recreating Nanite: Runtime LOD selection
Recreating Nanite: Mesh shader time

FrameGraph関連の資料

実装用のネタ帳です。

● FrameGraph: Extensible Rendering Architecture in Frostbite
https://www.gdcvault.com/play/1024612/FrameGraph-Extensible-Rendering-Architecture-in

● Advanced Graphics Tech: Moving to DirectX12: Lessons Learned
https://www.gdcvault.com/play/1024656/Advanced-Graphics-Tech-Moving-to

● fg
https://github.com/acdemiralp/fg

● Why Talking About Render Graphs
https://logins.github.io/graphics/2021/05/31/RenderGraphs.html

● Task Graph Renderer At Activision
https://research.activision.com/publications/2023/06/Task-Graph-Renderer-at-Activision

● Organizing GPU Work with Directed Acyclic Graphs
https://levelup.gitconnected.com/organizing-gpu-work-with-directed-acyclic-graphs-f3fd5f2c2af3

● Render graphs and Vulkan – a deep dive
https://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/

● Unreal Engine 5 – Render Dependency Graph
https://docs.unrealengine.com/5.3/en-US/render-dependency-graph-in-unreal-engine/

● GPU synchronization in Godot 4.3 is getting a major upgrade
https://godotengine.org/article/rendering-acyclic-graph/

● Render Graph 101
https://blog.traverseresearch.nl/render-graph-101-f42646255636

● An update to our Render Graph
https://blog.traverseresearch.nl/an-update-to-our-render-graph-17ca4154fd23

● EmbarkStudios kajiya-rg
https://github.com/EmbarkStudios/kajiya/tree/main/crates/lib/kajiya-rg

● Mastering Graphics Programming with Vulkan
https://github.com/PacktPublishing/Mastering-Graphics-Programming-with-Vulkan

● Render graphs
https://apoorvaj.io/render-graphs-1/

Virtual Texture関連の資料

Virtual Texture関連の私的メモです。

● Software Virtual Textures
https://mrelusive.com/publications/pubs_bytype.html
https://studiopixl.com/2022-04-27/sparse-virtual-textures

● Using Virtual Texturing to Handle Massive Texture Data
https://www.nvidia.com/content/GTC-2010/pdfs/2152_GTC2010.pdf

● Sparse Virtual Texturing
https://silverspaceship.com/src/svt/

● Advanced Virual Textures Topics
https://pdfs.semanticscholar.org/32f7/49c984e1ebd97e85f90d96b8ee5ed35c143a.pdf
https://dokumen.tips/documents/chapter02-mittring-advanced-virtual-texture-topics.html

● Virtual Texturing in Software and Hardware
https://mrelusive.com/publications/presentations/2012_siggraph/Virtual_Texturing_in_Software_and_Hardware_final.pdf

● Adaptive Virtual Texture Rendering in Far Cry 4
https://www.gdcvault.com/play/1021761/Adaptive-Virtual-Texture-Rendering-in

● Terrain Rendering in ‘Far Cry5’
https://www.gdcvault.com/play/1025480/Terrain-Rendering-in-Far-Cry

● Sampler Feedback
https://microsoft.github.io/DirectX-Specs/d3d/SamplerFeedback.html