• Direct3D 11のDynamic Shader Linkageを試す

    2011年08月25日 20時53分
    Direct3D 11で~とかしれっと書き続けて来ましたが、
    11の機能はほとんど使ってなくてインチキくさいので今回は11の機能についてです。


    プログラマブルシェーダは自由度が高く色々なことが出来ますので、
    処理がどんどん増えていきます。

    縦にコードが長くなるのはそれほど困らないのですが、
    場合分けで横に増えていくと困りものです。

    例えば、ポリゴンをそのまま表示する場合とテクスチャを張る場合。
    ライトを当てる場合と当てない場合。
    これだけでもう2 × 2 = 4のシェーダが必要になります。

    他にも描画要素はたくさんあるので、
    掛け算でシェーダ数が増えて行ってとんでもない数になります。



    「100万パワー+100万パワーで200万パワー!」
    「いつもの2倍のジャンプが加わって200×2の400万パワー!」
    「そしていつもの3倍の回転を加えれば400×3の・・・!
    バッファローマン!お前を上回る1200万パワーだーーーーっ!」




    みたいなもんです。

    if文を使った条件分岐でそれなりに対応は出来そうですが、難しい時もあるでしょう。

    そういった状況のためにDirect3D 11からはDynamic Shader Linkageが追加されました。
    OOPでおなじみのinterfaceとclassの概念をHLSLで使うことが出来ます。



    先ほども出た、ポリゴンそのままとテクスチャ有りの場合分けを例にします。


    今回のソース

    HSLS側の準備

    どちらの場合でも色データがとれればいいので、interfaceは次のように定義しました。
    1interface BaseColor
    2{
    3    float4    GetColor(float2 uv);
    4};


    このinterfaceに対するclassとして次のように実装しました。
    1class RawPolygonColor : BaseColor
    2{
    3    float4    GetColor(float2 uv)
    4    {
    5        // 固定で白を返す
    6        return    float4(1, 1, 1, alpha);
    7    }
    8};
    9
    10class TexturedColor : BaseColor
    11{
    12    float4    GetColor(float2 uv)
    13    {
    14        // 与えられたuv座標でテクスチャの色を返す
    15        float4 color = tex.Sample(wrapSampler, uv);
    16        color.a *- alpha;
    17        return color;
    18    }
    19};


    interfaceのインスタンス変数も用意します。
    1BaseColor baseColor;


    インスタンス変数はこのように使えます。
    1float4 oc = baseColor.GetColor(input.uv);


    Direct3D側の準備


    Direct3Dから、HLSLのインスタンス変数に実装クラスのインスタンスをセットします。


    ID3D11Device::CreateClassLinkageを使って、HLSL上interfaceのDirect3Dインターフェース(ややこしい)を作成します。
    1device_.CreateClassLinkage(&pixelShaderClassLinkage_)



    作成したインターフェースはID3D11Device::CreateXXXShaderに渡して関連付けます。
    1device_.CreatePixelShader(compiled.GetBufferPointer(), compiled.GetBufferSize(), pixelShaderClassLinkage_, &pixelShader_)


    この後、リフレクションを使って、interfaceが何番目のスロットに対応するかチェックするのですが、
    今回はinterfaceのインスタンスが1個のため確実に0番になるので省略します。


    classのインスタンスはID3D11ClassLinkage::CreateClassInstanceを使って、HLSL上のクラス名から取得します。
    1pixelShaderClassLinkage_.CreateClassInstance("RawPolygonColor", 0, 0, 0, 0, &pixelShaderRawPolygonColorClass_)
    2pixelShaderClassLinkage_.CreateClassInstance("TexturedColor", 0, 0, 0, 0, &pixelShaderTexturedColorClass_)

    (class内にメンバ変数がある場合は、GetClassInstanceを使います)



    あとは表示条件に従ってinterfaceのインスタンス変数にclassインスタンスのどちらかをセットして使います。
    1immediateContext_.PSSetShader(pixelShader_, &pixelShaderRawPolygonColorClass_, 1);
  • 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値テクスチャを生成するときと、通常のレンダリングをするとき、
    どちらもパイプラインの設定はほとんど同じになるので、エフェクトファイルを使うべきかなぁとも思います。
    手動だとすごく面倒です。
  • D言語でDirect3D 11を使う時のデバッグ出力参照

    2011年07月18日 13時12分
    正確には、Dに限らずVisualStudioのようなIDEが無くてもデバッグ出力を見る方法です。


    まずはアプリケーション側のデバッグ出力有効化です。
    この作業は表示側がIDEでも他のでも共通です。


    デバッグ出力を有効にするには、D3D11CreateDeviceD3D11CreateDeviceAndSwapChainを呼び出すときに
    デバッグフラグを指定してやります。

    具体的には、FlagsにD3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_DEBUGを指定します。


    このフラグを有効にするためには、D3D11SDKLayers.dllが必要になるので、
    DirectXのSDKをインストールする必要があります。


    詳しくは公式を参照下さい。
    ソフトウェアレイヤー



    これでアプリケーション側は出力するようになったので、
    何かしらの方法でキャプチャすれば見ることが出来ます。

    DirectXのデバッグ出力は、
    標準入力等ではなくWin32のOutputDebugStringという仕組みを使っているようです。

    デバッグ用の仕組みなので、当然キャプチャするアプリが準備されています。
    DebugView for Windows


    使い方はDebugViewを起動した状態で、
    OutputDebugStringを使うアプリを起動するだけです。
  • 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使用



    大体動いた気がするので、現時点のソースを置いておきます。参考になるかな。