OpenGL 3.2 GeometryShader をもう少し使ってみる

前々回は 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 に渡すことが出来ます。

Geometry Shader
↑基本の状態

// 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 をまとめてフラットシェーディング風にしてみます。(↓)

Geometry Shader

// 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 に
分かれて見えてしまいます。今回は気にしないでいきます。

さらに座標もずらしてみます。(↓)

Geometry Shader

// 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)でずらした結果(↓)
Geometry Shader

// 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 に注意。

Geometry Shader

// 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 3.2 GeometryShader をもう少し使ってみる」への4件のフィードバック

  1. S.W.

    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内での上記宣言をした場合に問題なくいくかどうかを試していただければ幸いです。

  2. S.W.

    すみません。書き忘れていたのですが、
    上記の宣言の仕方ではなく、
    in vec3 vposition[3];
    というシンプルな書き方をした場合には問題は起こりません。

  3. oga 投稿作成者

    仕事が忙しすぎてしばらくは無理かもしれません。
    すみません。
    GL 3.3 で何か変わったのかもしれません。
    ベースとなる GL のコンテキストを 3.2 でつくり直してみては
    いかがでしょうか。
    余裕があればまた新しいドライバで試してみたいと思います。

  4. S.W.

    いえいえとんでもないです。
    時間のあるとき、気が向いたときにでも試していただければ十分です。

コメントは停止中です。