GeometryShader で, Phong Tessellation を実装してみました
DirectX11 で Geometry Shader を使って, Phong Tessellation を実装してみました.
- 参考文献
- Phong Tessellation
- Dynamic Patch Tessellation
また, デモの実行環境は以下のようになってます.
- PC : Thinkpad T510
- OS : Windows 7 64-bit
- CPU : Intel Core i7 M 620 2.67GHz
- メモリ : 4.00 GB
- GPU : NVidia NVS 3100M ( DirectX10 HW 対応 )
靴の部分が元々のポリゴンが粗いため, わかりすかったのでテッセレーション前の靴の形状を見せます. fps は 374 です.

Phong テッセレーション後の靴の形状です. 靴の輪郭の形状が丸くなっています. fps は 147 です.

テッセレーション前の上半身です. fps は 323 です.

Phong テッセレーション後の上半身です. fps は 151 です. 腕や腰の部分がやや丸くなっていますが, 正直に言うと, 違いがわかりにくいので(汗), 視点位置に応じた適応型のテッセレーションをすべきだと思いました.

以下に, シェーダのソースコードを貼らせていただきます. (シェーダのコードは取り敢えず実装してみたレベルなので, もしどこか間違っている箇所があったら申し訳ないです... )
// Phong Tessellation の頂点シェーダ ( DirectX11 )
// オブジェクトごとの定数バッファです
cbuffer cbPerObject : register( b0 )
{
// WorldViewProjection 行列です.
matrix g_mWorldViewProjection : packoffset( c0 );
// World 行列です
matrix g_mWorld : packoffset( c4 );
};
// 頂点シェーダの入力データです
struct VS_INPUT
{
// オブジェクト座標系の位置座標です
float4 vPosition : POSITION;
// オブジェクト座標系の法線です
float3 vNormal : NORMAL;
// テクスチャ座標です
float2 vTexcoord : TEXCOORD0;
};
// 頂点シェーダの出力データです
struct VS_OUTPUT
{
// ワールド座標系の位置座標です
float4 vPosition : POSITION;
// ワールド座標系の法線です
float3 vNormal : NORMAL;
// テクスチャ座標です
float2 vTexcoord : TEXCOORD0;
};
// 頂点シェーダのエントリポイントです
VS_OUTPUT VSMain( VS_INPUT Input )
{
VS_OUTPUT Output;
// 頂点座標をワールド座標系に変換します
Output.vPosition = mul( Input.vPosition, g_mWorld );
// 法線を ワールド座標系に変換します
Output.vNormal = mul( Input.vNormal, (float3x3)g_mWorld );
// テクスチャ座標をそのまま渡します
Output.vTexcoord = Input.vTexcoord;
return Output;
}// Phong Tessellation のジオメトリシェーダ ( DirectX11 )
// オブジェクトごとの定数バッファです
cbuffer cbPerObject : register( b0 )
{
// View Projection 行列です.
matrix g_mViewProjection : packoffset( c0 );
};
// ジオメトリシェーダの入力データです
struct GS_INPUT
{
// ワールド座標系の位置座標です
float4 vPosition : POSITION;
// ワールド座標系の法線座標です
float3 vNormal : NORMAL;
// テクスチャ座標です
float2 vTexcoord : TEXCOORD0;
};
// ジオメトリシェーダの出力データです
struct PS_INPUT
{
// ワールド座標系の法線座標です
float3 vNormal : NORMAL;
// テクスチャ座標です
float2 vTexcoord : TEXCOORD0;
// クリップ座標の位置座標です
float4 vPosition : SV_POSITION;
};
// 位置座標 pos を,
// 平面( 位置座標 planePos, 法線 planeNormal) に射影します
float3 ComputeProjectedPoint( float4 planePos,
float3 planeNormal, float4 pos )
{
return planePos.xyz +
dot( ( planePos.xyz - pos.xyz ), planeNormal ) * planeNormal;
}
// 重心座標の係数と, 射影後の位置座標をかけます
float4 ComputePhongTessellation( float3 baryCentricCoords,
float3 vertex0ProjPos, float3 vertex1ProjPos,
float3 vertex2ProjPos )
{
float3 pos = baryCentricCoords.x * vertex0ProjPos +
baryCentricCoords.y * vertex1ProjPos +
baryCentricCoords.z * vertex2ProjPos;
return float4( pos.x, pos.y, pos.z, 1.0f );
}
// Phong Tessellation を行います.
// 但し, 実装はかなりシンプルなもので,
// 三角形の分割の個数は 4個に固定しています.
#define MAX_VERTEX_COUNT 16
[maxvertexcount( MAX_VERTEX_COUNT )]
void GSMain( triangle GS_INPUT input[3],
uint primitiveID : SV_PrimitiveID,
inout TriangleStream<PS_INPUT> TriStream )
{
// 通常の位置座標のブレンド係数です
float blendNormal = 0.25f;
// テッセレーション後の位置座標のブレンド係数です
float blendTess = 1.0f - blendNormal;
// 各辺の中点の位置座標を計算します
float4 medPosV0V1 =
( input[ 0 ].vPosition + input[ 1 ].vPosition ) / 2.0f;
float4 medPosV0V2 =
( input[ 0 ].vPosition + input[ 2 ].vPosition ) / 2.0f;
float4 medPosV1V2 =
( input[ 1 ].vPosition + input[ 2 ].vPosition ) / 2.0f;
// 中点01 ( 頂点 0 と頂点 1 の中点 ) を,
// 三角形の 3 つの頂点の各平面に対して射影します
float3 medProj0PosV0V1 =
ComputeProjectedPoint( input[ 0 ].vPosition,
input[ 0 ].vNormal, medPosV0V1 );
float3 medProj1PosV0V1 =
ComputeProjectedPoint( input[ 1 ].vPosition,
input[ 1 ].vNormal, medPosV0V1 );
float3 medProj2PosV0V1 =
ComputeProjectedPoint( input[ 2 ].vPosition,
input[ 2 ].vNormal, medPosV0V1 );
// 中点 01 の重心座標の係数と射影後の位置座標をかけます
float3 baryCentricCoords = float3( 0.5f, 0.5f, 0.0f );
float4 medPosV0V1Tess =
ComputePhongTessellation(baryCentricCoords,
medProj0PosV0V1, medProj1PosV0V1, medProj2PosV0V1);
// 最後に, テッセレーション前後の位置座標をブレンドします
medPosV0V1Tess = blendNormal * medPosV0V1 +
blendTess * medPosV0V1Tess;
// 中点 02 に関しても同様の処理を行います
float3 medProj0PosV0V2 =
ComputeProjectedPoint( input[ 0 ].vPosition,
input[ 0 ].vNormal, medPosV0V2 );
float3 medProj1PosV0V2 =
ComputeProjectedPoint( input[ 1 ].vPosition,
input[ 1 ].vNormal, medPosV0V2 );
float3 medProj2PosV0V2 =
ComputeProjectedPoint( input[ 2 ].vPosition,
input[ 2 ].vNormal, medPosV0V2 );
baryCentricCoords = float3( 0.5f, 0.0f, 0.5f );
float4 medPosV0V2Tess =
ComputePhongTessellation( baryCentricCoords,
medProj0PosV0V2, medProj1PosV0V2, medProj2PosV0V2);
medPosV0V2Tess = blendNormal * medPosV0V2 +
blendTess * medPosV0V2Tess;
// 中点 12 に関しても同様の処理を行います
float3 medProj0PosV1V2 =
ComputeProjectedPoint( input[ 0 ].vPosition,
input[ 0 ].vNormal, medPosV1V2 );
float3 medProj1PosV1V2 =
ComputeProjectedPoint( input[ 1 ].vPosition,
input[ 1 ].vNormal, medPosV1V2 );
float3 medProj2PosV1V2 =
ComputeProjectedPoint( input[ 2 ].vPosition,
input[ 2 ].vNormal, medPosV1V2 );
baryCentricCoords = float3( 0.0f, 0.5f, 0.5f );
float4 medPosV1V2Tess =
ComputePhongTessellation( baryCentricCoords,
medProj0PosV1V2, medProj1PosV1V2, medProj2PosV1V2 );
medPosV1V2Tess = blendNormal * medPosV1V2 +
blendTess * medPosV1V2Tess;
// 各中点の法線を求めます
float3 medNormalV0V1 =
normalize( input[ 0 ].vNormal.xyz + input[ 1 ].vNormal.xyz );
float3 medNormalV0V2 =
normalize( input[ 0 ].vNormal.xyz + input[ 2 ].vNormal.xyz );
float3 medNormalV1V2 =
normalize( input[ 1 ].vNormal.xyz + input[ 2 ].vNormal.xyz );
// 各中点のテクスチャ座標を求めます
float2 medTexcoordV0V1 =
( input[ 0 ].vTexcoord.xy + input[ 1 ].vTexcoord.xy ) / 2.0f;
float2 medTexcoordV0V2 =
( input[ 0 ].vTexcoord.xy + input[ 2 ].vTexcoord.xy ) / 2.0f;
float2 medTexcoordV1V2 =
( input[ 1 ].vTexcoord.xy + input[ 2 ].vTexcoord.xy ) / 2.0f;
PS_INPUT output;
// 三角形 0 用の頂点 0 を出力します
output.vPosition = mul( input[ 0 ].vPosition, g_mViewProjection );
output.vNormal = input[ 0 ].vNormal;
output.vTexcoord = input[ 0 ].vTexcoord;
TriStream.Append( output );
// 三角形 0 用の中点 01 を出力します
output.vPosition = mul( medPosV0V1Tess , g_mViewProjection );
output.vNormal = medNormalV0V1;
output.vTexcoord = medTexcoordV0V1;
TriStream.Append( output );
// 三角形 0 用の中点 02 を出力します
output.vPosition = mul( medPosV0V2Tess , g_mViewProjection );
output.vNormal = medNormalV0V2;
output.vTexcoord = medTexcoordV0V2;
TriStream.Append( output );
// 三角形 0 のストリップを終了します
TriStream.RestartStrip();
// 三角形 1 用の中点 01 を出力します
output.vPosition = mul( medPosV0V1Tess , g_mViewProjection );
output.vNormal = medNormalV0V1;
output.vTexcoord = medTexcoordV0V1;
TriStream.Append( output );
// 三角形 1 用の頂点 1 を出力します
output.vPosition = mul( input[ 1 ].vPosition, g_mViewProjection );
output.vNormal = input[ 1 ].vNormal;
output.vTexcoord = input[ 1 ].vTexcoord;
TriStream.Append( output );
// 三角形 1 用の中点 12 を出力します
output.vPosition = mul( medPosV1V2Tess , g_mViewProjection );
output.vNormal = medNormalV1V2;
output.vTexcoord = medTexcoordV1V2;
TriStream.Append( output );
// 三角形 1 のストリップを終了します
TriStream.RestartStrip();
// 三角形 2 用の中点 01 を出力します
output.vPosition = mul( medPosV0V1Tess , g_mViewProjection );
output.vNormal = medNormalV0V1;
output.vTexcoord = medTexcoordV0V1;
TriStream.Append( output );
// 三角形 2 用の中点 12 を出力します
output.vPosition = mul( medPosV1V2Tess , g_mViewProjection );
output.vNormal = medNormalV1V2;
output.vTexcoord = medTexcoordV1V2;
TriStream.Append( output );
// 三角形 2 用の中点 02 を出力します
output.vPosition = mul( medPosV0V2Tess , g_mViewProjection );
output.vNormal = medNormalV0V2;
output.vTexcoord = medTexcoordV0V2;
TriStream.Append( output );
// 三角形 2 のストリップを終了します
TriStream.RestartStrip();
// 三角形 3 用の中点 12 を出力します
output.vPosition = mul( medPosV1V2Tess , g_mViewProjection );
output.vNormal = medNormalV1V2;
output.vTexcoord = medTexcoordV1V2;
TriStream.Append( output );
// 三角形 3 用の頂点 2 を出力します
output.vPosition = mul( input[ 2 ].vPosition, g_mViewProjection );
output.vNormal = input[ 2 ].vNormal;
output.vTexcoord = input[ 2 ].vTexcoord;
TriStream.Append( output );
// 三角形 3 用の中点 02 を出力します
output.vPosition = mul( medPosV0V2Tess , g_mViewProjection );
output.vNormal = medNormalV0V2;
output.vTexcoord = medTexcoordV0V2;
TriStream.Append( output );
// 三角形 3 のストリップを終了します
TriStream.RestartStrip();
}// Phong Tessellation のピクセルシェーダ ( DirectX11 )
// オブジェクトごとの定数バッファです
cbuffer cbPerObject : register( b0 )
{
float4 g_vObjectColor : packoffset( c0 );
};
// フレームごとの定数バッファです
cbuffer cbPerFrame : register( b1 )
{
// ワールド座標系の光源の方向ベクトルです
float3 g_vLightDir : packoffset( c0 );
// Ambient 項です
float g_fAmbient : packoffset( c0.w );
};
// 2D テクスチャ とサンプラです
Texture2D g_txDiffuse : register( t0 );
SamplerState g_samLinear : register( s0 );
// ピクセルシェーダへの入力データです
struct PS_INPUT
{
// ワールド座標系の法線です
float3 vNormal : NORMAL;
// テクスチャ座標です
float2 vTexcoord : TEXCOORD0;
};
// ピクセルシェーダのエントリポイントです
float4 PSMain( PS_INPUT Input ) : SV_TARGET
{
// テクスチャから diffuse 色をサンプリングします
float4 vDiffuse =
g_txDiffuse.Sample( g_samLinear, Input.vTexcoord );
// ワールド座標系の法線 と
// ワールド座標系の光源の方向ベクトルの内積を計算します
float fLighting =
saturate( dot( g_vLightDir, Input.vNormal ) );
fLighting = max( fLighting, g_fAmbient );
// diffuse 色と ライティング結果を乗算します
return vDiffuse * fLighting;
}