VBOを使った2D図形の描画
VBO(頂点バッファオブジェクト)。
3Dポリゴンの頂点情報をあらかじめGPUのメモリに置いておくことで
描画を高速化する。
自分が作っているゲームは2Dで、
しかも大半が画像ファイルを元にしたテクスチャなのだけど、
直線とか四角形とかの基本的な図形はやっぱりポリゴンで描く。
ここが速くなるとやっぱり嬉しい。
という訳で、AndroidでOpenGL ESで2DでVBOで描画。
初歩からわかるAndroid最新プログラミング のサンプルコードをベースに、
初めてのAndroid開発とOpeGL ES 1.1 - tuedaの日記 を参考にさせてもらいつつ制作。
2Dゲームでの使用前提なので、本当に基本的なものしか描けない。
描けるのは直線、塗りつぶし長方形、線のみの円、塗りつぶし円。
まずメインの抽象クラス。
public abstract class Shape { /** 頂点座標バッファ */ protected IntBuffer vertexBuffer; protected int bufferId=-1; protected static final int one = 0x10000; /** 頂点数 */ protected int vertexCount; /** 塗りつぶしか否か */ protected boolean fill; /** 描画モード */ protected int mode; public static boolean EnableVBO=true; /** * 図形の描画 * @param gl * @param x 座標 * @param y * @param z * @param angle_x 回転角度 * @param angle_y * @param angle_z * @param scale_x 拡大率 * @param scale_y * @param scale_z * @param R 色 * @param G * @param B * @param A * @param LineWidth 線幅 */ public void draw(GL10 gl, double x, double y, double z, double angle_x, double angle_y, double angle_z, double scale_x, double scale_y, double scale_z,double R,double G,double B,double A,double LineWidth) { // 頂点配列を有効化 gl.glEnableClientState(GL11.GL_VERTEX_ARRAY); gl.glDisable(GL10.GL_TEXTURE_2D); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glPushMatrix(); // 平行移動 gl.glTranslatex(fixed(x), fixed(y), fixed(z)); // 回転 gl.glRotatef(angle_x, 1.0f, 0.0f, 0.0f); gl.glRotatef(angle_y, 0.0f, 1.0f, 0.0f); gl.glRotatef(angle_z, 0.0f, 0.0f, 1.0f); // リサイズ gl.glScalex(fixed(scale_x), fixed(scale_y), fixed(scale_z)); // 色 gl.glColor4x(fixed(R),fixed(G),fixed(B),fixed(A)); // 塗りつぶし図形でないなら線幅を設定 if(!fill){ gl.glLineWidthx(fixed(LineWidth)); } if(bufferId<0 || !EnableVBO){ // 頂点バッファを使わない描画 ( (GL11)gl).glVertexPointer(3, GL10.GL_FIXED, 0, vertexBuffer); }else{ // 頂点バッファのバインド ( (GL11)gl).glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId); ( (GL11)gl).glVertexPointer(3, GL11.GL_FIXED, 0, 0); } ( (GL11)gl).glDrawArrays(mode, 0, vertexCount); // 後始末 // 頂点バッファのアンバインド ( (GL11)gl).glBindBuffer(GL11.GL_ARRAY_BUFFER, 0); gl.glPopMatrix(); gl.glDisableClientState(GL11.GL_VERTEX_ARRAY); } /** * 頂点バッファの生成 */ public void makeBuffer(GL10 gl,int[ ] vertices){ // バッファ生成 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer = vbb.asIntBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); vertexCount=vertices.length/3; // バッファをVBOに変換 int[ ] bufferIds=new int[1]; ( (GL11)gl).glGenBuffers(1,bufferIds,0); ( (GL11)gl).glBindBuffer(GL11.GL_ARRAY_BUFFER,bufferIds[0]); ( (GL11)gl).glBufferData(GL11.GL_ARRAY_BUFFER, vertices.length*4,vbb,GL11.GL_STATIC_DRAW); bufferId= bufferIds[0]; } /** * 浮動小数点値を固定小数点値に変換 */ protected int fixed(double val){ return (int)(65536*val); } }
glBufferDataでVBOにデータを転送しておいて、
glBindBufferでバッファのIDを指定してバインド、
でglVertexPointerでバッファのポインタを直接指定する代わりに
オフセットを指定することでVBOが使えるようになる。
後はglDrawArraysで描画。
インデックスバッファも同様に先に転送しておいて
glDrawElementsで描画する方法も試してみたけど、
glDrawArraysの方が若干速かった。
図形の形状は、Shapeを継承したクラスで個々に設定。
直線
public class Line extends Shape{ public Line(GL10 gl) { // 頂点座標 int vertices[ ] = { 0, 0, 0, one, one, 0, }; makeBuffer(gl,vertices); mode=GL11.GL_LINE_STRIP; fill=false; } }
矩形
public class Rectangle extends Shape{ public Rectangle(GL10 gl) { // 頂点座標 int vertices[ ] = { 0, 0, 0, one, 0, 0, 0, one, 0, one, one, 0, }; makeBuffer(gl,vertices); mode=GL11.GL_TRIANGLE_STRIP; fill=true; } }
円(塗りつぶしなし)
public class Circle extends Shape{ private static final int div=32; public Circle(GL10 gl) { // 頂点座標 int vertices[ ] = new int[div*3]; for(int i=0;i<div;i++){ vertices[3*i]=fixed(Math.cos(2*Math.PI*i/div)); vertices[3*i+1]=fixed(Math.sin(2*Math.PI*i/div)); vertices[3*i+2]=0; } makeBuffer(gl,vertices); mode=GL11.GL_LINE_LOOP; fill=false; } }
円(塗りつぶしあり)
public class Disk extends Shape{ private static final int div=32; public Disk(GL10 gl) { // 頂点座標 int vertices [] = new int[(div+2)*3]; int i; for(i=0;i<3;i++){ vertices[i]=0; } for(i=1;i<div+2;i++){ vertices[3*i]=fixed(Math.cos(2*Math.PI*(i-1)/div)); vertices[3*i+1]=fixed(Math.sin(2*Math.PI*(i-1)/div)); vertices[3*i+2]=0; } makeBuffer(gl,vertices); mode=GL11.GL_TRIANGLE_FAN; fill=true; } }
アプリ内で各図形のインスタンスを一つずつ作っておいて、
drawメソッドを呼んでやれば描画できる。
座標とか拡大率とか色とか、その辺を変えるだけで
インスタンスは使いまわし可能。
肝心の速度は、ざっくり測っただけだけど
VBOを使わない描画(Shape.EnableVBO=false;にした場合)に比べ
だいたい1割くらいは速くなる。
…正直、もっと劇的な高速化になるかと思ってたけど
うまくいかないもんだ。