ジオメトリシェーダを使って輪郭線を表示してみます。方法は法線方向への拡大&裏面描画ではなく、ポリゴンエッジに輪郭線をつけるかどうか判定し、エッジ(辺)に沿ってラインを描画します。

輪郭線判定アルゴリズム
図のように1つの辺を共有した2つの三角形ポリゴンが表/裏、裏/表だった場合、共有する辺に沿ってラインを描画することで輪郭線表示を行います。
ジオメトリシェーダの入力データ
輪郭線表示のために2つのポリゴンデータをジオメトリシェーダに送る必要があります。1つの辺を共有しているため必要なデータは4頂点、lineadjというプリミティブを利用します。図のように4頂点で2つのポリゴンを定義(本来の使い方ではありませんが)。
ジオメトリシェーダでの輪郭線判定とライン描画
struct GS_INPUT
{
float4 Pos : SV_POSITION;
float Edge : COLOR;//輪郭線有無
};
struct PS_INPUT
{
float4 Pos : SV_POSITION;
float Edge : COLOR;//輪郭線有無
};
// ジオメトリシェーダで輪郭線判定、ライン出力
[maxvertexcount(2)]
void gsBasic( lineadj GS_INPUT input[4],
uint primID : SV_PrimitiveID,
inout LineStream<PS_INPUT> Stream )
{
float3 v0 = input[1].Pos.xyz/input[1].Pos.w;
float3 v1 = input[2].Pos.xyz/input[2].Pos.w;
float3 va = input[0].Pos.xyz/input[0].Pos.w;
float3 vb = input[3].Pos.xyz/input[3].Pos.w;
//ポリゴン v0-v1-vaとv1-v0-vbの方向を調べライン描画
float3 v0a = v0-va;
float3 v1a = v1-va;
float3 v0b = v0-vb;
float3 v1b = v1-vb;
if( 0 > cross(v0a,v1a).z * cross(v1b,v0b).z ){
PS_INPUT vo0,vo1;
float4 p0 = input[1].Pos;
float4 p1 = input[2].Pos;
vo0.Pos = p0;
vo1.Pos = p1;
vo0.Edge = input[1].Edge;
vo1.Edge = input[2].Edge;
Stream.Append(vo0);
Stream.Append(vo1);
Stream.RestartStrip();
}
}
ポリゴンの裏表は、スクリーン座標系でのポリゴン法線で判定します。ラスタライザステートのCullModeと同じ判定方法です。wで割って通常の座標系に戻すことを忘れずに。
サンプルプログラム
“ジオメトリシェーダで輪郭線表示” をダウンロード dx11GeoShader.zip – 331 回のダウンロード – 58 KB
ジオメトリシェーダ用データの生成
// zg_model.h/cpp
bool CreateGeoOutline(const Object& obj, Mesh& out_mesh);
ポリゴン情報から辺を共有している2つのポリゴンを検索し、ジオメトリシェーダに入力するlineadjプリミティブを作成します。作成したlineadjプリミティブデータは、新しくメッシュとマテリアルを作成、追加。処理自体は単純ですが、検索の効率化のためちょっと分かり難いプログラムになっています。
ジオメトリシェーダ
ソースコード shader/outline_b.fx
ライン一本だと細すぎるので、上下左右にずらして合計5本のラインを描画しています。プリミティブの増減を行えることがジオメトリシェーダの特徴です。
PMD表示対応
トゥーンテクスチャとスフィアマップ(加算のみ)に対応してみました。
sample_data.hで読み込むPMD/VMDファイルを指定できます。