現実逃避にGeometryShader試してみた。
GL_EXT_geometry_shader4をiMacで動かす実験。
GeometryShaderって普通にコンパイルしてリンクすりゃはいOKなのかと思ったら、最大出力頂点数とか入力プリミティブとか出力プリミティブを別途指定しなきゃならんのね。あんま綺麗じゃないな。
で、ちゃんと指定しないと初期値では最大出力頂点数が0なんでリンク時にエラー。しかしGF8600GTなWindowsマシンではリンクは通ってDrawElementsでINVALID_OPERATIONエラー発生。しっかりしてくれよNVIDIA…と思ったがGL_NV_geometry_shader4でリンク後でも出力頂点数設定できるようになってるからこれで正しいのか。
で、まあいろいろ罠に嵌められながらもちょこちょこ書いてったら動いたー!!
これが元。
GeometryShaderで分身。
動かしたのソースはこれ。Riko使ってるけどシェーダ自体はGLSLなんで。
$:.unshift(File.dirname(__FILE__)) require 'sampleframe' class GLSLGetometrySample < SampleFramework def initialize super(640, 480) @prim = ObjFile.new(File.read("#{PATH}/boxman.obj")).primitive tex = SDL::Surface.load("#{PATH}/texture.tga").to_texture tex.filter = :linear_mipmap_nearest vertex_main = <<EOS uniform mat4 projection_matrix; uniform mat4 view_matrix; uniform mat4 world_matrix; uniform mat3 normal_matrix; varying vec3 normal_geom; void main() { gl_Position = projection_matrix * view_matrix * world_matrix * gl_Vertex; gl_FrontColor = gl_Color; gl_TexCoord[0] = gl_MultiTexCoord0; normal_geom = normal_matrix * gl_Normal; } EOS geometry_main = <<EOS //varying in vec3 normal_geom[gl_VerticesIn]; varying in vec3 normal_geom[3]; varying out vec3 normal; void main() { for (int i=0; i<gl_VerticesIn; i++) { gl_Position = gl_PositionIn[i] + vec4(-0.33, 0.0, 0.0, 0.0) * gl_PositionIn[i].w; gl_FrontColor = gl_FrontColorIn[i]; gl_TexCoord[0] = gl_TexCoordIn[i][0]; normal = normal_geom[i]; EmitVertex(); } EndPrimitive(); for (int i=0; i<gl_VerticesIn; i++) { gl_Position = gl_PositionIn[i] + vec4(+0.33, 0.0, 0.0, 0.0) * gl_PositionIn[i].w; gl_FrontColor = gl_FrontColorIn[i]; gl_TexCoord[0] = gl_TexCoordIn[i][0]; normal = normal_geom[i]; EmitVertex(); } EndPrimitive(); } EOS fragment_main = <<EOS varying vec3 normal; uniform vec3 light_dir; uniform vec4 ambient; uniform sampler2D color_tex; void main() { gl_FragColor = (gl_Color * vec4(vec3(dot(normalize(light_dir.xyz), normalize(normal))), 1.0) + ambient) * texture2D(color_tex, gl_TexCoord[0].xy); } EOS geom_opt = { :geometry_vertices_out => 6, :geometry_input_type => :triangles, :geometry_output_type => :triangle_strip, } @shader = Riko::GLSLShader.compile( vertex_main, fragment_main, geometry_main, geom_opt) @shader.uniform('color_tex', tex) @shader.uniform('light_dir', Riko::Vector.new(1.0, 1.0, -1.0)) @shader.uniform('ambient', Riko::Vector.new(0.25, 0.25, 0.25, 0.0)) @shader.uniform('projection_matrix') { projection_matrix } @shader.uniform('view_matrix') { view_matrix } @shader.uniform('world_matrix') { world_matrix } @shader.uniform('normal_matrix') { (view_matrix*world_matrix).invert.transpose } end def main_loop Riko::Context.current.shader = @shader Riko::Context.current.target = @screen @prim.render end end GLSLGetometrySample.run
ああ、ソース色付きじゃないとちょっとみづらいな。ハイライトプラグインの導入を考えるか。
それはともかく、シェーダとしては、VertexShaderは受けとったのをそのまま流すだけ。頂点座標だけは普通にスクリーン座標に変換してる。FragmentShaderもテクスチャ貼って描いてるだけ。
GeometryShaderは入力されたプリミティブを左にちょっとずらして描いて、右にちょっとずらして描いてってのをやって分身させてるだけ。
まあとりあえず動くのを確認しただけですな。
FPSはなんか不安定なんであんまりあてにならないんだけど、かなり重くなってるってのは分かる。
しかし、まあこれRadeon X1600なiMacだからな。元々GeometryShaderなんて対応してないハードでそれなりの速度で動くだけでも奇跡。GF8600GTだとなんともないぜ的なスピードで動いてくれた。ああ、XPでもGeometryShader使えるのは楽でいいよな。
問題はこのGeometryShader、何に使うんだってことだよな…。分身とかしても重いだけで役に立たんし。普通に2回描いた方が速いだろ。
GL_EXT_texture_layerが無いから複数のレイヤーに同時に描画も出来ないしなぁ…って、そうかキューブマップと3Dテクスチャに同時に沢山描くのはGL_EXT_geometry_shader4とFBOだけで行けるのか。
とはいえ、ソフトエミュレーションではそんなに速度出ないだろうから、俺のiMacでは実用にならんだろうけど。
しかしGeometryShaderの使い途が同時に沢山描く、だけってのもな…。GL_EXT_draw_instancedで沢山描いてVertexShaderでインスタンス番号拾って変形させても同じだろ。MacにはまだGL_EXT_draw_instanced無いけど。
あとはテッセレーションか。微妙だなー。なんでそんなにテッセレータとか欲しがるんだか分からんのだけど。まああってもいいけどさ。
パーティクルを点からプリミティブに展開できるのはちょっと嬉しいけど、いや、べつにCPUでやってもいいんじゃね?
つかどれもCPUでやってもいいじゃん的なものが多いな。だからこそCPUでエミュレーションされてんのかもしらんけど。
つか、どうせCPUでやるように書くならシェーダにしちゃっていいんじゃね?ってことなのかもね。エミュレーションしてくれるなら勝手にエミュレーションして貰って、HWアクセラレーションが効くならそうして貰うと。
Intelなんかは結局GeometryShaderをCPUで実装しそうな気がするよね。