VBOを使った2D図形の描画

VBO(頂点バッファオブジェクト)。
3Dポリゴンの頂点情報をあらかじめGPUのメモリに置いておくことで
描画を高速化する。

自分が作っているゲームは2Dで、
しかも大半が画像ファイルを元にしたテクスチャなのだけど、
直線とか四角形とかの基本的な図形はやっぱりポリゴンで描く。
ここが速くなるとやっぱり嬉しい。

という訳で、AndroidOpenGL 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割くらいは速くなる。
…正直、もっと劇的な高速化になるかと思ってたけど
うまくいかないもんだ。