頂点シェーダを使用した DirectX 8 でのトゥーン レンダリング
Microsoft Corporation
March 2001
要約: この記事では、Microsoft DirectX 8 におけるオブジェクトのボディと輪郭線のトゥーン レンダリングについて説明します。またサンプルのアプリケーションも載せてあります。
MSDN Online Code Center のサンプル コード (Toon.exe) をダウンロードする
はじめに
「トゥーン レンダリング」 に、特定の定義はありません。漫画のように、ノンフォトリアリスティックなスタイルでオブジェクトをレンダリングすることです。その特徴は、単色ブロックを大量に使用したシンプルなシェーディングで、オブジェクトの周囲にアウトラインが作成されます。ここで説明するレンダリングの手法には 2 つの課題があります。
漫画のようにオブジェクトの 「ボディ」 をレンダリングすると、カラー バンドが極端になります (皮肉なことに、カラー バンドは通常不要ですが、ここでは強調しておきます)。
黒い太線としての輪郭線エッジのレンダリング
オブジェクト ボディのレンダリング (ペイント)
通常のディフューズ ライティングの計算 (この例では単一のディレクショナル ライト) を実行しますが、この結果を頂点カラー レジスタに入力する代わりに、ライティング値を、シンプルなバンド輝度テクスチャであるテクスチャのテクスチャ座標として使用します。頂点カラー レジスタへロードした目的のマテリアルの色を使用してこのテクスチャと合成すると、色が得られます。頂点シェーダは次のとおりです。
;入力: v0 = 位置
; v1 = 法線
; c0 = (0,0.5,xxx,xxx) 便利な定数
; c1-4 = ワールド ビュー行列
; c5-9 = ワールド ビュー射影行列
; c9 = ライト/マテリアルの色
; c10 = ライトの方向 (ビュー空間での)
vs.1.0 ; シェーダ バージョン 1.0
m4x4 oPos , v0 , c5 ; 射影位置の計算
m3x3 r1 , v1 , c1 ; r1 = ビュー空間の法線
dp3 r2 ,-r1 , c10 ; r2 = ディフューズ ライティングの計算
max r2 , r2 , c0.x ; r2 を 0 にクランプ
mov oT0.x, r2 ; テクスチャ座標 0 は (r2,0.5)
mov oT0.y, c0.y
mov oD0 , c9 ; ディフューズ色 = c9
説明を簡単にするためにライティングをビュー空間で実行します、オブジェクト空間で実行すればもう少し最適化できるでしょう、その場合は 2 番目の行列連結をシェーダで行う必要はありません (ビュー空間でライトの方向を指定するには、逆トランスフォームする必要があります)。
重要なステップとして、ライティングの計算結果をテクスチャ座標の 「u」 成分にロードします。これにより、テクスチャが 1D ルックアップ テーブルに効率的に変換されるため、このテーブルに目的の正確な 「バンド」 をロードできます。
最後に、マテリアルの色 (ライトと見なすことができます) を頂点カラー レジスタにロードし、これを使用して、テクスチャから読み取る濃度値を処理します。必要なら、この値は頂点成分から読み取ることができますが、説明を簡単にするために、このオブジェクトの色は一定ということを前提として定数を使用します。
便利ないくつかの定数は 1 つの c0
定数レジスタに入れます。c0.x
にロードされている 0 を使用してライティングの内積をクランプします。また、テクスチャ座標の 「v」 成分として 0.5 を c0.y
にロードします (テクスチャの真ん中に置いて、エッジ サンプリングの問題を回避します)。
輪郭線のレンダリング (インク)
オブジェクトの輪郭線をレンダリングするために、輪郭線のエッジを検出します。エッジは、それに接触した 2 つの面が別々にカリングされている場合 (1 つが背面でもう 1 つが背面でない場合など)、輪郭線の一部となります。滑らかなオブジェクトの場合、頂点法線が頂点のビュー ベクトルに対して垂直となるとき、つまりスクリーン空間位置を持つ法線の内積が 0 となるとき、頂点はほぼ輪郭線上に存在します。
したがって、頂点シェーダを使用して、正規化ビュー ベクトルと、頂点法線との内積を計算します。頂点は、値が 0 の場合は輪郭線にあり、値が 0 に近い場合は輪郭線に近いことがわかります。大まかに言うと、内積の値は、頂点が輪郭線のエッジにどれだけ近いかを示しています。「漫画」 らしさを出すには、輪郭線のエッジを太くします。したがって、輪郭線に含まれると見なされるポイントより下にしきい値を設定します。しきい値を大きくすると、エッジは太くなります。
このしきい値テストは、ピクセル単位で実行する必要があります。変化をシャープにするためです。都合の良いことに、こうしたテストをアルファ テストという形で実行する仕組みが既に用意されています。内積 ([-1,-1] から [0,1] へ再スケーリングする) を頂点カラー レジスタのアルファ チャネルにロードし、アルファ テストを使用して輪郭線にないピクセルを排除すると、ほかのピクセルはすべて黒い実線としてレンダリングされます。
頂点シェーダは次のとおりです。
; 入力: v0 = 位置
; v1 = 法線
; c0 = 便利な定数 (0,xxx,xxx,xxx)
; c1-4 = ワールド ビュー行列
; c5-9 = ワールド ビュー射影行列
vs.1.0 ; シェーダ バージョン 1.0
m4x4 r0 , v0 , c1 ; r0 = ビュー空間の位置
m3x3 r1 , v1 , c1 ; r1 = ビュー空間の法線
m4x4 oPos , v0 , c5 ; 射影位置の計算
dp3 r2.x , r0 , r0 ; r0 (位置) の正規化
rsq r2.x , r2.x
mul r0 , r0 , r2.x
dp3 r3.x , r0 , -r1 ; 内積の計算
mad oD0.w, r3.x , c0.y , c0.y ; [0,1] へスケーリングしてアルファを作成
mov oD0.xyz , c0.x ; ディフューズ RGB は 0
出力位置は、ビュー空間位置と射影行列を順に掛けて求めるのではなく、ワールド、ビュー、および射影の各行列を連結したものから直接トランスフォームして計算します。この理由は、オブジェクト ボディのレンダリング パスの場合と同じように位置を計算する必要があるからです。このようにしないと対象の位置の 「z が競合」 する場合があります。
ディフューズ色をコピーし、アルファ テストを適用するには、このパスのピクセル パイプラインを次のように設定します。
m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE , TRUE );
m_pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC , D3DCMP_LESS );
m_pd3dDevice->SetRenderState( D3DRS_ALPHAREF , m_dwSilhouetteAmount );
アルファ テストの参照値 m_dwSilhouetteAmount
を変更すると、輪郭線 エッジの太さを制御できます。
制限事項
この手法には、制限事項がいくつかあります。特に、大きなフラット サーフェスを持つオブジェクト、または 「平面」 にする予定のオブジェクトのレンダリングが通常おかしな表示になります。サーフェス全体において法線がほとんど同じかまったく同じになり、オブジェクトのボディの色が一定となるため、大部分が 「輪郭線」 と見なされる可能性があるからです。望ましい処理結果になる場合もありますが、一般に、求められている 「漫画らしさ」 が損なわれます。ポイント光源を使用した複雑なライティング公式を使用すると、オブジェクト ボディの一定した色をある程度固定することができます。ただし、ここで説明した輪郭線 レンダリングの手法を、大きなフラット サーフェスを持つオブジェクトへ適用することは困難になります。曲線的なオブジェクトでは通常うまくいきます。
サンプル アプリケーション
Microsoft® DirectX® 8 SDK のサンプル アプリケーション フレームワークをベースにした 「Toon」 サンプル アプリケーションは、この手法がシンプルなメッシュに適用できることを示しています。このサンプルをコンパイルするには、MSSDK\Samples\Multimedia\Direct3D\ パスの下にディレクトリを作成し、ここにソース ファイルをコピーします (このサンプルは、DirectX 8 SDK の共通サンプル アプリケーション フレームワークのファイルの一部を参照します)。
MSDN Online Code Center のサンプル コード (Toon.exe) を表示する。
コントロールは次のとおりです。
アクション | コントロール |
輪郭線しきい値を増やす/減らす | Q / A |
組み込みメッシュの切り替え (トーラスまたはティーポット) | T |
オブジェクトの回転 | 左クリックしてドラッグ |
.X ファイルのロード | L |
ロードした .Xファイルの表示 | O |