Bee さんや、nightmareさん(最新HPどこですか~)に『そんなんじゃ満足できん』いわれたので、拡張しました。
前回のものは、ツゥーンシェーダーとトーンシェーダーを重ねたのですが、今回は、トーンをツゥーンを用いて書き換えています。
薄いところはそれなりに、濃いところはそれなりに暗いトーンを張ります。
今回のソースは、次のものです(DirectX8.1用です)。
まぁ、いつものように適当にファイルが入っています。
ps.psh | ピクセルシェーダー。今回は主にここを書き換え |
draw.cpp | メインの描画部分。テクスチャー生成を追加 |
vs.vsh | 頂点シェーダー。 |
draw.h | 描画の各関数の定義。特に意味無いので出番無し。 |
main.h | 基本的な定数など。今回も出番無し。 |
main.cpp | 描画に関係しないシステム的な部分。変更が無いので、出番無し。 |
あと、モデルと、実行ファイル及び、プロジェクトファイルが入っています。
今回、トーンの模様は算術計算して求めたので、専用のトーンはありません。
実物の暗いスクリーントーンと、明るいスクリーントーンを見比べたときに、その点の大きさが異なっています。
色が薄くなっていたり、点の間隔が狭くなっているわけではありません(無論、そのような変化もありますが主要な変化が大きさの違いだと思うのです)。
そこで、点状のスクリーントーンを作っておき、その点の大きさをピクセルシェーダーで調整します。
実際には、Cook-Torrance の照明モデルで使った、D3DXFillTexture を使って、トーンを生成し、
レンダリングするときに、トーンの濃さを変化させて、貼り付けます。
変化させ方に、作り手による個性が出るのが NPR の面白いところですが、
今回は、ライトの色をトゥーんシェーディングしたものと線形合成します。
黒い模様を広くするには、一様に減算し、0クランプされる範囲を広げればいいと思いました。
逆に、明るいときは、白、一色になる必要があるので、このときは、1で飽和させる必要があります。
そこで、光の明るさを[-1,1]の範囲に広げて、加減算することによって、
一番明るいときには白、一番暗いときに黒が出るように実装しました。
頂点しぇ―ダーは前回と同じものです。
ピクセルシェーダープログラムを前回から書き換えました。
0001: ; ps.psh 0002: ; 0003: 0004: ps.1.0 0005: 0009: ; テクスチャーの色を引っ張ってくる 0010: tex t0 ; ツゥーンテクスチャー 0011: tex t1 ; トーンテクスチャー 0012: 0013: mov r0, t1 0014: add r0, r0, t0_bx2 ; r0 = t1+2(t0-0.5f) 0015: mul r0, r0, v0 0016:
ツゥーンテクスチャーとトーンテクスチャーをロードし、(t?レジスタは1命令に2つ使えないのでr0にコピーしてから)先ほどの計算をして、トーンの模様を作ります。
最終的にモデルの色を乗算して、色を決定します。
トーンのテクスチャーですが、点列が交互に並ぶように、斜めにずれた2つの点からの距離を測定し、明かる差を決定しています(draw.cpp)。
0148: // ---------------------------------------------------------------------------- 0149: // Name: MakeTexture() 0150: // Desc: テクスチャーの生成 0151: //----------------------------------------------------------------------------- 0152: VOID MakeTexture(D3DXVECTOR4* pOut, D3DXVECTOR2 *pTexCoord, D3DXVECTOR2 *pTexelSize, LPVOID pData) 0153: { 0154: const int num = 16; // 縦に並んだ円の個数 0155: const float grid_size = 1.0f/((float)num); // 円の直径 0156: 0157: float shade=1.0f; 0158: float dx, dy, r2; 0159: 0160: dx = pTexCoord->x/(0.5f*grid_size) - 0.0f; 0161: dy = pTexCoord->y/(0.5f*grid_size) - 0.0f; 0162: while( 1.0f0163: while(dx<-1.0f ) dx += 2.0f; 0164: while( 1.0f 0165: while(dy<-1.0f ) dy += 2.0f; 0166: r2 = 1.0f-2*(dx*dx+dy*dy); 0167: if(0.0f< r2)shade -= (float)sqrt(r2); 0168: 0169: dx = pTexCoord->x/(0.5f*grid_size) - 1.0f; 0170: dy = pTexCoord->y/(0.5f*grid_size) - 1.0f; 0171: while( 1.0f 0172: while(dx<-1.0f ) dx += 2.0f; 0173: while( 1.0f 0174: while(dy<-1.0f ) dy += 2.0f; 0175: r2 = 1.0f-2*(dx*dx+dy*dy); 0176: if(0.0f< r2)shade -= (float)sqrt(r2); 0177: 0178: if(shade < 0.0f)shade = 0.0f; 0179: if(1.0f < shade)shade = 1.0f; 0180: 0181: pOut->x = shade; 0182: pOut->y = shade; 0183: pOut->z = shade; 0184: pOut->w = 1.0f; 0185: }
テクスチャー作成の呼び出しは、以前にもやった通りです。
0186: // ---------------------------------------------------------------------------- 0187: // Name: InitRender() 0188: // Desc: ポリゴンの初期化 0189: //----------------------------------------------------------------------------- 0190: HRESULT InitRender(LPDIRECT3DDEVICE8 lpD3DDEV) 0191: { 0192: HRESULT hr; 0193: 0194: hr = LoadXFile("nsx.x", lpD3DDEV); 0195: if ( FAILED(hr) ) return hr; 0196: 0197: // ツゥーンテクスチャーの読み込み 0198: D3DXCreateTextureFromFileEx(lpD3DDEV, "toon.bmp",0,0,0,0,D3DFMT_A8R8G8B8, 0199: D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, 0200: 0, NULL, NULL, &pToonTexture); 0201: // トーンテクスチャーの生成 0202: if( FAILED(hr = lpD3DDEV->CreateTexture(256, 256, 1 ,D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &pToneTexture))) return hr; 0203: if( FAILED(hr = D3DXFillTexture(pToneTexture, MakeTexture, NULL))) return hr; 0204: ・・・・・・・・・ 0241: 0242: return S_OK; 0243: }
作りっぱなしの私が、珍しく機能拡張してみました。
新聞の白黒写真のように見えますね。
如何でしょうか?