DirectX8.1を使って、トゥーンレンダリングのうち、今回はシェーディングだけをします。
それにあたって下記のサイトを大いに参考にさせていただきました。
実はこれらのサイトのサンプルのサンプルをチュートリアルのプログラムにつぎはぎしていろいろ書き換えたものがこれなのですが。
そういうことで、ソースコードの解説は控えめにして、考え方の説明を中心に行っていきます。
左はイメージ画像です。これは普通にレンダリングしたのを減色しただけのものです。
なお、今回は知っていることを書いたのではなく、知らないことを手探りで探し求めながら、試行錯誤しつつ書いたものなので、間違ったことも書いてありますが、間違いだとわかったことはあとのほうで訂正していますので、読み始めたら最後まで読まないと誤解してしまう部分があるかもしれません。
何はともあれ3Dが表示できなければ話になりません。3Dを表示できるプログラムをとっとと作りましょう。
いまだにDirect3Dそのものがよく分かっていない私はSDKにあるサンプルを使うことにしました。ここで今回、ビューの位置などは、静止や自動でなく操作して移動できるようにしておきます。ついでにいうと、裏面も見えたほうが何かと都合が良いのでカリングは切っておきます。んでもって、法線の各成分は-1~1でないと困るので、NORMALIZENORMALSはオンにしておきます。
右に通常のレンダリング結果を示しておきます。画質が悪くなっていますが曲面の色が滑らかに変わっているのがわかると思います。
こんな滑らかなグラデーションをさせないというのが今回の目的ですから、ライティングの設定なんて切ってしまいます。それが左の画像です。
今度は環境マッピングを使って影をつけます。簡単に考えるために光が真上から当たっていると考えると、曲面の法線が下を向いているときに黒く塗れば影になるということです。
そこで、下半分が黒いテクスチャを作るのですが、今回はテクスチャがどんな風にマッピングされるかを見るために諧調を増やしてあります。
作ったテクスチャと法線ベクトルを右図のように対応させます。法線の各成分の値は-1~1であり、テクスチャ座標は0~1なので、適当にスケーリングしてやります。
// 例 D3DXMatrixScaling( &matTrans1, 0.5f,-0.5f,1.0f ); D3DXMatrixTranslation( &matTrans2, 0.5f,0.5f,0 ); matTrans = matTrans1 * matTrans2; g_pd3dDevice->SetTransform( D3DTS_TEXTURE0, &matTrans );
で、これで、SetTextureStageStateを呼び出してD3DTSS_TEXCOORDINDEXを適当に指定してやって、ついでにD3DTSS_TEXTURETRANSFORMFLAGSも指定してレンダリングしてやると左のようになります。
やー、こういう風に望みどおりの結果が出てきてくれると嬉しいものですね。そして、あんまり嬉しいのでいろんな角度から見てみたくなるものです。手動で動くようにしていた甲斐がありました。…んが、上から見たときに問題が発生します(右図)。
上から光が当たっているんだから、上から見たら真っ白になるはずなのに、何故でしょう。こちらから見れば確かに上のほうが白くなっていますけど…。
もう一度、もう一度冷静になって考えましょう。D3DTSS_TEXCOORDINDEXにはD3DTSS_TCI_CAMERASPACENORMAL指定したはずです。そうしないと横から見た図すらうまく表示されないはずですから。
要するにカメラから見た法線ということです。つまり、左図のように法線のZ成分はなくなってしまうので…しょうか?それを確かめるために簡単な実験を試みます。
// X,Y成分を消し、Z成分だけをV値に対応させる D3DXMatrixRotationX( &matTrans0, -PI/2 ); D3DXMatrixScaling( &matTrans1, 0,0,0.5f ); D3DXMatrixTranslation( &matTrans2, 0,0,0.5f ); matTrans = matTrans1 * matTrans2 * matTrans0; g_pd3dDevice->SetTransform( D3DTS_TEXTURE0, &matTrans );
で、実験の結果が右になります。Z値が法線のZ成分そのものであれば、こっちを向いているときには白で、向こうを向いているときには黒に見えます。図では、本体とふたの隙間からわずかに見えている部分が、向こうを向いた面がカリングされずに見えているもので、実際こっちを向いているときには白で、向こうを向いているときには黒に見えています。
ヘルプにこのことに関する記述を探してみたら、確かにありました。インターネットから見られるところでいきますと、MSDNライブラリの「カメラ空間トランスフォーム」のページに書いてあります。「N = norm(Nobject * (Mwv-1)T)」とのことです。法線のZ成分が消えるというのは私の勘違いだったようです。
さて、おかげで10行前後無駄にしてしまいましたが、得られるものもありました。ライトのベクトルにこの逆転置行列をかけてやればカメラ空間でもライトの方向になるわけです。これを正規化したものと例の法線の内積を取ってやればすなわちその部分の明るさというわけで、例によって適当にスケーリングしてやればめでたくこれでトゥーンシェーディングができるという計算です。
テクスチャ座標は、Vが内積にさえなれば他はどうでもいいので、2列目がライトの成分と同じ行列をかけます。
void ToonShade(){ g_pd3dDevice->SetTexture( 0, g_pTeaTextures[0] ); g_pd3dDevice->LightEnable( 0, FALSE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); D3DXMATRIX matTrans,matTrans0,matTrans1,matTrans2; D3DXVECTOR4 light1, light2; // ライト計算 D3DXMatrixInverse(&matTrans1,NULL,&g_matView); D3DXMatrixTranspose(&matTrans2,&matTrans1); D3DXVec3Transform(&light1, &g_vecDir, &matTrans2); light1.w = 0; // 4Dベクトルだと何かと都合が悪いのでW値を消去 D3DXVec4Normalize(&light2,&light1); matTrans0 = D3DXMATRIX( 1, light2.x, 0, 0, 0, light2.y, 0, 0, 0, light2.z, 1, 0, 0, 0, 0, 1); // V値を0~1にスケーリング D3DXMatrixScaling( &matTrans1, 1.0f,-0.5f,1.0f ); D3DXMatrixTranslation( &matTrans2, 0,0.5f,0 ); matTrans = matTrans0 * matTrans1 * matTrans2; g_pd3dDevice->SetTransform( D3DTS_TEXTURE0, &matTrans ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 ); }
そして、できたコードがこれだあっ!そして実行(左図)。どきどき・・・
やるかと思ったら本当にやっちまいました。対処法はもはや言うまでもないので省略。成功図は右。
その後面白がってライトを動かしまくったのは言うまでもありません。
前回はただ単にシェーディング用テクスチャを貼り付けただけだったから、元々テクスチャのあるメッシュ(さっきのコード例では虎)だとテクスチャが剥がれていました。
今回は元々テクスチャがあった場合はそれを反映するようにします。
よーするに2つのテクスチャを掛け合わせて表示できればいいのです。
文字通り掛ければいいのです。掛け算です。MULTIPLYです!
だからSetTextureStageStateでD3DTSS_COLOROPにD3DTOP_MULTIPLYを…と言いたい所ですが、そんなのありません。
ごめんなさい知っててわざといいました。でもこれが私なんですあきらめなさい。
D3DTOP_MULTIPLYじゃなくてD3DTOP_MODULATEですね、はい。
まず、ですね。元々の色と影の色を合成します。
これは今までどおり。でも今回書いたコードでは普通のシェーディングと切り替えるためにちょっとした細工をしてあるんですが関係ありませんね、多分。
で、さらにそこにテクスチャを貼っ付けるんですね。
g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE); g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE); g_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT); g_pd3dDevice->SetTexture( 1, g_pMeshTexture );
まあ、こんな感じ。多分。うまくいかなかったらごめん。