描画方法についてはキューブを回すところまでできて一段落したので、しばらくは追加された機能について試していきたいと思います。今回は OpenGL 4.x で追加されたハードウェアのテセレーションについてです。DirectX 11 でいうところの、ハルシェーダーだったりドメインシェーダーだったりに相当します。OpenGL ではこれらを、テセレーションコントロールシェーダーとテセレーションエバリュエーションシェーダーと呼びます。なかなかOpenGLでテセレーションの簡単なサンプルを見かけなかったので、ちょっと大変でしたが何となく最小限のものができたので今回はこれを説明&公開します。
テセレーションとは
テセレーションとは、ポリゴン面をさらに分割してよりなめらかな曲面を作り出すのに使用されます。近年のOpenGLやDirectXでは、テセレーションを行うハードウェアが搭載されており、これらをテセレーションコントロールシェーダー(Tessellation control shader)とテセレーションエバリュエーションシェーダー(Tessellation evaluation shader) によって制御します。DirectXではこれらのシェーダーを別名で呼び、ハルシェーダー、ドメインシェーダーと呼んでいます。
OpenGLでのテセレーション用シェーダーの準備
今までの頂点データは三角形や四角形のポリゴンデータを入力としていましたが、テセレーション使用時にはポリゴンではなくパッチデータを入力とします。ただ多くの場合ではパッチデータも三角形や四角形のデータになる気がしますので、ひとまずは単にデータの呼び方が変わっただけと考えておくと先に進めそうです。
OpenGLではこれらのデータを処理するのに Vertex Shader, Tessellation Control Shader, Tessellation Evaluation Shader, Pixel Shader を用意して、シェーダープログラムを作成します。
これらのシェーダープログラムの例を以下に示します。
#version 150 in vec4 in_position; out VertexData { vec3 cp_position; } VertexOut; void main() { VertexOut.cp_position = in_position.xyz; }
#version 400 layout (vertices = 4) out; in VertexData { vec3 cp_position; } VertexIn[]; out VertexData { vec3 ep_position; } VertexOut[]; uniform float tessLevelInner = 3.0f; uniform float tessLevelOuter = 4.0f; void main() { VertexOut[ gl_InvocationID ].ep_position = VertexIn[ gl_InvocationID ].cp_position; gl_TessLevelInner[0] = tessLevelInner; gl_TessLevelInner[1] = tessLevelInner; gl_TessLevelOuter[0] = tessLevelOuter; gl_TessLevelOuter[1] = tessLevelOuter; gl_TessLevelOuter[2] = tessLevelOuter; gl_TessLevelOuter[3] = tessLevelOuter; }
#version 400 layout (quads, equal_spacing, ccw ) in; in VertexData { vec3 ep_position; } VertexIn[]; out vec4 vsout_col; void main() { vec3 p0 = mix( VertexIn[0].ep_position, VertexIn[1].ep_position, gl_TessCoord.x ); vec3 p1 = mix( VertexIn[2].ep_position, VertexIn[3].ep_position, gl_TessCoord.x ); vec3 pos = mix( p0, p1, gl_TessCoord.y ); gl_Position = vec4( pos, 1.0 ); }\n"
#version 140 in vec4 vsout_col; out vec4 out_color0; void main(void) { out_color0 = vec4(1.0); }
ざっと説明しておくと、コントロールポイント(パッチデータの頂点群のこと)をそのままテセレーションのシェーダーに送っています。今は四角形のパッチを送っており、Tessellation Control Shader ではその頂点をそのまま加工せずに次のテセレーターに渡すべくgl_InvocationID という変数を使って出力しています。またポリゴンが割られた後の位置データを Tessellation Evaluation Shader で処理しています。割ったときの計数が gl_TessCoordに格納されているので、これを用いて線形補間するような形で頂点の現在位置を計算しています。
これらポリゴンを割る際の制御データは Tessellation Control Shader 内の gl_TessLevelInner, gl_TessLevelOuter という変数に格納することになっています。これらのデータがどこに対応するかを後ほど図示します。
OpenGL C++側の準備
頂点データを用意するところまでは従来と変わりません。変わるところは、描画直前の設定および描画についてです。
glPatchParameteri( GL_PATCH_VERTICES, 4 ); // 今4角形パッチなので. glDrawArrays( GL_PATCHES, 0, 4 );
このような感じでパッチのデータで描画を実行します。
実行結果
今回、下記の図に示すような実行結果が得られました。
先ほど話していた、gl_TessLevelInner,gl_TessLevelOuter がどこの位置に対応するかについても同時に示しておきます。対応する辺(もしくは位置)をどれだけで割るかを指定するようになっているようです。
おまけ:三角形ではどのようになるか
三角形パッチについても今回と同じようにテセレーションをかけることができます。その際に大きく変わるのは gl_TessLevelInnter,Outerの対応位置と、gl_TessCoord の使用範囲になります。他、4頂点指定していた部分やプリミティブをquads としていた部分が変更になります。
glPatchParameteri( GL_PATCH_VERTICES, 3 ); glDrawArrays( GL_PATCHES, 0, 3 );
#version 400 layout (vertices = 3) out; in VertexData { vec3 cp_position; } VertexIn[]; out VertexData { vec3 ep_position; } VertexOut[]; uniform float tessLevelInner = 3.0f; uniform float tessLevelOuter = 4.0f; void main() { VertexOut[ gl_InvocationID ].ep_position = VertexIn[ gl_InvocationID ].cp_position; gl_TessLevelInner[0] = tessLevelInner; gl_TessLevelOuter[0] = tessLevelOuter; gl_TessLevelOuter[1] = tessLevelOuter; gl_TessLevelOuter[2] = tessLevelOuter; }
#version 400 layout (triangles, equal_spacing, ccw ) in; in VertexData { vec3 ep_position; } VertexIn[]; out vec4 vsout_col; void main() { vec3 p0 = VertexIn[0].ep_position * gl_TessCoord.x; vec3 p1 = VertexIn[0].ep_position * gl_TessCoord.x; vec3 p2 = VertexIn[0].ep_position * gl_TessCoord.x; vec3 pos = p0+p1+p2; gl_Position = vec4( pos, 1.0 ); }\n"
これで実行してみるとこのような感じになります。
まとめ
最小限のポリゴンデータ(パッチデータ)でテセレーションを試してみることができました。個人的には結構難解に感じました。日本語のサンプルや実際にやってみた人のブログが見つからなかったというのもあります。あまり試してみている人がいないのでしょうか・・・DirectXだったらやっている人が少々いるみたいなのですが。