GeometryShader で, Phong Tessellation を実装してみました

DirectX11 で Geometry Shader を使って, Phong Tessellation を実装してみました.

また, デモの実行環境は以下のようになってます.


靴の部分が元々のポリゴンが粗いため, わかりすかったのでテッセレーション前の靴の形状を見せます. 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;
}