薄膜シェーダー


~NVIDIAのサンプルの奴です~




■はじめに

トゥーンシェーダーをやったので、似たようなものが無いか探してみました。 すると、NVIDIA の NVEffectsBrowser のサンプルに、membrane Lighting と言うのがあったのでこれを紹介します。
membrane とは、膜のことらしいです。さらに、扱っている膜が透明らしいので、ここでは、薄膜シェーダーと呼びます。

トゥーンシェーダーとの違いは、

I視線の方向と、頂点の法線をつかってテクスチャー座標を算出する
II加算半透明で、モデルを表示する

です。I は、トゥーンの場合には、光源の方向と法線の内積でした。 それは、照明と同じ効果を出します。 今回の場合は、正面を向いているかどうかを表現するので、輪郭抽出のような用途に使えます。

以下のファイルをダウンロードしてください。

今回は、PurpleGreenBright.bmp というファイルがエフェクト用に入っています。
今回のテクスチャーは、前回と異なり、内積の値が小さな場合に明るく、大きな場合に暗くなります。
結果的に、外側がぼおっとした表示になります。

また、トラでも表示してみました。

ちょっと地味でした。

■バーテックスシェーダー(vs.vsh)

バーテックスシェーダープログラムを見ます。

vs.1.0

; v0	頂点の座標値
; v3	法線ベクトル
; v8	テクスチャ座標1

;
; c0-3   -- world + ビュー + 透視変換行列
; c4-7   -- world 行列
; c8-11  -- world の逆転置行列
; c14    -- カメラの位置
; c15    -- メッシュの色

vs.1.0

;座標変換
dp4 oPos.x, v0, c0
dp4 oPos.y, v0, c1
dp4 oPos.z, v0, c2
dp4 oPos.w, v0, c3

;法線の変換
dp3 r0.x, v3, c8
dp3 r0.y, v3, c9
dp3 r0.z, v3, c10

;法線の正規化
dp3 r0.w, r0, r0
rsq r0.w, r0.w
mul r0, r0, r0.w

;ワールド座標系での頂点の位置を計算する
dp4 r1.x, v0, c4
dp4 r1.y, v0, c5
dp4 r1.z, v0, c6
dp4 r1.w, v0, c7

;カメラへの向きeを計算する
add r2, c14, -r1

;e の正規化
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w

; e dot n (テクスチャー座標の算出)
dp3 r1, r2, r0
mov oT0.x, r1

; メッシュのテクスチャー
mov oT1, v8

; 頂点色((1.0, 1.0, 1.0, 1.0) を入れます)
mov oD0, c15

今回、視線への方向ベクトル e を、新しく導入しました。 視線(カメラ)の位置は、ワールド座標での位置なので、各頂点をワールド座標の行列で変換します。 その後に、カメラの位置との差を取って、カメラの方向を算出します。
後は、法線との内積をとって、法線がどれだけ正面を向いているかを計算し、その値をテクスチャーの座標にします。
後は、元のテクスチャーを張ります。
NVIDIA のサンプルでは、元のテクスチャーは張らなかったのですが、さびしかったので、こうしました。
さらに、NVIDIA のサンプルでは、テクスチャーを流していたのですが、切れ目が汚かったので、流すのをやめました。
そして、元の色は反映させずに白色 (1.0, 1.0, 1.0, 1.0) を入れました。 元の色で塗ると、その色がつよすぎるので、こうしました。

■初期化

では、初期化です。
トゥーンシェーダーとの違いは、加算合成を行うために、Zバッファを切っていることです。

HRESULT InitRender(LPDIRECT3DDEVICE8 lpD3DDEV)
{
    HRESULT hr;

    hr = LoadXFile("car.x", lpD3DDEV);
    if ( FAILED(hr) ) return hr;
 
    // 虹色テクスチャーの読み込み
    D3DXCreateTextureFromFileEx(lpD3DDEV, "PurpleGreenBright.bmp",0,0,0,0,D3DFMT_A8R8G8B8,
                                D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,
                                0, NULL, NULL, &pTexture);

    // バーテックスシェーダーを作成する
    ID3DXBuffer*    pshader;
    hr = D3DXAssembleShaderFromFile("vs.vsh", 0,NULL,&pshader,NULL);
    if ( FAILED(hr) ) return hr;
    hr = lpD3DDEV->CreateVertexShader( dwDecl, (DWORD*)pshader->GetBufferPointer(), &hVertexShader, 0 );
    RELEASE(pshader);
    if ( FAILED(hr) ) return hr;
    
    // レンダリングの状態の設定
    lpD3DDEV->SetRenderState( D3DRS_ZENABLE, FALSE);
    lpD3DDEV->SetRenderState( D3DRS_AMBIENT, 0xff808080);
    
    return S_OK;

■表示

では、描画部分です。
mView のカメラの位置 eye を SetVertexShaderConstant の 14、 定数 (1.0f, 1.0f, 1.0f, 1.0f) を 15 に入れました。
後は、加算合成になるように、α合成の設定をしました。 一応、使い終わったら、α合成をしないように、元に戻しておきます。

void Render(LPDIRECT3DDEVICE8 lpD3DDEV)
{
    if(NULL == pMeshVB) return;
    
    D3DXMATRIX mWorld, mView, mProj;

    D3DXMatrixRotationY( &mWorld, timeGetTime()/1000.0f );
    
    D3DXVECTOR3 eye, lookAt, up;
    eye.x    = 0.0f; eye.y      = 1.5f; eye.z    = 3.0f;
    lookAt.x = 0.0f; lookAt.y   = 0.0f; lookAt.z = 0.0f;
    up.x     = 0.0f; up.y       = 1.0f; up.z     = 0.0f;
    D3DXMatrixLookAtLH(&mView, &eye, &lookAt, &up);
    
    D3DXMatrixPerspectiveFovLH(&mProj
        ,60.0f*PI/180.0f                        // 視野角
        ,(float)WIDTH/(float)HEIGHT                // アスペクト比
        ,0.01f                                    // 最近接距離
        ,100.0f                                    // 最遠方距離
        );
    D3DXMATRIX  m = mWorld * mView * mProj;
    D3DXMatrixTranspose( &m ,  &m);
    lpD3DDEV->SetVertexShaderConstant(0,&m, 4);
    D3DXMatrixTranspose( &m ,  &mWorld);
    lpD3DDEV->SetVertexShaderConstant(4, &m, 4);
    D3DXMatrixInverse( &m,  NULL, &mWorld);
    lpD3DDEV->SetVertexShaderConstant(8, &m, 4);
    lpD3DDEV->SetVertexShaderConstant(14, &eye, 1);

    lpD3DDEV->SetVertexShader(hVertexShader);
    lpD3DDEV->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    lpD3DDEV->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
    lpD3DDEV->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    lpD3DDEV->SetTextureStageState(0,D3DTSS_COLOROP,    D3DTOP_MODULATE);
    lpD3DDEV->SetTextureStageState(0,D3DTSS_COLORARG1,    D3DTA_TEXTURE);
    lpD3DDEV->SetTextureStageState(0,D3DTSS_COLORARG2,    D3DTA_DIFFUSE);
    lpD3DDEV->SetTextureStageState(0,D3DTSS_MAGFILTER,    D3DTEXF_LINEAR);
    lpD3DDEV->SetTextureStageState(0,D3DTSS_MINFILTER,    D3DTEXF_LINEAR);
    lpD3DDEV->SetTextureStageState(0,D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
    lpD3DDEV->SetTexture(0,pTexture);
    lpD3DDEV->SetVertexShaderConstant(15,D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f),1);

    //メッシュの描画
    lpD3DDEV->SetStreamSource(0, pMeshVB, sizeof(D3D_CUSTOMVERTEX));
    lpD3DDEV->SetIndices(pMeshIndex,0);
    for(DWORD i=0;iSetTexture(1,pMeshTextures[i]);
        lpD3DDEV->SetTextureStageState(1,D3DTSS_COLOROP,D3DTOP_MODULATE);
        lpD3DDEV->SetTextureStageState(1,D3DTSS_COLORARG1,D3DTA_TEXTURE);
        lpD3DDEV->SetTextureStageState(1,D3DTSS_COLORARG2,D3DTA_CURRENT);
        lpD3DDEV->SetTextureStageState(1,D3DTSS_MAGFILTER,D3DTEXF_LINEAR);
        lpD3DDEV->SetTextureStageState(1,D3DTSS_MINFILTER,D3DTEXF_LINEAR);

        lpD3DDEV->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 
                                        pSubsetTable[i].VertexStart,
                                        pSubsetTable[i].VertexCount,
                                        pSubsetTable[i].FaceStart * 3,
                                        pSubsetTable[i].FaceCount);
    }
    
    lpD3DDEV->SetTexture(0, NULL);
    lpD3DDEV->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
}

■X線レンダリング

後から、CG雑誌を読んだときに、レントゲン撮影をしたような画像をみました。
『新しく、シェーダーを書こうか』と思ったのですが、よく考えたら、薄膜シェーダーでいいかなぁと思いました。

の、内積の小さな成分を明いテクスチャーを使いました。
法線が横を向いている方が明るくなります。 これで輪郭が抽出でき、X線で照らされたように見えると思いました。

もっとポリゴンが多くないと、それらしくみえないですかねぇ。

■最後に

今回は、トゥーンから、少しの変更で、幻想的な表示のレンダリングを行うことができました。
NVIDIA のサンプルには、他にもテクスチャーがありました。それらの一つの、

のテクスチャーを使えば、下のような表示になります。

金属的な表現か?
こんなきれいな画像がお手軽に表示できるなんて、すごい時代になりましたね。





もどる

imagire@gmail.com