いまさらDirect3D11入門

初めてグラフィックスAPIを触る人に向けて

Zバッファと深度ステンシルステート

前回のパートで出力結合ステージのレンダーターゲットとブレンドステートについて見てきました。 今回は残りのZバッファ(英訳:Z-Buffer)と深度ステンシルステート(英訳:DepthStencilState)について見ていきます。 この2つは隠線消去という3Dレンダリングおいて重要な要素の制御を行うものになります。 ピクセルシェーダの出力を使用するかしないかはこの2つで決定します。

ドキュメント: 出力結合ステージ(日本語) Output-Merger Stage(英語)

概要

このパートではピクセルシェーダの後にその出力値を使うか使わないかをテストする深度テストとステンシルテストについて見ていきます。 この2つのテストはID3D11DepthStencilStateで制御します。 対応しているプロジェクトはPart07_ZBufferAndDepthStencilStateになります。

  1. 深度ステンシルビュー
  2. ID3D11DepthStencilState
    • 深度テストの設定
    • ステンシルテストの設定
  3. まとめ
  4. 補足
    • Early Z
    • ID3D11DepthStencilViewのクリア関数

深度ステンシルビュー

深度テストとステンシルテストではテスト結果をテクスチャに保存することができます。 保存するときは深度ステンシルビューを作成する必要があります。

作成

// 深度ステンシルバッファの作成
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = this->width();
desc.Height = this->height();
//深度値に24bitのfloat型をステンシル値に8ビットのuintを確保している
desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
desc.SampleDesc.Count = 1;
desc.MipLevels = 1;
desc.ArraySize = 1;
auto hr = this->mpDevice->CreateTexture2D(&desc, nullptr, this->mpDepthStencil.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("深度ステンシルバッファの作成に失敗");
}
D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
dsvDesc.Format = desc.Format;
dsvDesc.Texture2D.MipSlice = 0;
hr = this->mpDevice->CreateDepthStencilView(this->mpDepthStencil.Get(), &dsvDesc, this->mpDepthStencilDSV.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("深度ステンシルビュー作成に失敗");
}

上のID3D11Device::CreateDepthStencilViewで深度ステンシルビューを作成しています。 作り方はレンダーターゲットと同じく今まで出てきたビューとよく似ています。

ドキュメント:
ID3D11Device::CreateDepthStencilView (日本語) (英語)
D3D11_DEPTH_STENCIL_VIEW_DESC (日本語) (英語)

深度ステンシルビューを作成するにはリソース作成時にBindFlagsD3D11_BIND_DEPTH_STENCILを指定する必要があります。

リソースのFormatにはZバッファを表すD、ステンシル成分を表すSを持つものを使用してください。 Zバッファとして使用したリソースをシェーダリソースビュー等ほかの用途で使用したい場合は、リソース作成時にTYPELESSを持つものを指定し、ビュー作成時フォーマットを改めて指定する必要があります。

設定

設定は前回レンダーターゲットの設定した時に使ったID3D11DeviceContext::OMSetRenderTargetsで行います。
ドキュメント:ID3D11DeviceContext::OMSetRenderTargets (日本語) (英語)

// Scene::onRender関数の一部改変したもの
//アウトプットマージャステージ
std::array<ID3D11RenderTargetView*, 1> ppRTVs = { {
  this->mpBackBufferRTV.Get(),
} };
this->mpImmediateContext->OMSetRenderTargets(static_cast<UINT>(ppRTVs.size()), ppRTVs.data(), this->mpDepthStencilDSV.Get());

ID3D11DepthStencilState

深度テストとステンシルテストはID3D11DepthStencilStateで同時に設定します。 この2つのテストの順序は深度テストから実行され、その後にステンシルテストが行われます。

設定

まず、グラフィックスパイプラインへの設定はID3D11DeviceContext::OMSetDepthStencilStateで行います。

ドキュメント:ID3D11DeviceContext::OMSetDepthStencilState (日本語) (英語)

// Scene::onRender関数の一部
this->mpImmediateContext->OMSetDepthStencilState(this->mpDSStencilTest.Get(), 0);

作成

次にID3D11DepthStencilStateの作成はID3D11Device::CreateDepthStencilStateで行います。 ドキュメント:ID3D11DeviceContext::CreateDepthStencilState (日本語) (英語)

// Scene::onInit関数の一部
//深度テストとステンシルテストを別々で作っていますが、もちろん同時に行うこともできます。
D3D11_DEPTH_STENCIL_DESC desc = {};
//深度テストを行う深度ステンシルステートの作成
desc.DepthEnable = true;
desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
desc.StencilEnable = false;
auto hr = this->mpDevice->CreateDepthStencilState(&desc, this->mpDSDepthTest.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("深度テスト用のステート作成に失敗");
}

//ステンシルテストを行う深度ステンシルステートの作成
desc.DepthEnable = false;
desc.StencilEnable = true;
desc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
desc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_INCR;
desc.FrontFace.StencilFunc = D3D11_COMPARISON_GREATER_EQUAL;

desc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
desc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
desc.BackFace.StencilPassOp = D3D11_STENCIL_OP_INCR;
desc.BackFace.StencilFunc = D3D11_COMPARISON_GREATER_EQUAL;

hr = this->mpDevice->CreateDepthStencilState(&desc, this->mpDSStencilTest.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("ステンシルテスト用のステート作成に失敗");
}

各テストの設定はD3D11_DEPTH_STENCIL_DESCで指定します。各テストの設定については分けてみていきます。
ドキュメント:D3D11_DEPTH_STENCIL_DESC (日本語) (英語)

深度テストの設定

まず、深度テストの設定は以下のメンバで行います。

D3D11_DEPTH_STENCIL_DESC 深度テストに関係するメンバ

  • DepthEnable

    深度テストを行うかを指定するフラグになります。 trueでテストを行います。

  • DepthFunc

    D3D11_COMPARISON_FUNCを使い、深度テストを行う際の元データと上書きするデータ同士での比較方法を指定します。
    ドキュメント:D3D11_COMPARISON_FUNC (日本語) (英語)

  • DepthWriteMask

    D3D11_DEPTH_WRITE_MASKを使用して深度データを書き込む際のマスクを設定します。 D3D11_DEPTH_WRITE_MASKは今のところ書き込むか書き込まないかの2種類用意されています。
    ドキュメント:D3D11_DEPTH_WRITE_MASK (日本語) (英語)

ステンシルテストの設定

次に、ステンシルテストの設定は以下のメンバで行います。

D3D11_DEPTH_STENCIL_DESC ステンシルテストに関係するメンバ

  • StencilEnable

    ステンシルテストを行うかを指定するフラグになります。 trueでテストを行います。

  • StencilReadMask, StencilWriteMask

    読み込む部分または書き込む部分を指定するマスク値になります

  • FrontFace, BackFace

    深度テストとステンシルテストの結果でどのような処理を行うかを設定するものになります。 表面と裏面別で設定でき、D3D11_DEPTH_STENCILOP_DESCで指定します。
    ドキュメント:D3D11_DEPTH_STENCILOP_DESC (日本語) (英語)

    D3D11_DEPTH_STENCILOP_DESCのメンバ

    • StencilFunc

      深度テストと同じくD3D11_COMPARISON_FUNCを使ってステンシルテストの元データと上書きするデータの比較方法を指定します。

    • StencilPassOp

      D3D11_STENCIL_OPを使って、ステンシルテストに成功した時に実行する処理を指定します。
      ドキュメント:D3D11_STENCIL_OP (日本語) (英語)

    • StencilDepthFailOp

      StencilPassOpと同じく、深度テストに失敗した時の処理を指定します。

    • StencilFailOp

      StencilPassOpと同じく、ステンシルテストに失敗した時の処理を指定します。

まとめ

このパートでは出力結合ステージの深度テストとステンシルテストについて見てきました。 深度テストを使うことでカメラから近いものだけが描画できるようになったり、ステンシルテストで好きな部分だけを描画することができるようになります。

重たいピクセルシェーダや上書きされるピクセルが多い場合はカメラから一番近いものだけに対してだけピクセルシェーダを実行できるよう、事前にZバッファだけに書き込むというZプリパス(英訳:Z Pre-Pass)という手法も存在します。

また、深度テストはその性質から前パートで説明したブレンディングと相性が悪いものになります。 ガラスといった半透明なものや眩しさを表現するために加算合成を行ったパーティクルの描画を行う際は深度テストを切らないと正しい結果が得られません。

以上から、行いたい処理によって深度ステンシルステートとブレンドステートを切り替える必要がありますので注意してください。

補足

Early Z

最近のGPUにはピクセルシェーダを実行する前に深度テストを行うEarly Zと呼ばれる機能が存在します。

// PSEarlyZ.hlsl
[earlydepthstencil]
void main(float4 pos : SV_POSITION, float4 color : COLOR0, out float4 outColor : SV_Target0)
{
  outColor = color;
}
    
ピクセルシェーダのエントリポイント前にearlydepthstencil属性を指定することでEarly Zの機能を使うことを宣言します。
ドキュメント:earlydepthstencil (日本語) (英語)
この機能を使用することで不要なピクセルシェーダの実行を減らすことができ、実行速度が速くできる様になります。 制限事項として、この機能を使った場合はZバッファへの書き込みはできないため、深度バッファに書き込まない深度ステンシルステートを作成する必要がありますので注意してください。

また、効率的な深度テストのやり方については以下のリンクで詳しく説明されています。 GPUによっては内部でZバッファの階層構造を生成し、深度テストの効率化を図るものも存在しているみたいです。 そのようなこともリンク先にかかれているので、是非御覧ください。
Depth in-depth(英語)

ID3D11DepthStencilViewのクリア関数

深度ステンシルビューを表すID3D11DepthStencilViewにも専用のクリア関数が用意されています。
ドキュメント: ID3D11DeviceContext::ClearDepthStencilView 日本語 英語

<前 トップ 次>