HN 日本語サマリー

← 一覧へ戻る
科学・技術

テクスチャの解剖学

Anatomy of a Texture (agentlien.github.io)

10 pointsby Agentlien2 コメント

要約

この記事は、ゲーム開発におけるテクスチャメモリレイアウトの複雑さについて解説しています。特に、異なるプラットフォーム間でテクスチャデータを変換する際の課題に焦点を当て、ブロック圧縮、テクセル順序、ミップマップ、テクスチャタイルといった技術がパフォーマンス向上にどのように寄与し、同時にデバッグを難しくしているかを説明しています。BC7圧縮形式を例に、メモリアドレス計算の難しさを掘り下げています。

全文翻訳

Agentlien - グラフィックスプログラマー テクスチャの解剖学 はじめに 昨年、ゲームプラットフォーム間でテクスチャデータを変換するコードを書く必要がありました。その際、テクスチャメモリレイアウトの複雑さを真剣に過小評価していました。現在、私は505 Gamesのチームで、カスタムPCゲームエンジンをコンソールに移植する作業を行っています。直面している多くの課題の一つは、テクスチャ変換に関するものです。私はその複雑さと微妙な点の多くを知っていました。それらのほとんどを個別に認識していました。しかし、これらの詳細すべてを連携させて動作するコードを書くまで、その完全な複雑さが本当に理解できませんでした。そこで、これは興味深いブログ記事になるだろうと思いました!このブログ記事では、テクスチャメモリレイアウトの複雑さと、それがなぜ必要とされるのかを説明します。これは、コンソールテクスチャの各部分のメモリアドレスの計算をデバッグしようとしている人の視点を通して行われます。主題の性質上、この記事は少し技術的になる必要があります。プログラミングの基本、メモリレイアウト、および基本的なビデオゲーム技術とデジタル画像に関する知識を前提とします。この記事全体を通して、BC7を使用した1024x1024 RGBAテクスチャを例として使用します。説明のために、簡単な木目のテクスチャを使用しましょう。図1. 1024x1024 例示テクスチャ このテクスチャをプラットフォーム間で変換するには、多くの詳細を追跡する必要があります。この記事では、ブロック圧縮、テクセル順序、ミップ、テクスチャタイルについて説明します。ピッチ、深度、テクスチャ配列インデックスなどの他の詳細もあります。これらは主に単純なオフセットを必要とし、少し面倒ですが興味深い理論を追加しないため、無視します。これらの複雑さをすべて無視し、結果を単純な色の値のストリームとして解釈した場合、その画像は次のようになります。図2. 1024x1024 カオスな虹色のノイズ プラットフォーム固有の情報 この記事がテクスチャフォーマットについてなのに、なぜメモリ位置について話しているのか?私たちがこれから見ていくすべての詳細は、プラットフォーム間で非常に重要な類似点と相違点を持っています。特に、テクスチャはプラットフォーム間で同じ階層のビルディングブロックに分解されます。しかし、階層のすべてのレベルで、これらの要素のメモリ内での順序はプラットフォーム間で異なる場合があります。これは、テクスチャを保持するためのメモリ領域が与えられた場合、プラットフォーム間でテクスチャを変換する行為は、これらの各要素について、変換後の期待されるメモリアドレスを計算する問題と見なすことができることを意味します。私たちは、これらのコンポーネントのほとんどをプラットフォームに依存しない方法で主に議論します。区別が必要な場合は、DirectX 12のビューに依存します。 理想化されたビュー デジタル画像の一般的な理解があれば、2Dテクスチャがメモリでどのように表現されると期待できるでしょうか?画像には多くのカラーチャンネルがあり、それぞれに特定のビット深度があります。一般的なビット深度は8で、チャンネルあたり1バイトを意味します。これにより、色あたり[0,255]の範囲の値が得られます。画像は、通常2次元(幅と高さ)にわたるカラーサンプルのグリッドで構成されます。通常の画像では、これらのサンプルはピクセルと呼ばれます。テクスチャの場合、それらをテクセルと呼びます。これを踏まえると、ナイーブな仮定は、32ビットカラー深度のRGBA画像は、各チャンネルの1バイトずつ、4つの連続するバイトで表される各テクセルで、左から右へ、行ごとにスキャンされるテクセルだけのストリームであるということです。単純なテクスチャの中には実際にこのようになっているものもありますが、残念ながら、ほとんどのテクスチャが最新のビデオゲームでどのように表現されているかとはかけ離れています。その理由はパフォーマンスです。直交する多くのテクニックが適用されており、それぞれがテクスチャ表現を複雑にしますが、レンダリングパフォーマンスを向上させます。ほとんど(例:テクセル順序とテクスチャタイル)はキャッシュの局所性を改善するために存在します。つまり、同時に必要になるであろう他のすべてのデータにできるだけ近い場所にデータを格納するように最善を尽くします。良い局所性は、最新のコンピュータハードウェアで最もコストのかかる操作の1つであるメモリ転送を待って無駄にする時間を劇的に減少させます。ブロック圧縮も局所性を改善しますが、主にメモリ内のテクスチャサイズを縮小することでメモリ転送速度を向上させます。最後に、ミップがあります。これは、事前にテクスチャ全体を複数のステップでダウンサンプリングすることにより、レンダリングコストを劇的に削減します。これにより、周囲の領域のすべてのテクセルを平均化したい場合に必要となる多くのコストのかかるテクスチャサンプルを回避できます。プラットフォーム間でテクスチャ読み込みをデバッグする人の観点から見ると、これらの各テクニックは、追跡すべきもう一つの複雑さです。 単一のテクセル 典型的な最新のビデオゲームテクスチャで、実際のテクセルがどのように表現されているかを見てみましょう。簡単にするために、元の画像が上記のRGBAでチャンネルあたり8ビットであると仮定しましょう。非圧縮の場合、これはテクセルあたり合計32ビット(4バイト)になります。しかし、ほとんどの最新のテクスチャはブロック圧縮を使用しています。ブロック圧縮は、テクスチャのメモリサイズを縮小してデータ転送時間を短縮します。今日最も一般的なブロック圧縮フォーマットはBC7です。これは非常に複雑なフォーマットで、この記事で説明できる以上の奇妙な点があります。私自身も詳細をすべて知りません。その核心において、BC7は、隣接するテクセルの類似性に関する巧妙な仮定に依存する可逆圧縮フォーマットです。テクセルを4x4ブロックに格納します。各ブロックは、エンドポイントと呼ばれる参照色のペアを指定します。各テクセルは、これらのエンドポイント間の補間値を識別するインデックスを指定することによって色を取得します。単一のブロックは16バイトの大きさで、16テクセルを記述します。これは、テクセルあたり1バイトに償却されます。チャンネルあたり8ビットのRGBAテクスチャの場合、4倍の圧縮率です。この大きな圧縮率にもかかわらず、BC7圧縮テクスチャと非圧縮テクスチャの違いを人間の目で区別するのは非常に困難です。これは、大量の大きなテクスチャのストリーミングを最適化する必要があるゲーム開発者にとって朗報です。テクセルをブロックで圧縮するもう一つの利点は、多くのエフェクトが複数の隣接するテクセルをサンプリングする必要があることです。近くのテクセルをメモリ内で近づけることで、キャッシュの局所性が向上し、メモリアクセスが高速化されます。ここで、上記のテクスチャが、テクスチャを4x4 BC7ブロックを表す16バイトチャンクのシリーズとして正しく扱うと、どのように見えるかを見ることができます。順序は間違っていますが、正しい色のブロックが見えます。図3. 1024x1024 並べ替えられていないテクセルのごちゃ混ぜの混合 残念ながら、ブロック圧縮は変換コードにかなりの複雑さを加えます。なぜなら、それはテクスチャのメモリサイズとテクスチャのインデックス付けを変更するからです。最大の落とし穴は、すべてのテクスチャが圧縮されているわけではないため、コードが両方のケースで一貫して正しいことを行う必要があるということです。単一のテクセルを読み書きしていたコードは、テクセルを扱うかブロックを扱うかをチェックする必要があります。非圧縮テクスチャの場合、幅と高さは各次元の単なるインデックスです。圧縮テクスチャは、一度に4x4テクセルのブロックを反復処理する必要があります。圧縮は、テクスチャのメモリサイズを計算する方法にも影響します。メモリサイズは、解像度、チャンネル数、チャンネルビット深度、および潜在的な圧縮モードに依存します。 ブロックのデバッグ BC7によって採用されている巧妙なトリックの量は、デバッグにとっては悪いニュースです。ツールなしでは、メモリダンプは実質的に解読不能になります。グラフィックスデバッガには、テクスチャを視覚化および分析するための組み込みツールが含まれています。残念ながら、結果が上記の図のように見える場合、それはしばしば役に立ちません。圧縮されたテクスチャに視覚的に解釈可能なデバッグ情報を書き込むことも簡単ではありません。圧縮を無視して生の値を書き込むと、無意味な色の混乱が生じます。視覚的なデバッグを本当に複雑にするのは、バイトアラインメントを変更するあらゆる偶発的なオフセットが、すべてのデータを視覚的に不整合にするということです。なぜなら、それはブロックのどの部分がエンドポイントとして読み取られ、どの部分が読み取られるかに影響するからです。