【DirectX11】テクスチャが真っ黒になる・色がおかしい原因とは?|SRV・サンプラー・ミップ・チャンネル順を8通り再現して、デバッグレイヤーが教えてくれるかも確かめる

【DirectX11】テクスチャが真っ黒になる・色がおかしい原因とは?|SRV・サンプラー・ミップ・チャンネル順を8通り再現して、デバッグレイヤーが教えてくれるかも確かめる ゲーム制作

テクスチャを貼ったはずの四角形が、真っ黒です。モデルは正しい場所に出ていて、形も合っています。ただ、絵だけが出ません。CreateTexture2Dも、S_OKを返しています。

DirectX11では、テクスチャが読めなかったとき、エラーにならずに、黒く塗られることが、よくあります。原因は、1つではありません。そこで、テクスチャが、黒くなったり、色が変わったりする原因を、8通り再現して、画素の値と画像で比べました。デバッグレイヤーが、それぞれを教えてくれるかも、同時に確かめています。

画面は出さずに、128×128の描画先に、2×2のテクスチャを貼った四角形を描いて、画素を読み戻しました(MSVC 19.44、x64、Direct3D 11、ハードウェアドライバー)。

テクスチャが、画面に出るまでの流れ

シェーダーが、テクスチャの色を、読むまでには、

・テクスチャ(リソース) → シェーダーリソースビュー(SRV) → ピクセルシェーダーのスロットにセット → サンプラーで読み取る

という、段階があります。どこか1つでも抜けると、黒くなったり、色が変わったりします。ほとんどの原因は、この流れの、どこで切れているかを調べれば、見つかります。

実験の設定

テクスチャは、2×2で、左上が赤、右上が緑、左下が青、右下が黄です。ピクセルシェーダーは、tex.Sample(samp, uv)を返すだけです。ケースごとに、1つだけ、設定を壊しています。

▼描画前の設定(tx_exp.cppから抜粋。ケースによって、2・3行目のどちらかを飛ばす)
ctx->VSSetShader(vs, nullptr, 0); ctx->PSSetShader(ps, nullptr, 0);
if (cs != 1) ctx->PSSetShaderResources(0, 1, &srv);
if (cs != 2) ctx->PSSetSamplers(0, 1, &samp);
ctx->Draw(6, 0);

描画後に、四角形の4つの領域と、(ミップのケースでは)中心の色を、読み戻しました。

結果:8通りの画像

テクスチャを貼った四角形の描画結果。SRV未設定・ミップ未生成などで真っ黒になり、BGRAの取り違えで赤と青が入れ替わる
①が正常。②④⑤が真っ黒。③は、色がにじむ。⑦は、赤と青が入れ替わる。⑧は、1色になる
CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース0: 正常(2x2のテクスチャ、ポイントサンプリング)
  左上 RGB = (255,  0,  0)   右上 RGB = (  0,255,  0)
  左下 RGB = (  0,  0,255)   右下 RGB = (255,255,  0)
  デバッグレイヤーのメッセージ: 0 件

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース1: シェーダーリソースビューを、セットし忘れた
  左上 RGB = (  0,  0,  0)   右上 RGB = (  0,  0,  0)
  左下 RGB = (  0,  0,  0)   右下 RGB = (  0,  0,  0)
  デバッグレイヤーのメッセージ: 0 件

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース2: サンプラーを、セットし忘れた
  左上 RGB = (249,  3,  3)   右上 RGB = (  3,255,  0)
  左下 RGB = (  3,  3,252)   右下 RGB = (255,255,  0)
  デバッグレイヤーのメッセージ: 1 件
    [WARNING] ID3D11DeviceContext::Draw: The Pixel Shader unit expects a Sampler to be set at Slot 0, but none is bound. This is perfectly valid, as a NULL Sampler maps to default Sampler state. However, the developer may not want to rely on th

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x80070057
ケース3: テクスチャ作成時に、BIND_SHADER_RESOURCEを付け忘れた
  左上 RGB = (  0,  0,  0)   右上 RGB = (  0,  0,  0)
  左下 RGB = (  0,  0,  0)   右下 RGB = (  0,  0,  0)
  デバッグレイヤーのメッセージ: 2 件
    [ERROR] ID3D11Device::CreateShaderResourceView: A ShaderResourceView cannot be created of a Resource that did not specify the D3D11_BIND_SHADER_RESOURCE BindFlag.
    [ERROR] ID3D11Device::CreateShaderResourceView: Returning E_INVALIDARG, meaning invalid parameters were passed.

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース4: ミップを全段確保したが、ミップを生成していない(小さく描く)
  小さい四角形(約16画素)の中心 RGB = (  0,  0,  0)  (元のテクスチャは、全面(255,128,0))
  デバッグレイヤーのメッセージ: 0 件

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース5: ミップを全段確保して、GenerateMipsを呼んだ(小さく描く)
  小さい四角形(約16画素)の中心 RGB = (255,128,  0)  (元のテクスチャは、全面(255,128,0))
  デバッグレイヤーのメッセージ: 0 件

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース6: BGRAのデータを、RGBAのテクスチャとして読む
  左上 RGB = (  0,  0,255)   右上 RGB = (  0,255,  0)
  左下 RGB = (255,  0,  0)   右下 RGB = (  0,255,255)
  デバッグレイヤーのメッセージ: 0 件

CreateTexture2D: 0x00000000, CreateShaderResourceView: 0x00000000
ケース7: UV座標が、すべて(0, 0)
  左上 RGB = (255,  0,  0)   右上 RGB = (255,  0,  0)
  左下 RGB = (255,  0,  0)   右下 RGB = (255,  0,  0)
  デバッグレイヤーのメッセージ: 0 件

1つずつ見ていく

①正常

左上が赤、右上が緑、左下が青、右下が黄と、期待どおりでした。デバッグレイヤーのメッセージは、0件です。

②SRVを、セットし忘れた:真っ黒で、メッセージもなし

PSSetShaderResourcesを呼ばないと、4つの領域すべてが(0, 0, 0)の黒になりました。CreateTexture2DもCreateShaderResourceViewも、S_OKで、デバッグレイヤーのメッセージは、0件でした。「何も教えてくれない」ケースです。

③サンプラーを、セットし忘れた:色が、にじむ

サンプラーを、セットしなくても、描画はできました。ただし、画像のとおり、赤と緑の境目が、にじんでいます。サンプラーが未設定のときは、既定のサンプラーが使われて、フィルタリングが、効いてしまうからです。ポイントサンプリングのつもりが、線形補間になっています。デバッグレイヤーは、WARNINGで、「サンプラーが、設定されていませんが、NULLのサンプラーは、既定のサンプラー状態として扱われるので、有効です。ただし、その動作に頼らないほうがよいかもしれません」という趣旨のメッセージを、出しました。

④SRVの作成に、失敗している:真っ黒で、エラーは出る

テクスチャを作るとき、BindFlagsに、D3D11_BIND_SHADER_RESOURCEを付けないと、CreateShaderResourceViewが、0x80070057(E_INVALIDARG)で失敗します。失敗を見逃して、nullptrのSRVをセットすると、②と同じく、真っ黒になります。今回は、デバッグレイヤーが、「シェーダーリソースビューは、D3D11_BIND_SHADER_RESOURCEを指定していないリソースからは、作れません」と、ERRORで、原因を教えてくれました。

⑤【重要】ミップを、確保したのに、生成していない:小さく描くと、真っ黒

次は、ミップマップのケースです。256×256のテクスチャ(全面オレンジ)で、ミップを全段確保して、ミップ0にだけデータを書き込みました。それを、約16画素の、小さい四角形に貼ると、中心の色は、(0, 0, 0)の、黒でした(元の色は(255, 128, 0))。小さく描くと、縮小用のミップ(1以降)が、使われますが、そこには、何も書かれていなかったためです。

エラーも警告も、0件でした。しかも、テクスチャを大きく描くと、ミップ0が使われるので、正しく見えます。「近づくと見えるのに、遠くの物体だけが、真っ黒」という、気づきにくい症状になります。

⑥ミップを生成した:直る

同じ設定で、GenerateMipsを呼ぶと、中心の色は、元の(255, 128, 0)になりました。ミップを全段確保するなら、ミップ1以降を、生成するところまで、セットで行います。ミップマップの仕組みそのものは、ミップマップの記事で解説しています。

⑦BGRAのデータを、RGBAのテクスチャとして読む:色が、入れ替わる

画像データの、チャンネルの順番と、テクスチャのフォーマットが違うと、色が入れ替わります。今回は、B・G・R・Aの順に並べたデータを、R8G8B8A8のテクスチャとして読みました。左上が、赤ではなく青、左下が、青ではなく赤になりました。黒にはならないので、見た目で、すぐに気づけますが、画像ファイルのデコード結果をそのまま渡す場面では、起きやすいミスです。

⑧UV座標が、すべて(0, 0):1色になる

頂点のUV座標が、すべて(0, 0)だと、四角形の全体が、テクスチャの左上の1画素(赤)で塗られます。UVを、頂点バッファに書き込むときの、オフセットや、DXGI_FORMATの指定を間違えると、同じ見た目になります。

原因の切り分け:どこで、止まっているか

症状疑うところデバッグレイヤー
真っ黒(近くも遠くも)SRVを、セットしたか。CreateShaderResourceViewの戻り値(②④)SRV未設定は、0件。作成失敗は、ERRORで原因を教えてくれる
遠くだけ真っ黒ミップを、生成したか(⑤)0件
色が、にじむサンプラーを、セットしたか(③)WARNINGが出る
赤と青が入れ替わるデータの、チャンネル順と、フォーマット(⑦)0件
1色になるUV座標(⑧)0件
8通りのうち、デバッグレイヤーが教えてくれたのは、③と④の2つだけだった

直し方の、チェックリスト

  1. CreateTexture2DとCreateShaderResourceViewの、戻り値を確認する(FAILEDなら、その場で止める)
  2. テクスチャに、D3D11_BIND_SHADER_RESOURCEを付けたか
  3. 描画の前に、PSSetShaderResourcesとPSSetSamplersを、呼んでいるか
  4. ミップを使うなら、ミップ1以降を、生成しているか(GenerateMips、またはDirectXTexなど)
  5. 画像データの、チャンネル順(RGBA・BGRA)と、DXGI_FORMATが、合っているか
  6. 頂点の、UV座標が、期待した範囲か(RenderDocなどのツールや、画素値の出力で確認)

テクスチャが黒くなるときの、よくある失敗例と対処法

①CreateShaderResourceViewの、戻り値を確認しない

実験④のとおり、失敗しても、プログラムは、そのまま進みます。FAILED(hr)なら、メッセージを出して、止めてください。デバッグレイヤーを有効にしておくと、失敗の理由が、文章で分かります。

②近くで確認して、安心する

実験⑤のとおり、ミップが生成されていなくても、近くでは、正しく見えます。テクスチャを、小さく描く場面(遠くの物体など)でも、確かめてください。

③デバッグレイヤーが静かなので、問題ないと思う

SRVのセット忘れ(②)と、ミップ未生成(⑤)は、メッセージが0件でした。三角形が出ない原因の記事の考え方と同じで、メッセージが無いことは、正しい証拠にはなりません。

注意点

  • テクスチャが読めないとき、DirectX11は、エラーにせず、黒く塗ることが多い
  • SRVの未設定と、ミップ未生成は、デバッグレイヤーが、何も言わなかった(0件)
  • BIND_SHADER_RESOURCEの付け忘れは、CreateShaderResourceViewがE_INVALIDARGで失敗し、デバッグレイヤーが原因を教えた
  • サンプラー未設定は、既定のサンプラーで、動いてしまう(フィルタリングが効き、色がにじむ)
  • ミップは、確保しただけでは、中身が空。小さく描くと、真っ黒になった
  • 今回の検証は、MSVC 19.44、Direct3D 11、ハードウェアドライバー、デバッグレイヤー有効、2×2と256×256のテクスチャで行った。結果は、ドライバーによって変わる可能性がある

この記事のポイント

  • テクスチャの流れは、リソース → SRV → セット → サンプラー。どこで切れているかを探す
  • 真っ黒の主な原因は、SRVのセット忘れ・作成失敗・ミップ未生成。戻り値と、セットの有無を、確認する
  • 遠くだけ黒いなら、ミップを疑う
  • デバッグレイヤーは、8通りのうち2つしか、教えてくれなかった。画素値で、確かめる

「黒い」という症状は、1つに見えて、原因は、いくつもあります。流れの順に、1段ずつ、確かめていくだけで、たいていは、見つかります。そのとき、画面の、特定の場所の画素値を出力して、数字で見る方法は、とても強力です。

あわせて読みたい