3Dでスプライン曲線を描きたいというお題。
いいサンプルが見つからなかったので、とりあえずofx3DSplineをつくってみた。基本的には頂点配列を更新するとスプライン描画用頂点配列を更新してくれるクラス。でも、重かった。60fpsだと256頂点で20分割の補完が限界。これはリアルタイムに曲線をアニメーションさせ続けた場合の負荷。描画はVBOで高速にやってるのでCPUでやってるスプラインの計算がイケてないことは間違いない。他にもいろいろ計算させたいのでこれでは使い物にならない。
そこで、最近ハマってるシェーダーの登場。今回はあまり使ったことのないGeometryシェーダを使ってみる。ちなみにスプラインをやりたいのにB−スプライン曲線になってるのは力不足のため。本当にやりたいのは指定点を必ず通過するほうのスプライン曲線。こちらのほうがアニメーションでは制御しやすそうな気がしてる。あとこの分割ってところはTessellation シェーダーの役割のような気がしていて、実際できるみたいなんだけど、情報が少なかったのとATIとNvidiaで違うところがあるらしいので、今回はGeometryで。
スプラインの参考にしたのはこのドキュメント。
以下、GLSL4でB-スプラインを描くジオメトリシェーダー。uniformで頂点の分割数を変更可能。
layout( lines_adjacency ) in; layout( line_strip, max_vertices=200 ) out; uniform int division; vec4 w_bspline(float alpha){ vec4 tmp; float a2=alpha*alpha; float a3=alpha*alpha*alpha; tmp.x = -a3 + 3.0*a2 - 3.0*alpha + 1.0; tmp.y = 3.0*a3 - 6.0*a2 + 4.0; tmp.z = -3.0*a3 + 3.0*a2 + 3.0*alpha + 1.0; tmp.w = a3; return tmp; } void main(void) { int i; vec4 w; for (i=0; i<=division; i++) { w = w_bspline(float(i)/float(division)); gl_Position.xyzw = w.x * gl_PositionIn[0].xyzw + w.y * gl_PositionIn[1].xyzw + w.z * gl_PositionIn[2].xyzw + w.w * gl_PositionIn[3].xyzw; EmitVertex(); } }
注意点はOPENGLのほうで、GL_LINE_STRIP_ADJACENCYを指定すること、これによってシェーダーが隣接含め4点を受け取り補完を行える。こんなかんじで。
glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, mSplineVtx); glDrawArrays(GL_LINE_STRIP_ADJACENCY,0,mSplineVtxNum); glDisableClientState(GL_VERTEX_ARRAY);
ちなみにopenframeworksのofShaderはジオメトリシェーダーにもちゃんと対応してて、以下のように読み込めるようになってた。Tessellation はいまのところ未対応。
shader.load("myShader.vert","myShader.frag","myShader.geom");
以上でベジェ曲線の描画は上手く行ったんだけど、やっぱり重かったw。CPUでやるのとパフォーマンスそんなに変わってない。やはり若干滑らかさを犠牲にするしか無いかな。そもそもB-スプラインじゃだめだしw
参考: