仮想テクスチャリングは、テクスチャアトラスの論理的な極端です。
テクスチャアトラスは、内部の個々のメッシュのテクスチャを含む単一の巨大なテクスチャです。
テクスチャを変更すると、GPU上で完全なパイプラインフラッシュが発生するため、テクスチャアトラスが一般的になりました。メッシュを作成するとき、UVは、テクスチャアトラス全体の正しい「部分」を表すように圧縮/シフトされます。
@ nathan-reedがコメントで言及したように、テクスチャアトラスの主な欠点の1つは、繰り返し、クランプ、境界線などのラップモードを失うことです。さらに、テクスチャの周囲に十分な境界線がない場合、誤ってフィルタリングを行うときに、隣接するテクスチャからのサンプル。これは出血アーティファクトにつながる可能性があります。
テクスチャアトラスには、サイズという大きな制限があります。グラフィックスAPIは、テクスチャの大きさに弱い制限を設けます。とはいえ、グラフィックスメモリは非常に大きいだけです。そのため、v-ramのサイズによって与えられるテクスチャサイズにも厳しい制限があります。仮想テクスチャは、仮想メモリから概念を借用することにより、この問題を解決します。
仮想テクスチャは、ほとんどのシーンで、すべてのテクスチャのごく一部しか表示されないという事実を利用します。したがって、テクスチャのそのサブセットのみがvramにある必要があります。残りはメインRAMまたはディスク上にあります。
それを実装する方法はいくつかありますが、ショーンバレットがGDCトークで説明した実装について説明します。(視聴することを強くお勧めします)
仮想テクスチャ、物理テクスチャ、ルックアップテーブルの3つの主要な要素があります。
仮想テクスチャは、すべてに適合するのに十分なvramがあれば理論上のメガアトラスを表します。実際にはメモリ内のどこにも存在しません。物理テクスチャは、vramに実際にあるピクセルデータを表します。ルックアップテーブルは、2つの間のマッピングです。便宜上、3つの要素すべてを同じサイズのタイルまたはページに分割します。
ルックアップテーブルには、物理テクスチャ内のタイルの左上隅の場所が格納されます。それでは、仮想テクスチャ全体にUVを与えた場合、物理テクスチャに対応するUVをどのように取得するのでしょうか?
まず、物理テクスチャ内のページの場所を見つける必要があります。次に、ページ内のUVの位置を計算する必要があります。最後に、これら2つのオフセットを一緒に追加して、物理テクスチャ内のUVの位置を取得できます。
float2 pageLocInPhysicalTex = ...
float2 inPageLocation = ...
float2 physicalTexUV = pageLocationInPhysicalTex + inPageLocation;
pageLocInPhysicalTexの計算
ルックアップテーブルを仮想テクスチャのタイル数と同じサイズにした場合、最近傍サンプリングでルックアップテーブルをサンプリングするだけで、物理テクスチャ内のページの左上隅の位置を取得できます。
float2 pageLocInPhysicalTex = lookupTable.Sample(virtTexUV, nearestNeighborSampler);
inPageLocationの計算
inPageLocationは、テクスチャ全体の左上ではなく、ページの左上を基準としたUV座標です。
これを計算する1つの方法は、ページの左上のUVを差し引いてから、ページのサイズにスケーリングすることです。ただし、これはかなりの数学です。代わりに、IEEE浮動小数点の表現方法を活用できます。IEEE浮動小数点は、基数2の一連の小数で数値の小数部分を格納します。
この例では、番号は次のとおりです。
number = 0 + (1/2) + (1/8) + (1/16) = 0.6875
次に、仮想テクスチャの簡易バージョンを見てみましょう。
1/2ビットは、テクスチャの左半分にいるのか右にいるのかを示します。1/4ビットは、半分の4分の1を示しています。この例では、テクスチャは16に分割されているため、最初の2ビットはどのページにいるかを示しています。ビットはページ内の場所を示します。
exp2()でフロートをシフトし、fract()でそれらを取り除くことで、残りのビットを取得できます
float2 inPageLocation = virtTexUV * exp2(sqrt(numTiles));
inPageLocation = fract(inPageLocation);
numTilesは、テクスチャの片側あたりのタイル数を示すint2です。この例では、これは(4、4)になります
それでは、緑色の点のinPageLocationを計算してみましょう。(x、y)=(0.6875、0.375)
inPageLocation = float2(0.6875, 0.375) * exp2(sqrt(int2(4, 4));
= float2(0.6875, 0.375) * int2(2, 2);
= float2(1.375, 0.75);
inPageLocation = fract(float2(1.375, 0.75));
= float2(0.375, 0.75);
完了する前に最後にやることがあります。現在、inPageLocationは仮想テクスチャ「スペース」のUV座標です。ただし、物理的なテクスチャ「空間」にUV座標が必要です。これを行うには、仮想テクスチャサイズと物理テクスチャサイズの比率でinPageLocationをスケーリングする必要があります。
inPageLocation *= physicalTextureSize / virtualTextureSize;
したがって、完成した関数は次のとおりです。
float2 CalculatePhysicalTexUV(float2 virtTexUV, Texture2D<float2> lookupTable, uint2 physicalTexSize, uint2 virtualTexSize, uint2 numTiles) {
float2 pageLocInPhysicalTex = lookupTable.Sample(virtTexUV, nearestNeighborSampler);
float2 inPageLocation = virtTexUV * exp2(sqrt(numTiles));
inPageLocation = fract(inPageLocation);
inPageLocation *= physicalTexSize / virtualTexSize;
return pageLocInPhysicalTex + inPageLocation;
}