【DirectX11】半透明が表示されない・奥の物体が消える原因とは?|ブレンド設定・描画順・深度書き込みを6通り再現して、画素の値で確かめる

【DirectX11】半透明が表示されない・奥の物体が消える原因とは?|ブレンド設定・描画順・深度書き込みを6通り再現して、画素の値で確かめる ゲーム制作

半透明の板を、3枚重ねて描きました。ところが、1枚目は透けているのに、奥の2枚が、手前の1枚の下に隠れて、消えてしまいました。別の設定では、そもそも、まったく透けません。エラーは、どこにも出ていません。

DirectX11の半透明は、「ブレンドステートを有効にする」だけでは、うまくいきません。描画する順番と、深度バッファへの書き込みが、結果を左右します。そこで、奥から青・緑・赤の、3枚の四角形(アルファは、すべて0.5)を使って、設定を変えながら6通り描き、画素の値で比べました。

画面は、出さずに、128×128の描画先に描いて、画素を読み戻しています。検証は、MSVC 19.44(x64)と、Direct3D 11(ハードウェアドライバー)で行いました。

半透明のブレンドとは?

半透明のブレンドとは、

・これから描く色(ソース)と、すでに描かれている色(デスティネーション)を、アルファ値の割合で、混ぜる処理。標準的な設定は、SrcBlend = SRC_ALPHA、DestBlend = INV_SRC_ALPHA

です。「すでに描かれている色」と混ぜるので、どの順番で描くかで、結果が変わります。さらに、深度テストが、「手前にあるものを先に描いた」ことを理由に、あとから描く奥のものを、捨ててしまうことがあります。

実験の設定

3枚の四角形は、わずかに位置をずらして、中心で、3枚が重なるようにしました。奥は青(z = 0.7)、中は緑(z = 0.5)、手前は赤(z = 0.3)です。背景は、暗いグレーです。ブレンドの設定部分は、次のとおりです。

▼ブレンドステートの設定(bl_exp.cpp)
// ブレンド状態: 標準的な半透明(SrcAlpha, InvSrcAlpha)
D3D11_BLEND_DESC bd{};
bd.RenderTarget[0].BlendEnable = (cs == 0) ? FALSE : TRUE;
bd.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;  bd.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; bd.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
bd.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;   bd.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;     bd.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
bd.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
ID3D11BlendState* bs = nullptr; dev->CreateBlendState(&bd, &bs);
▼深度ステンシルステートの設定(bl_exp.cpp)
// 深度ステンシル状態
bool depthOn = (cs == 3 || cs == 4);
D3D11_DEPTH_STENCIL_DESC dsd{};
dsd.DepthEnable = depthOn ? TRUE : FALSE;
dsd.DepthWriteMask = depthOn ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
dsd.DepthFunc = D3D11_COMPARISON_LESS;
ID3D11DepthStencilState* dss = nullptr; dev->CreateDepthStencilState(&dsd, &dss);

画素は、3か所で測りました。3枚が重なる中心、奥(青)の1枚だけの領域、手前(赤)の1枚だけの領域です。

結果:6通りの画像

3枚の半透明の四角形を、ブレンド設定・描画順・深度書き込みを変えて描いた6通りの結果
①は不透明、②と⑤が正しい重なり、③は順番が逆、④は奥の2枚が欠け、⑥は透けていない
ケース0: ブレンド無効(アルファが無視される)
  3枚が重なる中心          RGB = (255,   0,   0)
  奥(青)の1枚だけの領域     RGB = (  0,   0, 255)
  手前(赤)の1枚だけの領域   RGB = (255,   0,   0)
ケース1: ブレンド有効 / 奥→手前の順 / 深度書き込みなし(正しい)
  3枚が重なる中心          RGB = (134,  70,  38)
  奥(青)の1枚だけの領域     RGB = ( 25,  25, 153)
  手前(赤)の1枚だけの領域   RGB = (153,  25,  25)
ケース2: ブレンド有効 / 手前→奥の順(描画順が逆)
  3枚が重なる中心          RGB = ( 38,  70, 134)
  奥(青)の1枚だけの領域     RGB = ( 25,  25, 153)
  手前(赤)の1枚だけの領域   RGB = (153,  25,  25)
ケース3: ブレンド有効 / 手前→奥の順 / 深度テスト・書き込みあり
  3枚が重なる中心          RGB = (153,  25,  25)
  奥(青)の1枚だけの領域     RGB = ( 25,  25, 153)
  手前(赤)の1枚だけの領域   RGB = (153,  25,  25)
ケース4: ブレンド有効 / 奥→手前の順 / 深度テスト・書き込みあり
  3枚が重なる中心          RGB = (134,  70,  38)
  奥(青)の1枚だけの領域     RGB = ( 25,  25, 153)
  手前(赤)の1枚だけの領域   RGB = (153,  25,  25)
ケース5: ブレンド有効 / ピクセルシェーダーのアルファが1.0のまま
  3枚が重なる中心          RGB = (255,   0,   0)
  奥(青)の1枚だけの領域     RGB = (  0,   0, 255)
  手前(赤)の1枚だけの領域   RGB = (255,   0,   0)
設定中心の色(RGB)見た目
①ブレンド無効(255, 0, 0)不透明。重なりが見えない
②ブレンド有効・奥→手前・深度書き込みなし(134, 70, 38)正しい重なり
③ブレンド有効・手前→奥・深度なし(38, 70, 134)色が、青寄りに(順番が逆)
④ブレンド有効・手前→奥・深度書き込みあり(153, 25, 25)奥の2枚が、手前に隠れて消える
⑤ブレンド有効・奥→手前・深度書き込みあり(134, 70, 38)正しい(②と同じ)
⑥ブレンド有効・ピクセルシェーダーのアルファが1.0(255, 0, 0)不透明のまま
ブレンド有効の②〜⑤では、奥の1枚だけの領域は、いずれも(25, 25, 153)(青が背景と混ざった色)。重なりの部分だけで、結果が変わった

1つずつ見ていく

①ブレンド無効:アルファが、無視される

ブレンドを無効(BlendEnable = FALSE)にすると、ピクセルシェーダーが出した色が、そのまま書き込まれます。中心は、手前の赤(255, 0, 0)になりました。アルファ値を、いくら0.5にしても、透けません。

②・⑤正しい重なり:奥から手前の順で描く

奥(青)→中(緑)→手前(赤)の順に描くと、中心は(134, 70, 38)になりました。青と緑が、赤の下に、ほんのり透けて見える色です。深度テスト・書き込みが有効でも(⑤)、奥から描けば、同じ結果になりました。

③描画順が逆:色が変わる

手前(赤)→中(緑)→奥(青)の順で描くと、中心は(38, 70, 134)になり、青寄りの色になりました。ブレンドは、「あとから描いたものが、上に重なる」計算なので、最後に描いた青が、いちばん上に来たかのように混ざったのです。奥の1枚だけの領域や、手前の1枚だけの領域は、変わらないので、重なる部分だけで、色がおかしくなります。

④【重要】深度書き込みが有効で、手前から描く:奥の物体が、消える

深度テストと深度書き込みを有効にして、手前から描くと、中心は(153, 25, 25)、つまり、手前の赤と、背景だけが混ざった色になりました。奥の緑と青は、画像のとおり、赤に隠れる部分で、まるごと消えています。

原因は、手前の赤が、先に深度バッファへ、自分の深度を書き込むためです。あとから描く緑と青は、「赤より奥にある」と判定されて、深度テストで捨てられます。半透明は、本来、奥のものが透けて見えるはずですが、深度バッファは、その透けを知らないので、不透明のものと同じ扱いで、捨ててしまいます。

⑥アルファが、1.0のまま:透けない

ブレンドを有効にしていても、ピクセルシェーダーが返すアルファが1.0だと、中心の色は(255, 0, 0)で、不透明のままです。ブレンドの設定ではなく、シェーダーの出力側が、原因のことがあります。テクスチャのアルファを使うつもりが、定数の1.0を返していた、というミスが、典型です。

直し方

  • ブレンドステートを有効にする(BlendEnable = TRUE、SRC_ALPHAとINV_SRC_ALPHA)。OMSetBlendStateで、描く前にセットする
  • 半透明は、不透明のあとに、奥から手前の順で描く。カメラからの距離で、並べ替える(ソート)
  • 半透明を描くときは、深度書き込みを切る(DepthWriteMask = ZERO)。今回の実験では、深度テストと書き込みを、両方切った状態(②)で確認した。深度テストだけを残す組み合わせは、試していない
  • ピクセルシェーダーが、アルファを返しているかを確認する

今回の実験では、深度書き込みなしで、奥から手前の順(②)が、正しい結果でした。④のように、深度書き込みがあっても、描画順が正しければ(⑤)、結果は、同じになりました。ただし、半透明の板が、互いに交差している場合などは、板単位の並べ替えでは、足りないことがあります(今回は、試していません)。

半透明の描画の、よくある失敗例と対処法

①ブレンドを有効にしたのに、透けない

OMSetBlendStateを、呼んでいるかを確認します。ブレンドステートを作っただけでは、使われません。次に、ピクセルシェーダーの、出力のアルファ値を見ます(実験⑥)。

②奥の半透明が、消える

実験④のとおり、深度書き込みが原因です。半透明の描画では、深度書き込みを切ります。

③色が、不自然に混ざる

描画順が、逆になっていないかを、確認します(実験③)。複数の半透明が、重なる場面では、カメラからの距離で、並べ替えるのが基本です。

④不透明と半透明を、混ぜて描く

描く順番は、先に不透明、あとに半透明です。半透明を先に描くと、背景のはずの不透明なものが、あとから、半透明を上書きして、透けが消えます。

注意点

  • 半透明は、ブレンドステート・描画順・深度書き込みの3つが、そろって初めて、正しくなる
  • 深度書き込みありで、手前から描くと、奥の2枚が消えた(中心の色が、手前と背景だけになった)
  • 描画順が逆だと、重なる部分だけ、色が変わる((134, 70, 38)が(38, 70, 134)に)
  • ピクセルシェーダーのアルファが1.0なら、ブレンドを有効にしても、透けない
  • 今回の検証は、MSVC 19.44、Direct3D 11、ハードウェアドライバー、128×128の描画先で行った。交差する半透明は、試していない

この記事のポイント

  • 半透明は、不透明のあとに、奥から手前の順で描く
  • 半透明では、深度書き込みを切る。残すと、奥のものが消える
  • 透けないときは、OMSetBlendStateと、シェーダーのアルファ出力を、確認する

半透明がおかしいとき、画面を眺めているだけだと、原因が、ブレンドなのか、順番なのか、深度なのか、分かりません。中心と、1枚だけの領域の画素値を、並べて見てみると、どこで差が出ているのかが、すぐに分かります。

あわせて読みたい