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; }