【ゲーム制作】DirectX11で三角形が表示されない・真っ黒になる原因とは?|原因7つを1つずつ再現して、画素数で確かめるチェックリスト

【ゲーム制作】DirectX11で三角形が表示されない・真っ黒になる原因とは?|原因7つを1つずつ再現して、画素数で確かめるチェックリスト ゲーム制作

画面は、真っ暗なまま。三角形を描くコードを書いたはずなのに、何も出てきません。しかも、エラーも警告も出ないので、どこを疑えばいいのか分かりません。

DirectX11で三角形が表示されない原因は、どこかの設定を1つ間違えるだけで起き、しかも1つではありません。そこで、原因を7通り、1つずつわざと起こして、三角形の色の画素が画面に何個残ったかを数えました。画面を目で見るだけでなく、数字にしたことで、思っていたのと違う結果も見つかりました。

三角形が表示されないとは?

三角形が表示されないとは、

・頂点データから画面のピクセルまでのパイプラインのどこか1か所で、三角形が捨てられてしまうこと

です。DirectX11の描画は、頂点の読み込み、座標変換、クリップ、ラスタライズ、深度テスト、といった段階を順に通ります。どの段階で捨てられても、結果は同じ「何も出ない」になります。だから、どの段階で落ちたのかを切り分けることが、調べ方の中心になります。

実験:いちばん小さい構成を作る

検証用に、三角形1つだけを描く最小構成を作りました。シェーダーは、行列をかけて位置を決め、決まった色を返すだけです。

// 「三角形が表示されない」原因を再現するための、いちばん小さいシェーダー。
//   b7: WVP行列(転置して渡す)と、三角形の色
#include "../common.hlsl"

cbuffer TriBuffer : register(b7) {
    matrix WVP;
    float4 TriColor;
};

struct TRI_OUT {
    float4 Position : SV_Position;
};

TRI_OUT VSMain(VS_IN In) {
    TRI_OUT o;
    o.Position = mul(In.Position, WVP);
    return o;
}

float4 PSMain(TRI_OUT In) : SV_Target {
    return TriColor;
}

C++側では、頂点バッファとインデックスバッファを作り、描画の直前に各設定を行います。ここでは、問題を起こしやすい箇所を、casという引数で1つずつ壊せるようにしてあります。

▼TroubleDemo.cpp(描画の部分)
void TroubleDemo::DrawTri(void) {
    ID3D11DeviceContext* ctx = GetDeviceContext();

    const float clear[4] = { 0.12f, 0.14f, 0.20f, 1.0f };
    float depthClear = (cas == 3) ? 0.0f : 1.0f;                 // 3: 深度を0でクリアしてしまう
    DemoTargetBegin(target, clear, depthClear);

    float nearZ = (cas == 5) ? 10.0f : 0.1f;                     // 5: ニアクリップが三角形より遠い
    XMMATRIX view = XMMatrixLookAtLH(XMVectorSet(DemoArgFloat("camx", 1.5f), DemoArgFloat("camy", 0.8f), -3.0f, 0.0f),
                                     XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
                                     XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
    XMMATRIX proj = XMMatrixPerspectiveFovLH(XMConvertToRadians(60.0f),
                                             (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT, nearZ, 100.0f);
    XMMATRIX wvp = view * proj;

    TriCB data;
    if (cas == 4) XMStoreFloat4x4(&data.wvp, wvp);               // 4: 転置し忘れ
    else          XMStoreFloat4x4(&data.wvp, XMMatrixTranspose(wvp));
    data.color = (cas == 6) ? XMFLOAT4(clear[0], clear[1], clear[2], 1.0f)   // 6: 背景と同じ色
                            : XMFLOAT4(1.0f, 0.55f, 0.10f, 1.0f);
    ctx->UpdateSubresource(cbTri, 0, NULL, &data, 0, 0);
    ctx->VSSetConstantBuffers(7, 1, &cbTri);
    ctx->PSSetConstantBuffers(7, 1, &cbTri);

    if (cas == 1) {                                              // 1: ビューポートが画面の外
        D3D11_VIEWPORT vp = {};
        vp.TopLeftX = 5000.0f;
        vp.Width = (FLOAT)SCREEN_WIDTH;
        vp.Height = (FLOAT)SCREEN_HEIGHT;
        vp.MaxDepth = 1.0f;
        ctx->RSSetViewports(1, &vp);
    }

    ctx->IASetInputLayout(layoutTri);
    ctx->VSSetShader(vsTri, NULL, 0);
    ctx->PSSetShader(psTri, NULL, 0);
    ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    ctx->IASetIndexBuffer((cas == 2) ? ibReversed : ib, DXGI_FORMAT_R32_UINT, 0);   // 2: 巻き順が逆
    if (cas != 7) {                                              // 7: 頂点バッファを設定し忘れ
        UINT stride = sizeof(VERTEX_3D), offset = 0;
        ctx->IASetVertexBuffers(0, 1, &vb, &stride, &offset);
    }
    ctx->DrawIndexed(3, 0, 0);

    DemoTargetEnd(target);
}

三角形の色は、背景と区別しやすいオレンジです。画面から、このオレンジ色の画素を数えて、「三角形が何ピクセル出たか」を測ります。正常なとき、画面に出る三角形は、約5万ピクセルでした。

7通りを1つずつ再現する

三角形が表示されない原因を1つずつ再現した8枚の画像と、三角形の画素数
左上が正常。それ以外は、1か所だけ壊したもの。画素数が0なら、三角形は完全に消えている
#原因三角形の画素数どの段階で捨てられたか
0(正常)50,156–
1ビューポートが画面の外にある0ラスタライザー(描く範囲が画面の外)
2頂点の並び(巻き順)が逆0ラスタライザー(背面カリング)
3深度バッファを0でクリアした0深度テスト(全部不合格)
4行列の転置し忘れ51,228(消えない。位置がずれる)
5ニアクリップが三角形より遠い0クリップ(手前の面より近いので切られる)
6三角形の色が背景と同じ0(描かれているが、見えない)
7頂点バッファを設定し忘れた0入力アセンブラ(頂点データが無い)
三角形のオレンジ色に近い画素の数(1280×720の画面)。#4だけ、消えずに残った

1:ビューポートが画面の外にある

ビューポートは「描く範囲」です。ここが画面の外(今回は左端を5,000ピクセル右にずらした)にあると、三角形は画面のどこにも描かれません。ウィンドウのサイズを変えたあとにビューポートを更新し忘れるなど、現実でも起こりやすい原因です。

2:頂点の並び(巻き順)が逆

DirectX11の既定では、時計回りに並んだ頂点が表で、背面は描かれません(カリング)。同じ三角形でも、頂点の並び順を逆にしただけで、画面から消えました。モデルを読み込んだら見えない、というときは、まずカリングを「なし」にして確認します。

3:深度バッファを0でクリアした

深度バッファのクリア値は、通常1.0(いちばん奥)です。0.0でクリアすると、すべてのピクセルの深度が「いちばん手前」になり、新しく描くものがすべて深度テストで不合格になります。「リバースZ」を試したあとに、設定を戻し忘れたときなどに起こります(リバースZは、Zファイティングの記事で扱っています)。

5:ニアクリップが三角形より遠い

カメラから三角形までは、約3.4の距離です。ニアクリップを10にすると、三角形は「カメラに近すぎる」ではなく、ニアより手前にあるとして、クリップで切られます。

6:三角形の色が背景と同じ

三角形はちゃんと描かれているのに、背景と同じ色だと、画面は何も変わりません。画面の見た目は、①〜⑤と、まったく同じです。背景色をいつもと違う色にすると、原因を1つ減らせます。

7:頂点バッファを設定し忘れた

描画の命令(DrawIndexed)の前に、頂点バッファを入力アセンブラに設定し忘れた状態です。命令は実行されても、読み込む頂点データがないため、何も描かれません。

【重要】意外な結果:行列を転置し忘れても、三角形は消えなかった

7つのうち、4番(行列の転置し忘れ)だけは、他と違って三角形が消えませんでした。画素数が、正常な5万156ピクセルに対して、5万1,228ピクセルで、ほぼ同じです。

DirectXの行列(XMMATRIX)は行優先、HLSLの定数バッファは列優先で読むので、シェーダーに渡す前に転置が必要です。転置し忘れると、変換の結果が狂います。ただし、狂い方が小さいと、三角形は出るけれど、位置や大きさがずれるだけで済みます。

▼TroubleDemo.cpp(転置する/しない)
TriCB data;
if (cas == 4) XMStoreFloat4x4(&data.wvp, wvp);               // 4: 転置し忘れ
else          XMStoreFloat4x4(&data.wvp, XMMatrixTranspose(wvp));
三角形の画素数三角形の中心(x, y)
転置した(正常)50,156(652, 388)
転置し忘れ51,228(565, 419)
カメラを斜めに置いた場合(中心の座標は、1280×720の画面でのピクセル位置)

転置し忘れたほうは、三角形の中心が、横に約87ピクセル、縦に約31ピクセルずれました。「出ているから大丈夫」と思い込むと、後で、モデルの位置や向きがおかしいことになり、原因が分かりにくくなります。

もう1つ、怖い結果があります。カメラを正面に置いて(三角形の真正面から見て)、同じ比較をしたところ、転置した画像と転置し忘れた画像が、1ピクセルも違いませんでした。

正面から見たとき
  転置した       : 三角形の画素数 77,666  中心 (639.5, 400.9)
  転置し忘れ     : 三角形の画素数 77,666  中心 (639.5, 400.9)
  2枚の画像の、画素値の最大の差 : 0

三角形が、カメラの真正面にあって、奥行きが同じ平面の上にあると、転置の違いが、画面の位置に現れないのです(行列を計算してみると、違いが出るのは奥行きの値だけで、今回の三角形では、それが画面に影響しません)。簡単なテストでは動いて、カメラを動かした途端におかしくなるのは、こういう理由でした。行列まわりの確認は、カメラを斜めにして行います。

調べる順番

「何も出ない」ときは、次の順で、1つずつ確認すると、無駄が少なくなります。

  1. 背景色と、三角形の色を、はっきり違う色にする(6を除く)
  2. カリングを「なし」にして、描いてみる(2を除く)
  3. 深度バッファのクリア値と、深度の比較関数を確認する(3を除く)
  4. ビューポートの位置とサイズを確認する(1を除く)
  5. ニアとファーの値、カメラと三角形の距離を確認する(5を除く)
  6. 頂点バッファ、インデックスバッファ、入力レイアウトの設定を確認する(7を除く)
  7. 行列を単位行列にして、頂点の座標を直接画面に出してみる(4を除く)

グラフィックスのデバッグツール(RenderDocなど)を使うと、各段階で三角形が残っているかを画面上で確認できますが、今回の実験では使っていません。上の順番は、ツールなしで、コードの設定を1つずつ疑うときの目安です。

三角形が表示されないときのよくある失敗例と対処法

①背景と同じ色で描いていた

単純ですが、実際によくあります。背景を、見慣れない色(例:濃い青)にしておくと、気づきやすくなります。

②カリングを疑わずに、行列や座標を何度も見直す

モデルが見えないときは、カリングが原因のことが多いです。まず、カリングを「なし」にして、見えるかを試します。

③見えたので、行列は正しいと思い込む

転置し忘れは、三角形が消えないこともあります。カメラを斜めに動かして、位置や形がおかしくないかを確認します。

注意点

  • 「何も出ない」は、パイプラインのどこかで捨てられている。段階ごとに切り分ける
  • 三角形の色の画素数を数えると、「消えた」か「ずれた」かを、数字で区別できる
  • 行列の転置し忘れは、三角形が消えないことがある。正面から見ただけでは、違いに気づけない
  • 深度バッファのクリア値は、リバースZを使っていないなら1.0
  • エラーが出ないのが普通。デバッグレイヤーや、フレームデバッガも、併用する(今回は未使用)
  • 今回の検証は、DirectX11、GeForce RTX 3050 Laptop、解像度1280×720のデバッグビルド

この記事のポイント

  • 三角形が出ない原因を、7通り再現した。ビューポート、巻き順、深度クリア、ニアクリップ、背景色、頂点バッファは、画素数が0になった
  • 行列の転置し忘れだけは消えず、位置がずれた。正面のカメラでは、1ピクセルも違わなかった
  • 切り分けは、色→カリング→深度→ビューポート→ニア・ファー→頂点バッファ→行列の順に、1つずつ確認する

描画のトラブルは、画面を見ているだけだと、全部同じ「何も出ない」に見えます。三角形の画素を数えるようにしてみると、何が起きているのかを、冷静に切り分けられるようになりました。

あわせて読みたい