トゥーンシェーダーをやったので、似たようなものが無いか探してみました。
すると、NVIDIA の NVEffectsBrowser のサンプルに、membrane Lighting と言うのがあったのでこれを紹介します。
membrane とは、膜のことらしいです。さらに、扱っている膜が透明らしいので、ここでは、薄膜シェーダーと呼びます。
トゥーンシェーダーとの違いは、
| I | 視線の方向と、頂点の法線をつかってテクスチャー座標を算出する |
| II | 加算半透明で、モデルを表示する |
です。I は、トゥーンの場合には、光源の方向と法線の内積でした。 それは、照明と同じ効果を出します。 今回の場合は、正面を向いているかどうかを表現するので、輪郭抽出のような用途に使えます。
以下のファイルをダウンロードしてください。
今回は、PurpleGreenBright.bmp というファイルがエフェクト用に入っています。
今回のテクスチャーは、前回と異なり、内積の値が小さな場合に明るく、大きな場合に暗くなります。
結果的に、外側がぼおっとした表示になります。
また、トラでも表示してみました。
ちょっと地味でした。
バーテックスシェーダープログラムを見ます。
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);
}
後から、CG雑誌を読んだときに、レントゲン撮影をしたような画像をみました。
『新しく、シェーダーを書こうか』と思ったのですが、よく考えたら、薄膜シェーダーでいいかなぁと思いました。
の、内積の小さな成分を明いテクスチャーを使いました。
法線が横を向いている方が明るくなります。
これで輪郭が抽出でき、X線で照らされたように見えると思いました。
もっとポリゴンが多くないと、それらしくみえないですかねぇ。
今回は、トゥーンから、少しの変更で、幻想的な表示のレンダリングを行うことができました。
NVIDIA のサンプルには、他にもテクスチャーがありました。それらの一つの、
のテクスチャーを使えば、下のような表示になります。
金属的な表現か?
こんなきれいな画像がお手軽に表示できるなんて、すごい時代になりましたね。