GLSLを用いて単純なジオメトリシェーダを用いたプログラムを実装する. ジオメトリシェーダとは†ジオメトリシェーダはバーテックスシェーダとフラグメントシェーダの間に位置するシェーダである. バーテックスシェーダは頂点ごとの処理,フラグメントシェーダはフラグメント(ピクセル)ごとの処理であり, この二つだけでは,例えば,入力された三角形ポリゴンから点群を生成して描画する, ポリゴンのテッセレーション,ディスプレースメントマップなどの処理は難しい. これらの処理のためにはプリミティブごとの処理を行い,かつ,新しいプリミティブを作り出すことができるシェーダが必要である. これがジオメトリシェーダである. 例えば,ポリゴンのテッセレーションを行いたい場合,1ポリゴンをプリミティブとして入力とし, ジオメトリシェーダ内で新しい複数のポリゴンを生成して出力することで処理を実現できる. このとき,ジオメトリシェーダへの入力,出力はポリゴンとなる. また,ポリゴンから点群を生成したい場合,1ポリゴンをプリミティブとして入力して, ジオメトリシェーダ内で新しい点群を生成して出力する. このときの入力はポリゴンで出力は点となる. このように,ジオメトリシェーダを用いる場合,その入力と出力プリミティブが場合によって異なるため, シェーダプログラムのパラメータとして入力,出力を設定しなければならない. ジオメトリシェーダのビルド†GLSLについてで述べているように,シェーダのビルドを行う必要がある. ジオメトリシェーダのコンパイルは,targetにGL_GEOMETRY_SHADERを渡すこと以外は, 他のシェーダ(頂点シェーダ,フラグメントシェーダ)と同じであるので,GLSLについてを参照してほしい. 他のシェーダとビルドにおいて異なるのがシェーダへの入出力の設定で, これはシェーダオブジェクト登録(glAttachShader)とプログラムのリンク(glLinkProgram)の間で行う. 入出力設定にはglProgramParameterを用いる. void glProgramParameteri(GLuint program, GLenum pname, GLint value); programにglCreateProgramで作ったプログラムオブジェクト,pname,valueにパラメータと値を設定する.パラメータと値は,
ジオメトリシェーダを含むプログラムのリンクを行う関数の例を以下に示す. /*! * バーテックス/ジオメトリ/フラグメントシェーダで構成されるGLSLプログラム作成 * @param[in] vs バーテックスシェーダオブジェクト * @param[in] gs ジオメトリシェーダオブジェクト * @param[in] fs フラグメントシェーダオブジェクト * @param[in] input_type ジオメトリシェーダへの入力タイプ * @param[in] output_type ジオメトリシェーダからの出力タイプ * @param[in] vertex_out ジオメトリシェーダの最大出力頂点数 * @return GLSLプログラムオブジェクト */ inline GLuint LinkGLSLProgram(GLuint vs, GLuint gs, GLuint fs, GLint input_type, GLint output_type, GLint vertex_out) { // プログラムオブジェクト作成 GLuint program = glCreateProgram(); // シェーダオブジェクトを登録 glAttachShader(program, vs); glAttachShader(program, gs); glAttachShader(program, fs); // ジオメトリシェーダへの入出力 glProgramParameteriEXT(program, GL_GEOMETRY_INPUT_TYPE_EXT, input_type); glProgramParameteriEXT(program, GL_GEOMETRY_OUTPUT_TYPE_EXT, output_type); glProgramParameteriEXT(program, GL_GEOMETRY_VERTICES_OUT_EXT, vertex_out); // プログラムのリンク glLinkProgram(program); // エラー出力 GLint charsWritten, infoLogLength; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); char * infoLog = new char[infoLogLength]; glGetProgramInfoLog(program, infoLogLength, &charsWritten, infoLog); printf(infoLog); delete [] infoLog; // リンカテスト GLint linkSucceed = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linkSucceed); if(linkSucceed == GL_FALSE){ glDeleteProgram(program); return 0; } return program; } シェーダソースファイルからコンパイル,リンクを行う関数の例を以下に示す. 返値はrxGLSL型の変数となっており,シェーダファイル名,プログラムオブジェクトなどを格納する構造体である. struct rxGLSL { string VertProg; //!< 頂点プログラムファイル名 string GeomProg; //!< ジオメトリプログラムファイル名 string FragProg; //!< フラグメントプログラムファイル名 string Name; //!< シェーダ名 GLuint Prog; //!< シェーダID }; /*! * GLSLのコンパイル・リンク(ファイルより,ジオメトリシェーダを含む) * @param[in] vs 頂点シェーダファイルパス * @param[in] gs ジオメトリシェーダファイルパス * @param[in] fs フラグメントシェーダファイルパス * @param[in] name プログラム名 * @param[in] input_type ジオメトリシェーダへの入力タイプ * @param[in] output_type ジオメトリシェーダからの出力タイプ * @param[in] vertex_out ジオメトリシェーダの最大出力頂点数 * @return GLSLオブジェクト */ inline rxGLSL CreateGLSLFromFile(const string &vs, const string &gs, const string &fs, const string &name, GLint input_type, GLint output_type, GLint vertex_out = -1) { rxGLSL glsl; glsl.VertProg = vs; glsl.GeomProg = gs; glsl.FragProg = fs; glsl.Name = name; GLuint v, g, f; v = CompileGLSLShaderFromFile(GL_VERTEX_SHADER, vs.c_str()); g = CompileGLSLShaderFromFile(GL_GEOMETRY_SHADER, gs.c_str()); f = CompileGLSLShaderFromFile(GL_FRAGMENT_SHADER, fs.c_str()); if(vertex_out < 0){ glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &vertex_out); } glsl.Prog = LinkGLSLProgram(v, g, f, input_type, output_type, vertex_out); return glsl; } もし,vertex_outに負の値を指定したらシステムの最大出力頂点数を調べて設定する. 最大出力頂点数について†上記のCreateGLSLFromFileではGL_MAX_GEOMETRY_OUTPUT_VERTICESでシステムの最大出力頂点数を調べて設定しているが, ジオメトリシェーダでは最大出力頂点数の他に最大出力要素数(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS)も制限されている. 最大出力要素数,最大出力頂点数を調べて出力するコード例を以下に示す. GLint nc, nv; glGetIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, &nc); cout << "maximum number of components : " << nc << endl; glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &nv); cout << "max output vertices : " << nv << endl; GeForce GTX 480を持つPC上で実行した結果を以下に示す(OpenGLバージョン,GLEWバージョンも出力した). OpenGL Ver. 4.1.0 Glew Ver. 1.5.7 maximum number of components : 1024 max output vertices : 1024 最大出力要素数における要素とは頂点における座標や色のことであり, 通常1頂点は8つの要素(4つの位置要素と4つの色要素)を持つ. そうすると,この環境上では最大出力頂点数は1024となっているが,実際には最大出力要素数の制限から 128(=1024/8)に制限される.位置要素のみにした場合でも256(=1024/4)となる. ジオメトリシェーダのビルドイン変数と関数†頂点シェーダのgl_PositionやglTexCoord[]のようにジオメトリシェーダにもビルドイン変数がいくつか用意されている. 頂点シェーダとフラグメントシェーダのみの場合は,これらのビルドイン変数はシェーダ間で共通になっていたが, ジオメトリシェーダでは頂点シェーダで処理された複数の頂点を読み込む場合があり, そのため,in変数とout変数で異なるものが定義されている. 例えば,頂点シェーダでgl_Positionに設定された値は, ジオメトリシェーダ側からはgl_PositionIn[]配列を用いてアクセスできる. この配列の大きさは変数gl_VerticesInで定義され,gl_VerticesInの値はGL_GEOMETRY_INPUT_TYPE_EXTにどれを指定したかで決定される. これらの値を用いてジオメトリシェーダの出力用変数gl_Position変数に値を設定後,ビルドイン関数であるEmitVertex()を実行することで ラスタライザに頂点座標が出力される. ビルドイン変数,関数を以下に示す. varying in vec4 gl_FrontColorIn[gl_VerticesIn]; varying in vec4 gl_BackColorIn[gl_VerticesIn]; varying in vec4 gl_FrontSecondaryColorIn[gl_VerticesIn]; varying in vec4 gl_BackSecondaryColorIn[gl_VerticesIn]; varying in vec4 gl_TexCoordIn[gl_VerticesIn][]; varying in float gl_FogFragCoordIn[gl_VerticesIn]; varying in vec4 gl_PositionIn[gl_VerticesIn]; varying in float gl_PointSizeIn[gl_VerticesIn]; varying in vec4 gl_ClipVertexIn[gl_VerticesIn]; varying out vec4 gl_FrontColor; varying out vec4 gl_BackColor; varying out vec4 gl_FrontSecondaryColor; varying out vec4 gl_BackSecondaryColor; varying out vec4 gl_TexCoord[]; varying out float gl_FogFragCoord; void EmitVertex(); void EndPrimitive(); 具体的な使い方は下記のコード例を参照. ユーザ定義のvarying変数†ビルドイン変数と同様にユーザ定義の変数を用いて頂点シェーダからジオメトリシェーダへ, ジオメトリシェーダからラスタライザを介してフラグメントシェーダに値を渡すことができる. 注意として,頂点シェーダとフラグメントシェーダのみの場合と異なり,varying変数が入力(varying in)か出力(varying out)かを 明示的に指定する必要がある.また,頂点シェーダの出力とジオメトリシェーダの入力は数が異なるので, ジオメトリシェーダ側の変数は同じ変数名の配列となる. 例えば,頂点シェーダ側で varying float Alpha; varying vec3 Nrm; とした場合,ジオメトリシェーダでは, varying in float Alpha[3]; varying in vec3 Nrm[3]; とする.ただし,ここでは入力としてGL_TRIANGLESが指定されたとしている. フラグメントシェーダに出力する場合,ジオメトリシェーダでは, varying out vec3 LightPos; のようにし,これをEmitVertex()する各頂点で値を設定する. LightPos = ... EmitVertex(); フラグメントシェーダでは, varying vec3 LightPos; として受け取る. ジオメトリシェーダの例†ジオメトリシェーダを用いた単純な例として,入力された三角形ポリゴンを縮小して描画する例を示す. 三角形の重心を縮小の中心として利用する. 頂点シェーダだけでは重心を求めることができないためジオメトリシェーダを用いる. まず,頂点シェーダではPhong反射モデルに基づき頂点色を決定する. /*! @file simple.vs @brief GLSL頂点シェーダ - ジオメトリシェーダのテスト @author Makoto Fujisawa @date 2011 */ #version 120 uniform vec3 eyePosition; void main(void) { // 光源 vec3 La = gl_LightSource[0].ambient.xyz; // ライト環境光 vec3 Ld = gl_LightSource[0].diffuse.xyz; // ライト拡散反射光 vec3 Ls = gl_LightSource[0].specular.xyz; // ライト鏡面反射光 vec3 Lp = gl_LightSource[0].position.xyz; // ライト位置 // 材質 vec3 Ke = gl_FrontMaterial.emission.xyz; // 放射色 vec3 Ka = gl_FrontMaterial.ambient.xyz; // 環境光 vec3 Kd = gl_FrontMaterial.diffuse.xyz; // 拡散反射 vec3 Ks = gl_FrontMaterial.specular.xyz; // 鏡面反射 float shine = gl_FrontMaterial.shininess; vec3 P = (gl_ModelViewMatrix*gl_Vertex).xyz; vec3 V = normalize(-P); // 視線ベクトル vec3 N = normalize(gl_NormalMatrix*gl_Normal); // 法線ベクトル vec3 L = normalize(Lp-P); // ライトベクトル // 放射色の計算 vec3 emissive = Ke; // 環境光の計算 vec3 ambient = Ka*La; // 拡散反射の計算 float diffuseLight = max(dot(L, N), 0.0); vec3 diffuse = Kd*Ld*diffuseLight; // 鏡面反射の計算 vec3 H = normalize(L+V); float specularLight = pow(max(dot(H, N), 0.0), shine); if(diffuseLight <= 0.0) specularLight = 0.0; vec3 specular = Ks*Ls*specularLight; gl_FrontColor = vec4(emissive+ambient+diffuse+specular, 1); gl_Position = gl_Vertex; } ジオメトリシェーダでは頂点の情報をビルドイン変数の varying in vec4 gl_PositionIn[gl_VerticesIn]; から取得して,三角形ポリゴンの重心を算出し,さらに, varying in vec4 gl_FrontColorIn[gl_VerticesIn]; にから頂点色を取得して設定する. 重心座標に基づき縮小したポリゴン頂点座標を計算して,gl_Positionに設定する. gl_FrontColor, gl_Positionに頂点情報を設定したら,ジオメトリシェーダ用の関数 void EmitVertex(); を呼び出して,頂点の設定を完了する.これを三角形ポリゴンの3頂点全てで行った後, void EndPrimitive(); を呼び出して,プリミティブ(ここでは三角形ポリゴン)の設定を完了する. /*! @file simple.gs @brief GLSLジオメトリシェーダ - ジオメトリシェーダのテスト @author Makoto Fujisawa @date 2011 */ #version 120 #extension GL_EXT_geometry_shader4 : enable void main(void) { // 三角形ポリゴンの重心 vec3 mg = vec3(0.0); for(int i = 0; i < gl_VerticesIn; ++i){ mg += gl_PositionIn[i].xyz; } mg /= float(gl_VerticesIn); // 重心を原点としてポリゴンを縮小して描画 vec3 v, p; for(int i = 0; i < gl_VerticesIn; ++i){ gl_FrontColor = gl_FrontColorIn[i]; v = mg-gl_PositionIn[i].xyz; p = gl_PositionIn[i].xyz+0.2*v; gl_Position = gl_ModelViewProjectionMatrix*vec4(p, 1); EmitVertex(); } EndPrimitive(); } フラグメントシェーダでは単純に色をピクセルに渡すだけである. /*! @file simple.fs @brief GLSLフラグメントシェーダ - ジオメトリシェーダのテスト @author Makoto Fujisawa @date 2011 */ #version 120 void main(void) { gl_FragColor = gl_Color; } レンダリング結果†レンダリング結果を以下に示す. ソースコード† |