日別アーカイブ: 2009年9月5日

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 シェーダー管理