画面は、真っ暗なまま。三角形を描くコードを書いたはずなのに、何も出てきません。しかも、エラーも警告も出ないので、どこを疑えばいいのか分かりません。
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つずつ再現する

| # | 原因 | 三角形の画素数 | どの段階で捨てられたか |
|---|---|---|---|
| 0 | (正常) | 50,156 | – |
| 1 | ビューポートが画面の外にある | 0 | ラスタライザー(描く範囲が画面の外) |
| 2 | 頂点の並び(巻き順)が逆 | 0 | ラスタライザー(背面カリング) |
| 3 | 深度バッファを0でクリアした | 0 | 深度テスト(全部不合格) |
| 4 | 行列の転置し忘れ | 51,228 | (消えない。位置がずれる) |
| 5 | ニアクリップが三角形より遠い | 0 | クリップ(手前の面より近いので切られる) |
| 6 | 三角形の色が背景と同じ | 0 | (描かれているが、見えない) |
| 7 | 頂点バッファを設定し忘れた | 0 | 入力アセンブラ(頂点データが無い) |
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) |
転置し忘れたほうは、三角形の中心が、横に約87ピクセル、縦に約31ピクセルずれました。「出ているから大丈夫」と思い込むと、後で、モデルの位置や向きがおかしいことになり、原因が分かりにくくなります。
もう1つ、怖い結果があります。カメラを正面に置いて(三角形の真正面から見て)、同じ比較をしたところ、転置した画像と転置し忘れた画像が、1ピクセルも違いませんでした。
正面から見たとき
転置した : 三角形の画素数 77,666 中心 (639.5, 400.9)
転置し忘れ : 三角形の画素数 77,666 中心 (639.5, 400.9)
2枚の画像の、画素値の最大の差 : 0三角形が、カメラの真正面にあって、奥行きが同じ平面の上にあると、転置の違いが、画面の位置に現れないのです(行列を計算してみると、違いが出るのは奥行きの値だけで、今回の三角形では、それが画面に影響しません)。簡単なテストでは動いて、カメラを動かした途端におかしくなるのは、こういう理由でした。行列まわりの確認は、カメラを斜めにして行います。
調べる順番
「何も出ない」ときは、次の順で、1つずつ確認すると、無駄が少なくなります。
- 背景色と、三角形の色を、はっきり違う色にする(6を除く)
- カリングを「なし」にして、描いてみる(2を除く)
- 深度バッファのクリア値と、深度の比較関数を確認する(3を除く)
- ビューポートの位置とサイズを確認する(1を除く)
- ニアとファーの値、カメラと三角形の距離を確認する(5を除く)
- 頂点バッファ、インデックスバッファ、入力レイアウトの設定を確認する(7を除く)
- 行列を単位行列にして、頂点の座標を直接画面に出してみる(4を除く)
グラフィックスのデバッグツール(RenderDocなど)を使うと、各段階で三角形が残っているかを画面上で確認できますが、今回の実験では使っていません。上の順番は、ツールなしで、コードの設定を1つずつ疑うときの目安です。
三角形が表示されないときのよくある失敗例と対処法
①背景と同じ色で描いていた
単純ですが、実際によくあります。背景を、見慣れない色(例:濃い青)にしておくと、気づきやすくなります。
②カリングを疑わずに、行列や座標を何度も見直す
モデルが見えないときは、カリングが原因のことが多いです。まず、カリングを「なし」にして、見えるかを試します。
③見えたので、行列は正しいと思い込む
転置し忘れは、三角形が消えないこともあります。カメラを斜めに動かして、位置や形がおかしくないかを確認します。
注意点
- 「何も出ない」は、パイプラインのどこかで捨てられている。段階ごとに切り分ける
- 三角形の色の画素数を数えると、「消えた」か「ずれた」かを、数字で区別できる
- 行列の転置し忘れは、三角形が消えないことがある。正面から見ただけでは、違いに気づけない
- 深度バッファのクリア値は、リバースZを使っていないなら1.0
- エラーが出ないのが普通。デバッグレイヤーや、フレームデバッガも、併用する(今回は未使用)
- 今回の検証は、DirectX11、GeForce RTX 3050 Laptop、解像度1280×720のデバッグビルド
この記事のポイント
- 三角形が出ない原因を、7通り再現した。ビューポート、巻き順、深度クリア、ニアクリップ、背景色、頂点バッファは、画素数が0になった
- 行列の転置し忘れだけは消えず、位置がずれた。正面のカメラでは、1ピクセルも違わなかった
- 切り分けは、色→カリング→深度→ビューポート→ニア・ファー→頂点バッファ→行列の順に、1つずつ確認する
描画のトラブルは、画面を見ているだけだと、全部同じ「何も出ない」に見えます。三角形の画素を数えるようにしてみると、何が起きているのかを、冷静に切り分けられるようになりました。


