半透明の板を、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通りの画像

ケース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つずつ見ていく
①ブレンド無効:アルファが、無視される
ブレンドを無効(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枚だけの領域の画素値を、並べて見てみると、どこで差が出ているのかが、すぐに分かります。

