-- Views
February 08, 26
スライド概要
DirectX11を使ったソフトウェアラスタライザの実装解説です(生成AI使用)
すでにラスタライザについては資料が充実しているので、このスライドでしっかり勉強してもらうわけではなく、ラスタライザに使われているアルゴリズムやパイプライン
のイメージをまずは雑に大まかにつかんでいただければ幸いです
DirectX11/DirectX12 3DCGは全く分かりませんが、これから書きながら勉強していこうかと・・・
まず結果から…
DirectX11で ソフトウェアラスタライザ
と、その前に くだらないイメージ画で今回やることを紹介 モアイ像君には犠牲になってもらいます
1:記録装置から3DモデルファイルをPCに読み込み
次の二枚が今回扱う範囲です
2:図形の作成および画像にする 範囲指定を行う
ここで一旦画像化(ラスター化と呼ぶ)
今回やる事
手順 座標変換:ジオメトリパイプライン ラスター化:ラスタライゼーション ラスター化:三角形の内外テスト クリッピング:深度テスト 補間
ジオメトリパイプライン:座標変換 // 3. スクリーン座標への変換(Viewport Transform) // NDC (-1~1) -> Screen (0~w, 0~h) float2 s0, s1, s2; s0.x = (c0.x * invW0 + 1.0f) * 0.5f * ScreenSize.x; s0.y = (1.0f - c0.y * invW0) * 0.5f * ScreenSize.y; // Y反転 s1.x = (c1.x * invW1 + 1.0f) * 0.5f * ScreenSize.x; s1.y = (1.0f - c1.y * invW1) * 0.5f * ScreenSize.y; s2.x = (c2.x * invW2 + 1.0f) * 0.5f * ScreenSize.x; s2.y = (1.0f - c2.y * invW2) * 0.5f * ScreenSize.y; ・・・なんでしょうねこれ
座標変換って・・・・? 簡単に言うと自分の空間に引きこもっている3Dモデル君を外の世界に引っ張り出した 後、カメラ空間に閉じ込めて見世物にするひどいことです
座標変換:概略 今回はスクリーン座標空間への変換ですが、以下4枚で並べる座標変換と原理は同じ ですので、そちらをさらっとみていただきます
1:モデル空間にある3Dモデル 最初、3Dモデルはそれぞれローカル空間と呼ばれる自分たちの座標空間と呼ばれる 空間に閉じこもってます なに昼間からいびきかいて寝てんだ
3Dモデルをワールド空間に引っ張り出す 寝ているところすみませんが、外に出ろ(ワールド空間への座標変換) これでカメラに3Dモデルを映す準備ができたので・・・
3:カメラ空間に閉じ込める 君にはカメラの中の異次元空間に閉じこもってもらいます。 これでスクリーンに映る準備ができました・・・・
4:プロジェクション空間に閉じ込める そこからさらにモニターの中に閉じ込めます。強く生きろよ💛 これで晴れて3Dモデルがモニターに映りました
次はクリッピング(背面カリング)について紹介をします
クリッピング カメラに映す絵の中から、不要な情報を破棄していく作業 ここではカリングがクリッピングにあたる クリッピングは今回の主題である「ラスター化」の核心部分となります
図形の作成および画像にする 範囲指定。さっきさらっと見ましたね
この切り取りを行う空間をクリッピング空間と呼びます 3D空間から、レンダリング対象となる領域を切り取った空間。 この空間の外にある頂点はレンダリングされない。
ラスター化:クリッピング:カリング(三角形の内外判定) // 4. ラスタライズ判定 (エッジ関数) float area = EdgeFunction(s0, s1, s2); // バックフェイスカリング (反時計回りを正とする場合、負な ら裏面) if (area <= 0) continue; float w0 = EdgeFunction(s1, s2, p); ….
ラスター化その2:三角形の内外判定 // 5. 三角形の内外判定 if (w0 >= 0 && w1 >= 0 && w2 >= 0) { // 重心座標の正規化 w0 /= area; w1 /= area; w2 /= area;
三角形の重心って・・・・? つまり各々の頂点からの中心点ですね(詳しい話は検索してください)
次は深度バッファリング
深度バッファリング (Depth Buffering / Z-Buffering) カメラから見えない3Dデータを消してしまおう というのが隠面消去 その隠面消去処理の中で 現在最も一般的な手法
深度バッファリング 新しい点の深度 がバッファの値 より小さい(カメラに近い)場合のみ、ピクセルを上書きし、深度バッファの値も更新
深度バッファリング シーンの深度バッファは、 奥に向かって無限に透明の画用紙敷 き詰められたもので、そこに色を打つ ・・・とイメージするとよい 次は補間(ほかん)の説明に入ります・・・
たとえばこの処理をすると、絵はこう見えるでしょう
実装例に入ります
深度テスト(深度バッファリング) 1/3 // 6. 深度テスト (Z値の線形補間) // NDCのZ (c.z / c.w) を補間するのが一般的ですが、 // ここでは簡易的に W の逆数を使って深度判定します(1/Wが大きい=Wが小さい=手前) float interpolatedInvW = w0 * invW0 + w1 * invW1 + w2 * invW2; float currentW = 1.0f / interpolatedInvW;
深度テスト(深度バッファリング)2/3 // 深度バッファ更新チェック (Wが小さい方が手前) // ※NDC深度(0~1)を使う場合は z < bestDepth // ここでは簡易的なZテストとして比較 float currentDepth = c0.z * invW0 * w0 + c1.z * invW1 * w1 + c2.z * invW2 * w2; currentDepth *= currentW; // 復元 深度テストがなんなのかは、次のスライドから簡単に説明します
深度テスト(深度バッファリング)2/3 if (currentDepth < bestDepth) { bestDepth = currentDepth; 深度テストがなんなのかは、あらためてスライドから簡単に説明します
改めて深度バッファリングの説明を・・・
深度バッファリング (Depth Buffering / Z-Buffering) カメラから見えない3Dデータを消してしまおう というのが隠面消去 その隠面消去処理の中で 現在最も一般的な手法
深度バッファリング 新しい点の深度 がバッファの値 より小さい(カメラに近い)場合のみ、ピクセルを上書きし、深度バッファの値も更新
深度バッファリング シーンの深度バッファは、 奥に向かって無限に透明の画用紙敷 き詰められたもので、そこに色を打つ ・・・とイメージするとよい 次は補間(ほかん)の説明に入ります・・・
たとえばこの処理をすると、絵はこう見えるでしょう 次は補間の説明に入ります・・・
補間 // 7. 遠近補間 (写真の遠近感のひずみを補正する ) // UVやColorは直接 w0,w1,w2 で補間すると歪むため、 // 一度 (Value / W) を補間し、最後に W を掛けて復元する。 // UVの補間 float2 uv0_p = v0_raw.uv * invW0; …. float2 finalUV = (w0 * uv0_p + w1 * uv1_p + w2 * uv2_p) * currentW; // Colorの補間 float4 col0_p = v0_raw.color * invW0; … float4 finalVertexColor = (w0 * col0_p + w1 * col1_p + w2 * col2_p) * currentW; そもそも遠近補間ってなんだろう?(次スライドで簡単に図解)
遠近補間って? たとえば左絵のように曲がりゆがんだ画像に補正をかけて、右絵のようにまっすぐな線 に直して遠近感を表現することです
話を補間に戻します・・・
補間 人間でいえば「頭の中で足りない箇所を想像する」ようなものになる ミロのビーナスの全身を想像するようなもの? →画像化の時に、元データに存在しない ピクセル等のグラフィックデータを 微積や関数のグラフのような計算式を使い、色の値を計算する
比喩で「補間は人間が頭の中で絵を想像するようなもの」と書いたが・・・・ 人工知能の力によって画像を「綺麗な形にする」という事が既に商業化されている。気に なる方は「人工知能による超解像」と調べると面白いだろう
まぁこれからちょいと紹介しますが・・・
少し寄り道:超解像? 正式名称は スーパーサンプリング・アルゴリズム または スーパーサンプリング・アンチエイリアスアルゴリズム 左絵のようにギザギザ(ジャギーともいう。ジャギジャギ?)な線を右のように滑らかにする 俺の名前を言ってみろ
ゲームでの利用(½) ゲームでの利用はもう10年以上前から当たり前でしたが、特にここ5年は 機械学習や深層学習(ディープラーニング)による超解像処理が当たり前になっています。
人工知能による超解像のゲームでの利用(2/2) メジャーなアルゴリズムでは、 NvidiaのDLSSやAMDのFSR,Intelの XeSSがあります。 (ゲームに採用された例は数多く、検索すれば簡単に見つかるはずなので、 そこは各自調べていただいて・・・)
ラスタライゼーションのまとめ 座標変換は3Dモデルをテレビモニターに移すための処理 クリッピングは不要な絵を描く処理をしないようにするための下準備 補間はわからないところを想像して補う。「ミロのヴィーナスの両腕を想像する」
フルコード https://github.com/kantamRobo/DirectXTKComputeRasterizer/tree/master/DirectX TKComputeRasterizer フルコードはこちらになります
おまけ:DirectX11側の実装コード
テクスチャの生成
// 1. テクスチャの作成 (UAVとして使えるように BindFlags を設定)
D3D11_TEXTURE2D_DESC texDesc = {};
…
// 重要: SHADER_RESOURCE と UNORDERED_ACCESS を指定
texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
device->CreateTexture2D(&texDesc, nullptr, &pOutputTexture);
UAVの作成
// 2. UAVの作成
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
…
device->CreateUnorderedAccessView(pOutputTexture.Get(), &uavDesc, &pUAV);
UAVって? Unordered Access View(順序付けされないメモリへの読み書きビュー) つまりメモリへのランダムアクセスを行う際の指示書となる コンピュートシェーダーがGPGPUプログラミングであることから話さないといけないの で、詳しく話そうとすると、一口ではとても言えない
UAVって?:結論 GPGPUは普通のGPUプログラミングと違いメモリへのランダムアクセスができる方がよ い UAVはそれを操作するための指示書である ということを気に留めてくれればいい。 そもそもコンピュートシェーダー(GPGPU)プログラミングはそれ一分野で一冊分厚い本 が書けてしまう・・・・
描画部分:バックバッファへのレンダリング結果転送 // --- 重要: バックバッファへの転送 --ID3D11Texture2D* pBackBuffer = nullptr; HRESULT hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer); context->CopyResource(pBackBuffer, pOutputTexture.Get());
レンダリング結果転送: ここは何をしているのか・・・・? // --- 重要: バックバッファへの転送 --ID3D11Texture2D* pBackBuffer = nullptr; …. context->CopyResource(pBackBuffer, pOutputTexture.Get());
レンダリング結果転送: まず説明のイメージを予習 ガラスでできた架空のがくぶちの後ろを、その額縁の本体が支えている・・・・ といったイメージになる
レンダリング結果転送: ここは何をしているのか・・・・? HLSLシェーダーで、レンダリング結果を基本3原色+αチャンネルのテクスチャとして返し ていた。 // 出力先: バックバッファへ転送するためのテクスチャ RWTexture2D<float4> OutputTexture : register(u0);
レンダリング結果転送: ここは何をしているのか・・・・? この、RWTexture2Dのテクスチャバッファの実データを管理するためのバッファ領域が、 ID3D11Texture2D* pBackBuffer となる つまり・・・・
つまり・・・ ガラスでできた架空のがくぶちの後ろを、そのがくぶちの本体が支えている・・・・ といったイメージになる