2009/09/03
OpenGL 3.2 の GeometryShader
OpenGL 3.2 では core 機能に Geometry Shader が含まれています。
試してみました。
・OpenGL Registry
GeometryShader はポリゴンの面単位で実行可能なシェーダープログラムです。
面単位なので、プリミティブタイプが Triangle List (GL_TRIANGLES) なら一度に
3頂点を入力として受け取ります。主な用途は下記の通りです。
・頂点演算
・面単位のマテリアル演算
・形状の操作
・出力先の変更
・StreamOutput (Transform Feedback)
わかりやすいところでは面法線の生成など面単位のマテリアル設定が考えられます。
ユニークなのは入力と関係なく出力プリミティブを組み立てられることです。
出力頂点数は任意なので、エッジだけ Line として書き出したり、面を分割したり、
面を消すことも出来ます。
例えば入力を PointList (GL_POINTS) で 1頂点だけ受け取り、Triangle で
2 プリミティブ出力すればポイントスプライトの代わりになるわけです。
出力先の変更は Direct3D だと RenderTarget Array, OpenGL では
Layered Rendering と呼ばれているようです。
Geometry Shader で書き込むフレームバッファの選択が可能で、Cube Map への
レンダリングも一回で出来る仕様になっています。
入力可能なプリミティブタイプも Direct3D 10 同様に増えています。
下記は増えた分のみ。使えるのは GeometryShader が有効な場合だけに限られます。
あまり馴染みがない名称ですが、極端な言い方をすれば 4,6 頂点のプリミティブが
使えるようになったということです。
GL_TRIANGLES だと GeometryShader は 3 頂点単位で受け取りますが、
GL_TRIANGLES_ADJACENCY では一度に 6頂点受け取れることになります。
GL_LINES_ADJACENCY の 4頂点は、QUAD プリミティブの代わりとしても使えます。
ちなみに Direct3D 11 の ShaderModel 5.0 はさらに増えていて、テセレータ用に
32頂点まで任意の数の頂点を入力できるようになっています。
今回使用したビデオカードは GeForce GTX280。ステートを調べると下記の通りです。
頂点の生成が最大 1024 float なのも Direct3D 10 の ShaderModel 4.0 と同じです。
● GeometryShader のコンパイル
Shader の読み込みとコンパイル手順はこれまでの VertexShader, FragmentShader
と同じです。もともとシェーダーしか使えない OpenGL ES 2.0 を使っていたため、
ちょうど GeometryShader の部分が追加された形になります。
エラー判定は省いてあります。
次はコンパイルしたシェーダーをリンクして Program の作成です。
これまで VertexShader, FragmentShader だけ attach していたところに
GeometryShader が加わります。
●ドライバの補足
GLSL の GeometryShader の書き方がわからなかったため、ドキュメント
OpenGL Shadeing Language 1.50.09 と Direct3D の知識を頼りに進めました。
最初はコンパイルすらうまく通らず、EmitVertex を用いると
「#extension GL_EXT_geometry_shader4 : enable」
を宣言しなさいとエラーが出てしまいます。
「#version 150」で GLSL 1.5 を指定すると未対応のバージョンだといわれます。
140 なら通ります。ドライバを調べてみると、最新版 190.62 は OpenGL 3.1 しか
対応していませんでした。
ドライバが OpenGL 3.2 / GLSL 1.5 に対応していなかったことが原因です。
・NVIDIA OpenGL 3.0/3.1/3.2 Support for Windows, Linux, FreeBSD, and Solaris
上のページにある、OpenGL 3.2 対応ドライバ 190.57 を入れたら、#version 150 指定
による GLSL 1.5 も、GeometryShader 専用命令も通るようになりました。
「#version 150」とだけ書いておけばよく、extension 指定は不要です。
● GLSL v1.50
「#version 150」を指定したら VertexShader でもエラーが出るようになりました。
attribute, varying 宣言は古いので、in, out を使えと言われます。
もともと attribute は頂点からの入力、varying はラスタライザの補間パラメータを
意味しています。出力先が GeometryShader かもしれないし、シェーダーパイプラインが
多層化&汎用化されたので名称が変わったものと思われます。
OpenGL ES 2.0 との互換性も残したいのでとりあえず define でごまかしました。
attribute を in に、varying を out に置き換えます。
Matrix 周りが少々不自然なのは、OpenGL ES 2.0 に mat4x3 が無かったことと頂点
アニメーションを削ったためです。もともと Skinning の処理が入っていました。
同じように Fragment Shader も書き換えます。
FragmentShader の場合 VertexShader と逆で varying が in になります。
GeometryShader は最初から in/out で記述します。
プリミティブのタイプは layout() で宣言しています。
入力は block 宣言を使っており、Triangle なので 3頂点分のデータを受け取ります。
VertexShader が書き込んだシステム変数 gl_Position の値は、こちらも builtin
の gl_in[] という配列で受け取れます。
出力はこれまで通り out (varying) 宣言した変数に書き込みます。
必要なパラメータを書き込んだ後に EmitVertex() 命令で頂点が確定します。
プリミティブの区切りは EndPrimitive() 命令です。
このジオメトリシェーダーは何もせずに、頂点の出力をそのままスルーしています。
上の (1) の行を削除すると面に同じ法線が適用されるため、フラットシェーディング
風の見た目になるはずです。
同じシェーダーを DirectX の HLSL で書くとこんな感じです。
書式が違うだけでほとんど同じです。
Direct3D でも、アセンブラ出力すると頂点の生成は emit 命令なのです。
関連エントリ
・OpenGL のはじめ方
・OpenGL ES 2.0 Emulator
・OpenGL ES 2.0
・OpenGLES2.0 DDS テクスチャを読み込む
・OpenGLES2.0 Direct3D とのフォーマット変換
・OpenGLES 2.0 頂点フォーマットの管理
・OpenGLES2.0 の頂点
・OpenGLES2.0 D3D座標系
・OpenGLES2.0 シェーダー管理
試してみました。
・OpenGL Registry
GeometryShader はポリゴンの面単位で実行可能なシェーダープログラムです。
面単位なので、プリミティブタイプが Triangle List (GL_TRIANGLES) なら一度に
3頂点を入力として受け取ります。主な用途は下記の通りです。
・頂点演算
・面単位のマテリアル演算
・形状の操作
・出力先の変更
・StreamOutput (Transform Feedback)
わかりやすいところでは面法線の生成など面単位のマテリアル設定が考えられます。
ユニークなのは入力と関係なく出力プリミティブを組み立てられることです。
出力頂点数は任意なので、エッジだけ Line として書き出したり、面を分割したり、
面を消すことも出来ます。
例えば入力を PointList (GL_POINTS) で 1頂点だけ受け取り、Triangle で
2 プリミティブ出力すればポイントスプライトの代わりになるわけです。
出力先の変更は Direct3D だと RenderTarget Array, OpenGL では
Layered Rendering と呼ばれているようです。
Geometry Shader で書き込むフレームバッファの選択が可能で、Cube Map への
レンダリングも一回で出来る仕様になっています。
入力可能なプリミティブタイプも Direct3D 10 同様に増えています。
下記は増えた分のみ。使えるのは GeometryShader が有効な場合だけに限られます。
GL_LINES_ADJACENCY GL_LINE_STRIP_ADJACENCY GL_TRIANGLES_ADJACENCY GL_TRIANGLE_STRIP_ADJACENCY
あまり馴染みがない名称ですが、極端な言い方をすれば 4,6 頂点のプリミティブが
使えるようになったということです。
GL_TRIANGLES だと GeometryShader は 3 頂点単位で受け取りますが、
GL_TRIANGLES_ADJACENCY では一度に 6頂点受け取れることになります。
GL_LINES_ADJACENCY の 4頂点は、QUAD プリミティブの代わりとしても使えます。
ちなみに Direct3D 11 の ShaderModel 5.0 はさらに増えていて、テセレータ用に
32頂点まで任意の数の頂点を入力できるようになっています。
今回使用したビデオカードは GeForce GTX280。ステートを調べると下記の通りです。
頂点の生成が最大 1024 float なのも Direct3D 10 の ShaderModel 4.0 と同じです。
GL_MAX_GEOMETRY_OUTPUT_VERTICES = 1024 GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS = 32 GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = 1024
● GeometryShader のコンパイル
Shader の読み込みとコンパイル手順はこれまでの VertexShader, FragmentShader
と同じです。もともとシェーダーしか使えない OpenGL ES 2.0 を使っていたため、
ちょうど GeometryShader の部分が追加された形になります。
GLuint vshader= glCreateShader( GL_VERTEX_SHADER ); glShaderSource( vshader, 1, vshader_source_text, NULL ); glCompileShader( vshader ); GLuint gshader= glCreateShader( GL_GEOMETRY_SHADER ); glShaderSource( gshader, 1, gshader_source_text, NULL ); glCompileShader( gshader ); GLuint pshader= glCreateShader( GL_FRAGMENT_SHADER ); glShaderSource( pshader, 1, pshader_source_text, NULL ); glCompileShader( pshader );
エラー判定は省いてあります。
次はコンパイルしたシェーダーをリンクして Program の作成です。
これまで VertexShader, FragmentShader だけ attach していたところに
GeometryShader が加わります。
GLuint shaderProgram= glCreateProgram(); glAttachShader( shaderProgram, vshader ); glAttachShader( shaderProgram, gshader ); glAttachShader( shaderProgram, pshader ); glLinkProgram( shaderProgram );
●ドライバの補足
GLSL の GeometryShader の書き方がわからなかったため、ドキュメント
OpenGL Shadeing Language 1.50.09 と Direct3D の知識を頼りに進めました。
最初はコンパイルすらうまく通らず、EmitVertex を用いると
「#extension GL_EXT_geometry_shader4 : enable」
を宣言しなさいとエラーが出てしまいます。
「#version 150」で GLSL 1.5 を指定すると未対応のバージョンだといわれます。
140 なら通ります。ドライバを調べてみると、最新版 190.62 は OpenGL 3.1 しか
対応していませんでした。
ドライバが OpenGL 3.2 / GLSL 1.5 に対応していなかったことが原因です。
・NVIDIA OpenGL 3.0/3.1/3.2 Support for Windows, Linux, FreeBSD, and Solaris
上のページにある、OpenGL 3.2 対応ドライバ 190.57 を入れたら、#version 150 指定
による GLSL 1.5 も、GeometryShader 専用命令も通るようになりました。
「#version 150」とだけ書いておけばよく、extension 指定は不要です。
● GLSL v1.50
「#version 150」を指定したら VertexShader でもエラーが出るようになりました。
attribute, varying 宣言は古いので、in, out を使えと言われます。
もともと attribute は頂点からの入力、varying はラスタライザの補間パラメータを
意味しています。出力先が GeometryShader かもしれないし、シェーダーパイプラインが
多層化&汎用化されたので名称が変わったものと思われます。
OpenGL ES 2.0 との互換性も残したいのでとりあえず define でごまかしました。
attribute を in に、varying を out に置き換えます。
// sysdef2.vsh (Vertex Shader) #define attribute in #define varying out #version 150 uniform vec4 World[3]; uniform mat4 PView; attribute vec3 POSITION; attribute vec3 NORMAL; attribute vec2 TEXCOORD; varying vec3 onormal; varying vec2 otexcoord; void main() { gl_Position= vec4( POSITION.xyz, 1 ) * PView; vec3 nvec; nvec.x= dot( NORMAL.xyz, World[0].xyz ); nvec.y= dot( NORMAL.xyz, World[1].xyz ); nvec.z= dot( NORMAL.xyz, World[2].xyz ); onormal= nvec; otexcoord= TEXCOORD; }
Matrix 周りが少々不自然なのは、OpenGL ES 2.0 に mat4x3 が無かったことと頂点
アニメーションを削ったためです。もともと Skinning の処理が入っていました。
同じように Fragment Shader も書き換えます。
// sysdef2.fsh (Fragment Shader) #define varying in #version 150 varying vec3 onormal; varying vec2 otexcoord; uniform sampler2D ColorMap; void main() { vec3 lightdir= vec3( 0.0, 0.0, -1.0 ); float lv= clamp( dot( normalize( onormal ), lightdir ), 0.0, 1.0 ) + 0.5; vec4 color= vec4( 1.0, 1.0, 1.0, 1.0 ) * lv; vec4 tcol= texture2D( ColorMap, otexcoord ); gl_FragColor= color * tcol; }
FragmentShader の場合 VertexShader と逆で varying が in になります。
GeometryShader は最初から in/out で記述します。
// sysdef2.gsh (Geometry Shader) #version 150 layout(triangles) in; layout(triangle_strip, max_vertices= 3) out; in Inputs { vec3 onormal; vec2 otexcoord; } gin[3]; out vec3 onormal; out vec2 otexcoord; void main() { gl_Position= gl_in[0].gl_Position; onormal= gin[0].onormal; otexcoord= gin[0].otexcoord; EmitVertex(); gl_Position= gl_in[1].gl_Position; onormal= gin[1].onormal; // (1) otexcoord= gin[1].otexcoord; EmitVertex(); gl_Position= gl_in[2].gl_Position; onormal= gin[2].onormal; // (1) otexcoord= gin[2].otexcoord; EmitVertex(); EndPrimitive(); }
プリミティブのタイプは layout() で宣言しています。
入力は block 宣言を使っており、Triangle なので 3頂点分のデータを受け取ります。
VertexShader が書き込んだシステム変数 gl_Position の値は、こちらも builtin
の gl_in[] という配列で受け取れます。
出力はこれまで通り out (varying) 宣言した変数に書き込みます。
必要なパラメータを書き込んだ後に EmitVertex() 命令で頂点が確定します。
プリミティブの区切りは EndPrimitive() 命令です。
このジオメトリシェーダーは何もせずに、頂点の出力をそのままスルーしています。
上の (1) の行を削除すると面に同じ法線が適用されるため、フラットシェーディング
風の見た目になるはずです。
同じシェーダーを DirectX の HLSL で書くとこんな感じです。
// HLSL Geometry Shader struct GS_INPUT { float4 Position : POSITION; float3 Normal : NORMAL; float2 Texcoord : TEXCOORD; }; struct GS_OUTPUT { float4 Position : SV_POSITION; float3 Normal : NORMAL; float2 Texcoord : TEXCOORD; }; [maxvertexcount(3)] void GS_Main( triangle GS_INPUT In[3], inout TriangleStream<GS_OUTPUT> gsstream ) { GS_OUTPUT Out; Out.Position= In[0].Position; Out.Texcoord= In[0].Texcoord; Out.Normal = In[0].Normal; gsstream.Append( Out ); Out.Position= In[1].Position; Out.Texcoord= In[1].Texcoord; Out.Normal = In[1].Normal; gsstream.Append( Out ); Out.Position= In[2].Position; Out.Texcoord= In[2].Texcoord; Out.Normal = In[2].Normal; gsstream.Append( Out ); gsstream.RestartStrip(); }
書式が違うだけでほとんど同じです。
Direct3D でも、アセンブラ出力すると頂点の生成は emit 命令なのです。
関連エントリ
・OpenGL のはじめ方
・OpenGL ES 2.0 Emulator
・OpenGL ES 2.0
・OpenGLES2.0 DDS テクスチャを読み込む
・OpenGLES2.0 Direct3D とのフォーマット変換
・OpenGLES 2.0 頂点フォーマットの管理
・OpenGLES2.0 の頂点
・OpenGLES2.0 D3D座標系
・OpenGLES2.0 シェーダー管理