• Direct3D 11でトゥーンレンダリング

    2011年07月21日 23時51分
    MMDといえばトゥーンレンダリングだろうと思いますので実装します。

    例によって本家と全く同じ処理というわけではありませんのでご注意ください。


    MMDはトゥーン用テクスチャを10個持っています。
    そして、モデルデータにはそのインデックスが入っています。

    PMDファイルの内部構造についてはこちらのサイトが詳しいです。
    MMDのモデルデータ(PMD)形式 めも4 - 通りすがりの記憶

    拡張仕様でデフォルトのテクスチャ以外も使えるようですが、
    読み込み位置が遠いので今回は対応しません。


    よって、テクスチャを10個読み込んで、
    インデックスデータを読み込めば下準備完了です。



    トゥーンレンダリング自体も、単純な仕様を選べば難しいものではありません。

    今回はディレクショナルライトと、法線ベクトルの内積を取り、
    180度(光が強く当たる) ~ 90度以下(光が当たらない)の値を取ります。(-1 ~ 1の値になります)

    この値を元に、テクスチャ座標の0(光が強く当たる) ~ 1(光が当たらない)として使います。

    通常の拡散反射の計算ですと角度によってなめらかに光の強さが変わりますが、
    テクスチャから色を取る事によって段階的に光の強さが変わります。


    こんな感じのシェーダを書きました
    1float force = 1;
    2if (minDepth >= input.lpos.z) // 深度バッファシャドウの判定
    3{
    4    // 法線と光線の角度を内積として取得する
    5    force = dot(normalize(input.normal), normalize(directionalLightDirection));
    6
    7    // テクスチャ座標に変換する
    8    force = saturate(force + 1);
    9}
    10
    11// 与えられた値を元にtoonテクスチャから色を拾い、拡散反射光として扱う
    12float3 diffuse = toonTex.Sample(clampSampler, float2(0, force));



    実行するとこうなります。顔の影などが、ある点から表示されているのがわかります。
    実行結果
  • Direct3D 11で深度バッファシャドウ

    2011年07月18日 22時24分
    MMDのシェーダを見ていると、深度バッファシャドウが入っているようなので入れてみます。

    一口に深度バッファシャドウと言っても細かい実装方法が色々ありますので、
    厳密にはMMDの処理とは違うと思います。
    深度バッファシャドウの一例としてご覧下さい。


    今回のソース


    深度バッファシャドウを実現するために必要になるのが
    Z値を保持するテクスチャです。これを生成します。

    ポイントはD3D11_TEXTURE2D_DESC.Usageです。
    Render targetとするためにD3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET
    Shader resourceとするためにD3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCEを設定します。
    1D3D11_TEXTURE2D_DESC renderTextureDesc;
    2renderTextureDesc.Width = width;
    3renderTextureDesc.Height = height;
    4renderTextureDesc.MipLevels = 1;
    5renderTextureDesc.ArraySize = 1;
    6renderTextureDesc.Format = DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT;
    7renderTextureDesc.SampleDesc.Count = 1;
    8renderTextureDesc.SampleDesc.Quality = 0;
    9renderTextureDesc.Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT;
    10renderTextureDesc.BindFlags = D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET;
    11renderTextureDesc.CPUAccessFlags = 0;
    12if (FAILED(device.CreateTexture2D(&renderTextureDesc, null, &renderTarget_)))
    13{
    14    cleanup();
    15    throw new Error("Failed create depth stencil shadow texture");
    16}


    生成したテクスチャは、Z値を書き込む必要があるため、Render target viewを生成します。
    1if (FAILED(device.CreateRenderTargetView(renderTarget_, null, &renderTargetView_)))
    2{
    3    cleanup();
    4    throw new Error("Failed create depth stencil shadow texture view");
    5}



    また、Z値を参照する必要もあるため、Shader resource viewも生成します。
    1if (FAILED(device.CreateShaderResourceView(renderTarget_, null, &renderTargetResourceView_)))
    2{
    3    cleanup();
    4    throw new Error("Failed create depth stencil shadow texture resource view");
    5}





    このテクスチャにZ値を書き込むには、
    生成したRender target viewを使用してRender targetに設定します。
    1deviceContext.OMSetRenderTargets(1, &renderTargetView_, depthStencilView_);



    この状態でレンダリングします。

    ビュー変換は、ライトをカメラとして変換を行います。
    また、射影変換は正射影変換を使っています。

    そして、ピクセルシェーダの出力が色データではなくZ値になります。
    1return input.pos.z;



    この出力したZ値を色として見るとこうなります。
    深度データテクスチャ




    生成されたZ値テクスチャを、通常のレンダリングするときにピクセルシェーダにセットします。
    1immediateContext_.PSSetShaderResources(1, 1, &depthTextureView);



    頂点シェーダでは、Z値テクスチャを生成した時と同じワールドビュー射影変換も追加で行います。
    このXY座標は-1~1の値になっているので、0~1のテクスチャ座標に変換します。
    1float2 depthTexCoord;
    2depthTexCoord.x = (input.lpos.x + 1) * 0.5;
    3depthTexCoord.y = (1 - input.lpos.y) * 0.5;


    この座標を使ってテクスチャからデータを取ると、深度データが取れます。
    1float minDepth = depthTex.Sample(sam, depthTexCoord).r + 0.0005f;


    取得したテクスチャのZ値と、そのピクセルのZ値(Z値テクスチャを生成した時と同じワールドビュー射影変換した座標)を比較し、
    テクスチャのZ値より大きければ、ライトの処理をキャンセルします。(ライトが遮られた状態と判断出来る)


    結果、このように影が表示されます。
    深度バッファシャドウ適用



    パイプラインの入力と出力には同じデータがセット出来ません。
    次にRender targetにテクスチャをセットするために、ピクセルシェーダにセットしたテクスチャは外しておきます。
    1ID3D11ShaderResourceView nullView = null;
    2immediateContext_.PSSetShaderResources(1, 1, &nullView);


    Render tagetも同様に外す必要がありますが、
    今回の場合、バックバッファをRender targetにした時点で外れるので、明示的には外していません。


    Z値テクスチャを生成するときと、通常のレンダリングをするとき、
    どちらもパイプラインの設定はほとんど同じになるので、エフェクトファイルを使うべきかなぁとも思います。
    手動だとすごく面倒です。
  • Direct3D 11のアルファテスト

    2011年07月16日 12時25分
    前回の続き。

    ここまで来ればあとは簡単です。

    肌が表示されていた部分が透明になっているということは、
    透明ピクセルの時もZバッファへの書き込みがされているということです。

    アルファテストを入れて、透明ピクセルの時はZバッファに書き込まないようにします。


    ただ、Direct3D 11にはアルファテストという機能自体はありません。
    ピクセルシェーダで代わりに処理をしてあげる必要があります。



    通常、ピクセルシェーダは最後にピクセル情報を返します。

    例としてはこんな感じ
    1return float4(ambient + diffuse + specular, alpha);



    ここで、ピクセルシェーダでのみ使用出来る制御構文discardを使用します。
    1// アルファ値0の場合は表示しない
    2if (alpha == 0)
    3    discard;


    discardを使用するとピクセルを出力しないため、Zバッファへの書き込みもキャンセルすることが出来ます。


    アルファ値0の時にdiscardをするように処理すると、正常に表示されるようになりました。
    discard使用



    大体動いた気がするので、現時点のソースを置いておきます。参考になるかな。
  • Direct3D 11のアルファブレンド設定

    2011年07月15日 22時43分
    前回の続き。

    ニーソ破れる問題がどうしてもわからず悩み続けました。
    悩みすぎて夢の中でもコードを書いている状態です。

    しかし、それ程苦しんだ甲斐があり、閃きが突然訪れました。




    「ぱんつのテクスチャ透明にすれば、ぱんつ脱げるんじゃね!?」




    はやる気持ちを抑え、ボクはテクスチャを編集しました。
    幸いテクスチャは解りやすく、容易くぱんつの部分を透明にすることに成功しました。

    圧倒的な達成感に包まれながらボクはプログラムでモデルを表示しました。


    しかし、待っていたのは思わぬ結果でした。
    なんの変化もありません。


    その後、アルファ値の設定を変えたりしてみて気づいたのですが、
    アルファブレンドの処理がされていないようです。

    そこで今回はアルファブレンドの設定方法です。

    ステータス設定はいつもの手順です。
    1. D3D11_BLEND_DESCにアルファブレンドの設定を入れる
    2. D3D11_BLEND_DESCをID3D11Device.CreateBlendStateに渡してID3D11BlendStateを生成
    3. ID3D11DeviceContext.OMSetBlendStateにID3D11BlendStateをセット
    4. ID3D11BlendStateは終了時にReleaseを忘れずに


    ちょっとはまった点。

    RenderTargetのSrcBlendとDestBlendはD3D11_BLEND_SRC_COLORとかを入れたくるのですが、
    通常はD3D11_BLEND_SRC_ALPHA等を入れるのが正解です。
    これらはRGBのブレンド設定ですが、ブレンドの係数自体はアルファ値を使うためです。


    アルファ値を設定して描画です。
    アルファ値設定済み


    体と足が表示されない!

    つまり、元々このモデルデータは体と足が透明になるのが正しいわけです。
    ということはこの状態で服も描画すれば正常な表示なる!


    アルファ値設定済み全部描画


    ならなかった。
  • Direct3D 11のZFUNC設定

    2011年07月15日 00時02分
    前回の続き。

    さらに表示を直して行きます。

    現在はニーソが破れたような感じになっています。
    また、よく見ると上着も穴が空いています。


    理由がよく解らないので、原因を切り分けようと服と体を別々に表示してみました。

    服のみを表示
    服のみ

    体のみを表示
    体のみ


    どちらも正常に表示されています。
    ということは、Z値が正常でないため、前後関係がおかしくなっていると予想されます。

    他の部分についてはZ値が正常に働いているように見えるので
    ほんのわずかな誤差のせいではないでしょうか。

    というわけで、試しにZバッファの精度を落としてみました。

    現在は32bitにしているところを
    1textureDesc.Format = DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT;


    16bitにしてみます。
    1textureDesc.Format = DXGI_FORMAT.DXGI_FORMAT_D16_UNORM;


    微妙に破れた範囲が広がりました。


    あと思いつくのはZFUNCでしょうか。
    ちょっと比較式を変更してみましょう。

    Direct3D 11のZFUNCの設定ですが、例によってSetRenderStateを呼び出してはいオッケーでは無くなっています。
    次の手順が必要です。
    1. D3D11_DEPTH_STENCIL_DESCに深度ステンシルの設定を入れる
    2. D3D11_DEPTH_STENCIL_DESCをID3D11Device.CreateDepthStencilStateに渡してID3D11DepthStencilStateを生成
    3. ID3D11DeviceContext.OMSetDepthStencilStateにID3D11DepthStencilStateをセット
    4. ID3D11DepthStencilStateは終了時にReleaseを忘れずに

    また面倒ですが、このパターンにも慣れてきました。


    これで比較式を変更出来るので、色々な設定で試してみましたが全く改善されません。
    どうもZバッファの問題ではなさそうです。