前々回は VertexShaded から GeometryShader へのパラメータ渡しで
built-in のシステム変数 gl_Position を使いました。
VertexShader: gl_Position= vec4( POSITION.xyz, 1.0 ) * PView; GeomteryShader: position= gl_in[0].gl_Position;
gl_Position を使うメリットは下記の通りです。
・宣言なしに使うことが出来る
・VertexShader の段階で書き込んでいるので GeomteryShader が無くても描画できる
前々回のような単なるスルー出力では GeomteryShader が無くても描画可能です。
VertexShader で gl_Position に値を書き込んでいたので、GeomteryShader を attach
せずにそのまま描画することが出来ました。
ただし、常に VertexShader の段階で座標が確定するとは限りません。
頂点からの座標値も、普通の変数を介して GeomteryShader に渡すことが出来ます。
↑基本の状態
// GLSL 1.5 VertexShader gl_Position を使わない #version 150 uniform vec4 World[3]; uniform mat4 PView; in vec3 POSITION; in vec3 NORMAL; in vec2 TEXCOORD; out vec4 vPosition; out vec3 vNormal; out vec2 vTexcoord; void main() { vPosition= vec4( POSITION.xyz, 1 ) * PView; vNormal.x= dot( NORMAL.xyz, World[0].xyz ); vNormal.y= dot( NORMAL.xyz, World[1].xyz ); vNormal.z= dot( NORMAL.xyz, World[2].xyz ); vTexcoord= TEXCOORD; }
// GLSL 1.5 GeometryShader #version 150 layout(triangles) in; layout(triangle_strip, max_vertices= 3) out; in Inputs { vec4 vPosition; vec3 vNormal; vec2 vTexcoord; } gin[3]; out vec3 vNormal; out vec2 vTexcoord; void main() { for( int i= 0 ; i< 3 ; i++ ){ gl_Position= gin[i].vPosition; vNormal= gin[i].vNormal; vTexcoord= gin[i].vTexcoord; EmitVertex(); } EndPrimitive(); }
gl_in を使用していません。
面毎に法線と texcoord をまとめてフラットシェーディング風にしてみます。(↓)
// GeometryShader フラット化 void main() { vNormal= normalize( gin[0].vNormal + gin[1].vNormal + gin[2].vNormal ); vTexcoord= (gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord)/3; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= gin[i].vPosition; EmitVertex(); } EndPrimitive(); }
きちんと法線を算出している訳じゃないので、本来平面のはずの Quad が Triangle に
分かれて見えてしまいます。今回は気にしないでいきます。
さらに座標もずらしてみます。(↓)
// GeometryShader 座標もずらす void main() { vec3 vnormal= normalize(gin[0].vNormal + gin[1].vNormal + gin[2].vNormal); vNormal= vnormal; vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3; vec3 offset= vnormal.xyz*2.0; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= gin[i].vPosition + vec4( offset.xy, 0.0, 0.0 ); EmitVertex(); } EndPrimitive(); }
上のスクリーンショットではわからないかもしれませんが、投影後の 2D 座標がずれる
だけなので、形状として厳密に正しい物ではないです。
3D で変形を行うには、Geometry Shader で頂点演算を行う必要があります。
Vertex Shader の方がスルー出力になります。
座標値が vec3 になっている点に注意。任意の型やデータをやりとりできるのが
gl_Position を使わない場合の利点です。
なお正しく面法線を求めていれば、三角形ではなく四角形に押し出されていたはず。
3D 座標(local)でずらした結果(↓)
// GLSL 1.5 VertexShader #version 150 in vec3 POSITION; in vec3 NORMAL; in vec2 TEXCOORD; out vec3 vPosition; // vec4 → vec3 out vec3 vNormal; out vec2 vTexcoord; void main() { vPosition= POSITION; vNormal= NORMAL; vTexcoord= TEXCOORD; }
// GLSL 1.5 GeometryShader #version 150 uniform vec4 World[3]; uniform mat4 PView; layout(triangles) in; layout(triangle_strip, max_vertices= 3) out; in Inputs { vec3 vPosition; // vec4 → vec3 vec3 vNormal; vec2 vTexcoord; } gin[3]; out vec3 vNormal; out vec2 vTexcoord; vec4 transform( vec3 pos ) { return vec4( pos, 1 ) * PView; } void main() { vec3 vnormal= normalize(gin[0].vNormal + gin[1].vNormal + gin[2].vNormal); vNormal.x= dot( vnormal.xyz, World[0].xyz ); vNormal.y= dot( vnormal.xyz, World[1].xyz ); vNormal.z= dot( vnormal.xyz, World[2].xyz ); vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= transform( gin[i].vPosition + vnormal * 1.5 ); EmitVertex(); } EndPrimitive(); }
以前は VertexShader で受け取っていた uniform 変数 World, PView が
GeometryShader に移動しています。
OpenGL + GLSL の場合、Link したあとに uniform の設定を行うので C++ 側のコード
は何も修正しなくて済みます。結構便利です。
Direct3D だと各シェーダー毎に ConstantBuffer の設定が必要なので、
VSSetConstantBuffers() → GSSetConstantBuffers() と C++ の呼び出し側も
変更しなければなりませんでした。
ここまできたら VertexShader は不要な気もします。
一応試してみましたが VertexShader の省略は出来ませんでした。
Direct3D の Effect (FX) だと VertexShader と GeometryShader に同じ内容を
割り当てることで省略できます。
GeometryShader でポリゴンを増やしてみます。中に元の形状が内部に置いてある状態です。
共有頂点分の演算が重複するので、こういうケースでは VertexShader を併用
した方が効率がよいかもしれません。
out の layout で max_vertices= 6 に注意。
// GLSL 1.5 GeometryShader #version 150 uniform vec4 World[3]; uniform mat4 PView; layout(triangles) in; layout(triangle_strip, max_vertices= 6) out; in Inputs { vec3 vPosition; vec3 vNormal; vec2 vTexcoord; } gin[3]; out vec3 vNormal; out vec2 vTexcoord; vec4 transform( vec3 pos ) { return vec4( pos, 1 ) * PView; } void setnormal( vec3 normal ) { vNormal.x= dot( normal.xyz, World[0].xyz ); vNormal.y= dot( normal.xyz, World[1].xyz ); vNormal.z= dot( normal.xyz, World[2].xyz ); } void main() { for( int i= 0 ; i< 3 ; i++ ){ setnormal( gin[i].vNormal ); vTexcoord= gin[i].vTexcoord; gl_Position= transform( gin[i].vPosition ); EmitVertex(); } EndPrimitive(); vec3 vnormal= normalize( gin[0].vNormal + gin[1].vNormal + gin[2].vNormal ); setnormal( vnormal ); vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= transform( gin[i].vPosition + vnormal * 2.0 ); EmitVertex(); } EndPrimitive(); }
関連エントリ
・OpenGL GLSL のバージョン
・OpenGL 3.2 の GeometryShader
・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の新しいフィーチャーを日本語で扱っているサイトが少なく、以前このページは非常に参考になりました。
OpenTKを使用しC#からOpenGLを利用してます。
以前はこのページを参考にGSを使用したサンプルを記述してみました。当時はVSをほぼスルー出力にし、GSで
in Input{
vec3 vposition;
} VSout[3];
と宣言しGSからはVSout[i].vpositionといった具合に呼び出し問題なく使用できていたのですが、最近OSを7(64bit)に変更したせいか、それともGeforceのドライバーのバージョンを258.96に変更したせいか、とある問題が発生しました。
その問題と言うのは、上記の宣言の仕方をした場合にGSのvpositionへとスルーさせるためのVSのin変数のロケーションが取得できなくなるというものです。
具体的にはGetAttribLocationを使用し該当のVSのin変数を指定した場合に「-1」が返ってきます。
私の環境(8600M GT)だけなのか、最新のGeforceドライバーが問題なのかがわかりません。
そこで非常に図々しく恐縮ですが、管理人さんの環境で現状もVSのスルー出力 + GS内での上記宣言をした場合に問題なくいくかどうかを試していただければ幸いです。
すみません。書き忘れていたのですが、
上記の宣言の仕方ではなく、
in vec3 vposition[3];
というシンプルな書き方をした場合には問題は起こりません。
仕事が忙しすぎてしばらくは無理かもしれません。
すみません。
GL 3.3 で何か変わったのかもしれません。
ベースとなる GL のコンテキストを 3.2 でつくり直してみては
いかがでしょうか。
余裕があればまた新しいドライバで試してみたいと思います。
いえいえとんでもないです。
時間のあるとき、気が向いたときにでも試していただければ十分です。