DirectX 8.1:フォーカス


~誰かが君を見ている~




■はじめに

今回は、テクスチャーへのレンダリングの応用として、フォーカスの実装をします。
下の画面を見てください。画面中央だけくっきりとして、周りはボケています。
一部分だけ焦点が合うフォーカスの効果が現れています。

今回のソースは、次のものです。

内容は次のとおりになっています。

draw.h描画の各関数の定義。特に意味無いので出番無し。
draw.cppメインの描画部分。
blur.pshピクセルシェーダープログラム。ボケた画面を作る。
blur.vsh頂点シェーダープログラム。ボケた画面を作る。
focus.pshピクセルシェーダープログラム。ボケてる画面と普通の画面を合成する。
bg.cpp背景表示。半球ライティングの時のものと同じ
vs.vsh頂点シェーダープログラム。平行光源ライト。
load.cppシェーダーやテクスチャーのロード開放
load.hシェーダーやテクスチャーのロード開放
main.cpp描画に関係しないシステム的な部分。変更が無いので、出番無し。
main.h基本的な定数など。今回も画面サイズは512x512です。
mask.bmp (ボケたテクスチャーと普通のテクスチャーを合成するときの合成パラメータ)
tile.bmp (床デカール)
sky.bmp (空デカール)

あと、モデルとして、nsx.xと、実行ファイルの MyBase.exe 及び、 VC++ でコンパイルするためのプロジェクトファイル MyBase.dsw MyBase.dsp が入っています。

■方法

実際の方法ですが、以下の手順で行います。
先ず、テクスチャーに表示したい画像を描画します。

もとの絵

次に、そのテクスチャーを右、下、右下にそれぞれ少しずつずらした4枚の画像を合成してぼやけたテクスチャーを使います。

ボケた絵

最後に、mask.bmp を合成パラメータとして、それぞれの画像を合成します。

合成の計算に用いる式は、mask.bmp の色を t0 として、

出力する色 = (1-t0)*元の色 + t0*ぼやけた色

の線形の式です。

■ぼけたテクスチャーの作り方

では、ボケたテクスチャーの作り方ですが、今回はピクセルシェーダーが大活躍です。
先ず、ぼかすために頂点シェーダーで、テクスチャーのUV値をずらします。
GeForce3 では、テクスチャーステージが4つまでつかえるので、4枚の画像を合成しました。

0001: ; Blur.vsh
0002: 
0003: vs.1.1
0004: 
0005: mov oPos, v0
0006: 
0007: add oT0, v7, c20
0008: add oT1, v7, c21
0009: add oT2, v7, c22
0010: add oT3, v7, c23

頂点座標は、あらかじめ透視変換されたものを用います。
c20, c21, c22, c23 には、ずらす量を入れます。実際には、

c20 = (    0,     0,    0,    0)
c21 = (    0, inv_h,    0,    0)
c22 = (inv_w, inv_h,    0,    0)
c23 = (inv_w,     0,    0,    0)

を入れました。
ここで、inv_w、inv_h は、(UV値が0.0fから1.0fに対応して)画面のサイズに反比例する量です。

それから、ピクセルシェーダーで画像を合成します。
線形合成のための命令 lrp があるので、lrp を3回使って、4つの画像の平均を取ります。

0001: ; blur.psh
0002: ;
0003: ;            0 0 0
0004: ; 周りの色を 0 1 1 の平均を取る
0005: ;            0 1 1
0006: ps.1.1
0007: 
0008: def c0, 0.5f, 0.5f, 0.5f, 0.5f
0009: 
0010: ; テクスチャーの色を引っ張ってくる
0011: tex t0      ; 0:0 0 0  1:0 0 0  2:0 0 0  3:0 0 0  
0012: tex t1      ;   0 1 0    0 0 0    0 0 0    0 0 1  
0013: tex t2      ;   0 0 0    0 1 0    0 0 1    0 0 0  
0014: tex t3
0015: 
0016: ; r0 = 0.5*(0.5*(t0+t1)+0.5*(t2+t3))) = (t0+t1+t2+t3)/4
0017: ; 色の平均を取ってくる
0018: lrp r0, c0, t0, t1
0019: lrp r1, c0, t2, t3
0020: lrp r0, c0, r0, r1

実際のボケた画像を得るために、以上の合成を6回行いました。

■2枚の画像の合成

ボケた画像と、普通の画像の合成ですが、ピクセルシェーダーで簡単に実現できます。
但し、lrp r0, t0, t1, t2 は、入力レジスタにテクスチャーを2つまでしか指定できなかったので、 mov 命令を使って一次退避してから、合成しています。

0001: ; focus.psh
0002: ;
0003: ; t0 を合成パラメータとして、t1, t2 を合成する
0004: ;  r0 = (1-t0)*t2 + t0*t1
0005: 
0006: ps.1.1
0007: 
0008: ; テクスチャーの色を引っ張ってくる
0009: tex t0      ; ブレンド用テクスチャー
0010: tex t1      ; 元テクスチャー
0011: tex t2      ; ボケテクスチャー
0012: 
0013: mov r0, t0  ; GeForce3では、lrpのソースレジスタtnが2つしか使えないので、一時r0レジスタにコピー
0014: lrp r0, r0, t1, t2

■ソース

さて、それ以外のセットアップの部分です。draw.cpp を見ます。
普通の部分ははしょります。

今回の為に追加するオブジェクトは、次のとおりです。

0029: // ぼかした絵を作るためのオブジェクト
0030: LPDIRECT3DSURFACE8      pBackbuffer = NULL;         // 通常の描画領域を一次退避する
0031: LPDIRECT3DTEXTURE8      pMaskTexture = NULL;        // マスク用テクスチャー(mask.bmp)
0032: DWORD                   hBlurVertexShader=~0;       // blur.vsh
0033: DWORD                   hBlurPixelShader=~0;        // blur.psh
0034: DWORD                   hFocusPixelShader=~0;       // focus.psh
0037: 
0038: const int nBlurTex = 4;// 0:元絵、1~3:数字が大きくなるにつれ、ボケた絵になる
0039: LPDIRECT3DTEXTURE8      pTexture[nBlurTex];
0040: LPDIRECT3DSURFACE8      pTextureSurface[nBlurTex];
0041: 
0042: const int nTempTex = 2;// pTexture[i] を作る途中で使う
0043: LPDIRECT3DTEXTURE8      pTmpTexture[nTempTex];
0044: LPDIRECT3DSURFACE8      pTmpSurface[nTempTex];

pTexture[1~3] がボケた絵ですが、それ以外にワークとして、pTmpTexture[]を途中にボケた絵として使います。
これは、次回を見据えてのことなので、途中のpTexture[1],pTexture[2]は作らないで、pTmpTexture[]の回数を多くしても良いです。

画面全体にテクスチャーを張って合成するのですが、そのために、次の頂点、インデックスバッファと、FVF 頂点バッファを用います。
座標とテクスチャーだけの簡単なものです。

0035: LPDIRECT3DVERTEXBUFFER8 pBlurVB = NULL;             // 画面全体にテクスチャーを張るための頂点バッファ
0036: LPDIRECT3DINDEXBUFFER8  pBlurIB = NULL;             // 同、インデックスバッファ

0046: typedef struct {
0047:     float x,y,z;
0048:     float tu,tv;
0049: }D3D_BLUR_VERTEX;
0050: #define D3DFVF_BLUR_VERTEX      (D3DFVF_XYZ | D3DFVF_TEX1)
0051: 
0052: DWORD dwBlurDecl[] = {
0053:     D3DVSD_STREAM(0),
0054:     D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),          //D3DVSDE_POSITION,  0
0055:     D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ),          //D3DVSDE_TEXCOORD0, 7  
0056:     D3DVSD_END()
0057: };

では、初期化です。
最初に全画面描画用の頂点バッファを作成します。
次の黄色の部分が前回やったレンダリング用テクスチャー及びサーフェスの設定です。
pTexture[] と pTmpTexture[] の2回に分かれているだけで、同じ事をしています。
最後にシェーダプログラムをロードして、ずれの定数レジスタを設定します。

0175: //-----------------------------------------------------------------------------
0176: // Name: InitBlurTexture()
0177: // Desc: ボケたテクスチャー用の下準備
0178: //-----------------------------------------------------------------------------
0179: HRESULT InitBlurTexture(LPDIRECT3DDEVICE8 lpD3DDev)
0180: {
0181:     HRESULT hr;
0182:     DWORD i;
0183:     
0184:     // 頂点バッファの作成 
0185:     D3D_BLUR_VERTEX *pBlurDest;
0186:     WORD *pIndex;
0187:     lpD3DDev->CreateVertexBuffer( 4 * sizeof(D3D_BLUR_VERTEX),
0188:                                 D3DUSAGE_WRITEONLY, D3DFVF_BLUR_VERTEX, D3DPOOL_MANAGED,
0189:                                 &pBlurVB );
0190:     // 頂点をセットアップ
0191:     pBlurVB->Lock ( 0, 0, (BYTE**)&pBlurDest, 0 );
0192:     for (i = 0; i < 4; i++) {
0193:         pBlurDest->x   = (i == 0 || i == 1)?-1:(float)1;
0194:         pBlurDest->y   = (i == 0 || i == 2)?-1:(float)1;
0195:         pBlurDest->z   = 0.0f;
0196:         pBlurDest->tu = (i == 2 || i == 3)?1:(float)0;
0197:         pBlurDest->tv = (i == 0 || i == 2)?1:(float)0;
0198:         pBlurDest++;
0199:     }       
0200:     pBlurVB->Unlock ();
0201:     // インデックスをセットアップ
0202:     lpD3DDev->CreateIndexBuffer( 6 * sizeof(WORD),
0203:                                0,
0204:                                D3DFMT_INDEX16, D3DPOOL_MANAGED,
0205:                                &pBlurIB );
0206:     pBlurIB->Lock ( 0, 0, (BYTE**)&pIndex, 0 );
0207:     pIndex[0] = 0;  pIndex[1] = 1;  pIndex[2] = 2;
0208:     pIndex[3] = 1;  pIndex[4] = 3;  pIndex[5] = 2;
0209:     pBlurIB->Unlock ();
0210: 
0211:     // 描画用テクスチャーを用意する
0212:     D3DSURFACE_DESC Desc;
0213:     LPDIRECT3DSURFACE8 lpZbuffer = NULL;
0214:     if( FAILED(hr = lpD3DDev->GetRenderTarget(&pBackbuffer))) return hr;
0215:     if( FAILED(hr = pBackbuffer->GetDesc( &Desc ))) return hr;
0216: 
0217:     // 深度バッファのサーフェスを確保する
0218:     if( FAILED(hr = lpD3DDev->GetDepthStencilSurface( &lpZbuffer ))) return hr;
0219:     
0220:     for(i = 0; i < nBlurTex; i ++){
0221:         // テクスチャーの生成
0222:         if( FAILED(hr = lpD3DDev->CreateTexture(Desc.Width, Desc.Height, 1
0223:                                 , D3DUSAGE_RENDERTARGET, Desc.Format, D3DPOOL_DEFAULT, &pTexture[i]))) return hr;
0224:         // テクスチャーとサーフェスを関連づける
0225:         if( FAILED(hr = pTexture[i]->GetSurfaceLevel(0,&pTextureSurface[i]))) return hr;
0226:         // テクスチャー用の描画と深度バッファを関連付ける(一枚目だけ深度を持つ)
0227:         if( FAILED(hr = lpD3DDev->SetRenderTarget(pTextureSurface[i], (i==0)?lpZbuffer:NULL ))) return hr;
0228:     }
0229:     // ぼけテクスチャー作成用のリソースを確保する
0230:     for (i = 0; i < nTempTex; ++i) {
0231:         // テクスチャーの生成
0232:         if( FAILED(hr = lpD3DDev->CreateTexture(Desc.Width, Desc.Height, 1
0233:                                 , D3DUSAGE_RENDERTARGET, Desc.Format, D3DPOOL_DEFAULT, &pTmpTexture[i]))) return hr;
0234:         // テクスチャーとサーフェスを関連づける
0235:         if( FAILED(hr = pTmpTexture[i]->GetSurfaceLevel(0,&pTmpSurface[i]))) return hr;
0236:         // テクスチャー用の描画と深度バッファを関連付ける
0237:         if( FAILED(hr = lpD3DDev->SetRenderTarget(pTmpSurface[i], NULL ))) return hr;
0238:     }
0239: 
0240:     // 描画を元の画面に戻す
0241:     lpD3DDev->SetRenderTarget(pBackbuffer, lpZbuffer );
0242:     
0243:     // シェ-ダーのロード
0244:     if ( FAILED(CVertexShaderMgr::Load(lpD3DDev, "blur.vsh",     &hBlurVertexShader, dwBlurDecl)) ) return hr;
0245:     if ( FAILED( CPixelShaderMgr::Load(lpD3DDev, "blur.psh",     &hBlurPixelShader)) ) return hr;
0246:     if ( FAILED( CPixelShaderMgr::Load(lpD3DDev, "focus.psh",    &hFocusPixelShader)) ) return hr;
0247:     
0248:     // 定数レジスタの設定
0249:     float const s = 4.0f/3.0f;
0250:     float const inv_w = s / (float)WIDTH;
0251:     float const inv_h = s / (float)HEIGHT;
0254:     lpD3DDev->SetVertexShaderConstant(20, &D3DXVECTOR4  ( 0.0f,  0.0f, 0.0f, 0.0f), 1);
0255:     lpD3DDev->SetVertexShaderConstant(21, &D3DXVECTOR4  ( 0.0f, inv_h, 0.0f, 0.0f), 1);
0256:     lpD3DDev->SetVertexShaderConstant(22, &D3DXVECTOR4  (inv_w, inv_h, 0.0f, 0.0f), 1);
0257:     lpD3DDev->SetVertexShaderConstant(23, &D3DXVECTOR4  (inv_w,  0.0f, 0.0f, 0.0f), 1);
0258: 
0259:     return S_OK;
0260: }

描画部分は大まかに7つに別れています。

Iレンダリング場所をテクスチャに設定
II行列の作成
III背景の描画
IVモデル(車)の描画
V半透明合成して、ボケたテクスチャーを作成する
VI完成した絵を合成する
VII環境を元に戻す

それぞれのステップで何をやっているかは、今までに出てきたものや今回説明した内容の設定がほとんどですので、すぐに分かると思います。
気をつけることは、ボケたテクスチャーを作るときに、テクスチャーの設定に全て同じテクスチャーを設定することです。
全てに設定しないと、4枚の合成ができません。
また、それぞれ、読み込みと書き出しに何を使っているか、気をつける必要があります
(最初にpTexture[i-1]を読み込んで、その後、pTmpTexture[]を交互に使って読み書きし、最後にpTexture[i]に書き出します)。

0292: //-----------------------------------------------------------------------------
0293: // Name: Render()
0294: // Desc: Draws the scene
0295: //-----------------------------------------------------------------------------
0296: VOID Render(LPDIRECT3DDEVICE8 lpD3DDev)
0297: {
0298:     DWORD i, j, k;
0299:     D3DXMATRIX mWorld, mView, mProj, m;
0300:     
????:     //
????:     // レンダリングをテクスチャに設定
????:     //
0301:     LPDIRECT3DSURFACE8 lpZbuffer = NULL;
0302:     lpD3DDev->GetDepthStencilSurface( &lpZbuffer );
0303:     lpD3DDev->SetRenderTarget(pTextureSurface[0], lpZbuffer);
0304:     lpD3DDev->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,0),1.0f,0);
0305: 
????:     //
????:     // 行列の作成
????:     //
0307:     D3DXMatrixRotationY( &mWorld, timeGetTime()/1000.0f );
0308:     
0310:     D3DXVECTOR3 eye    = D3DXVECTOR3(0.0f,MeshRadius,2.5f*MeshRadius);
0311:     D3DXVECTOR3 lookAt = D3DXVECTOR3(0.0f,  0.0f,  0.0f);
0312:     D3DXVECTOR3 up     = D3DXVECTOR3(0.0f,  1.0f,  0.0f);
0314:     D3DXMatrixLookAtLH(&mView, &eye, &lookAt, &up);
0315:     D3DXMatrixPerspectiveFovLH(&mProj
0316:         ,60.0f*PI/180.0f                        // 視野角
0317:         ,(float)WIDTH/(float)HEIGHT             // アスペクト比
0318:         ,0.01f,100.0f                           // 最近接距離,最遠方距離
0319:         );
0320:     //
0321:     // 背景描画
0322:     // 
0323:     lpD3DDev->SetTransform( D3DTS_VIEW, &mView );
0324:     lpD3DDev->SetTransform( D3DTS_PROJECTION, &mProj );
0325:     DrawBg(lpD3DDev);
0326:     
0342:     //
0343:     // もでる描画
0344:     //
0327:     // 共通パラメータの設定
0328:     m = mWorld * mView * mProj;
0329:     D3DXMatrixTranspose( &m ,  &m);
0330:     lpD3DDev->SetVertexShaderConstant(0,&m, 4);
0331: 
0332:     D3DXVECTOR4 lightDir(1.0f, 1.0f, 0.5f, 0.0f);
0333:     D3DXVec4Normalize(&lightDir, &lightDir);
0334:     D3DXMatrixInverse( &m,  NULL, &mWorld);
0335:     D3DXVec4Transform(&lightDir, &lightDir, &m);
0336:     lightDir[3] = 0.3f;// 環境光の強さ
0337:     lpD3DDev->SetVertexShaderConstant(13, &lightDir, 1);
0338:     
0339:     lpD3DDev->SetStreamSource(0, pMeshVB, sizeof(D3D_CUSTOMVERTEX));
0340:     lpD3DDev->SetIndices(pMeshIndex,0);
0341: 
0345:     lpD3DDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE);
0346:     lpD3DDev->SetVertexShader(hVertexShader);
0347:     for(i=0;i<dwNumMaterials;i++){
0348:         //色をセット
0349:         D3DXVECTOR4 vl;
0350:         vl.x = pMeshMaterials[i].Diffuse.r;
0351:         vl.y = pMeshMaterials[i].Diffuse.g;
0352:         vl.z = pMeshMaterials[i].Diffuse.b;
0353:         lpD3DDev->SetVertexShaderConstant(14, &vl, 1);
0354: 
0355:         lpD3DDev->SetTexture(0,pMeshTextures[i]);
0356:         lpD3DDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 
0357:                                         pSubsetTable[i].VertexStart,
0358:                                         pSubsetTable[i].VertexCount,
0359:                                         pSubsetTable[i].FaceStart * 3,
0360:                                         pSubsetTable[i].FaceCount);
0361:     }
0362:     // 
0363:     // 半透明合成して、ボケたテクスチャーを作成する
0364:     // 
0365:     lpD3DDev->SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
0366:     lpD3DDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
0367:     lpD3DDev->SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE);
0368: 
0369:     for (i = 0; i < 4; ++i) {
0370:         lpD3DDev->SetTextureStageState(i, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
0371:         lpD3DDev->SetTextureStageState(i, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
0372:         lpD3DDev->SetTextureStageState(i, D3DTSS_MIPFILTER, D3DTEXF_NONE);
0373:         lpD3DDev->SetTextureStageState(i, D3DTSS_ADDRESSU,  D3DTADDRESS_CLAMP);
0374:         lpD3DDev->SetTextureStageState(i, D3DTSS_ADDRESSV,  D3DTADDRESS_CLAMP);
0375:     }
0376: 
0377:     lpD3DDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
0378:     lpD3DDev->SetRenderState(D3DRS_BLENDOP,   D3DBLENDOP_ADD);                 
0379:     lpD3DDev->SetRenderState(D3DRS_SRCBLEND,  D3DBLEND_ONE);
0380:     lpD3DDev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
0381: 
0382:     lpD3DDev->SetRenderState(D3DRS_ZENABLE, FALSE);
0383: 
0392:     lpD3DDev->SetVertexShader(hBlurVertexShader);
0393:     lpD3DDev->SetPixelShader(hBlurPixelShader); 
0394:     lpD3DDev->SetStreamSource(0, pBlurVB, sizeof(D3D_BLUR_VERTEX));
0395:     lpD3DDev->SetIndices(pBlurIB,0);
0396:     
0397:     for (i = 1; i < nBlurTex; ++i){
0398:         for (j = 0; j < nTempTex; ++j){
0399:             LPDIRECT3DTEXTURE8  pSource      = (j ==          0)?       pTexture[i-1]:pTmpTexture[ j   &1];
0400:             LPDIRECT3DSURFACE8  pDestination = (j == nTempTex-1)?pTextureSurface[i  ]:pTmpSurface[(j+1)&1];
0401:             lpD3DDev->SetRenderTarget(pDestination, NULL);
0402:             for (k = 0; k < 4; ++k) lpD3DDev->SetTexture(k, pSource);
0403:             lpD3DDev->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 4, 0, 2 );
0404:         }
0405:     }
0406:     // 
0407:     // 完成した絵を合成する
0408:     // 
0409:     lpD3DDev->SetRenderTarget(pBackbuffer, lpZbuffer );                 // 描画をバックバッファに戻す
0410:     lpD3DDev->SetPixelShader(hFocusPixelShader);    
0411:     lpD3DDev->SetTexture( 0, pMaskTexture );                            // マスクテクスチャー
0412:     lpD3DDev->SetTexture( 1, pTexture[0] );                             // 元テクスチャー
0413:     lpD3DDev->SetTexture( 2, pTexture[nBlurTex-1] );                    // ボケテクスチャー
0414:     lpD3DDev->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 4, 0, 2 );
0415:     //
0416:     // 環境を元に戻す
0417:     //
0418:     lpD3DDev->SetPixelShader(NULL); 
0419:     lpD3DDev->SetRenderState(D3DRS_ZENABLE, TRUE);
0420: }

あとは、適当に開放してください。
(最近 Debug環境 にして、分かったのですが、プログラムの開放部分がおかしいようです。そのうち直します)。

■最後に

テクスチャーの合成を利用したフォーカスエフェクトを作成しました。
ガンシューティングや雨の中の見ずらくするための表現に使えるのではないでしょうか。
次回は、合成パラメータに深度バッファを用いた被写界深度ができたらいいなぁと思っています。




もどる

imagire@gmail.com