図形の定義

OpenGL ES ビューにおいて描画する図形を定義することは、優れたハイエンド グラフィックスを作成するための第一歩です。OpenGL ES で描画を行う場合、グラフィック オブジェクトを定義する際に必要とされる基本的な知識がないと、困難になる可能性があります。

このレッスンでは、Android デバイス画面を基準とした OpenGL ES 座標系、図形と図形面の定義の基本、三角形と四角形の定義について説明します。

三角形を定義する

OpenGL ES では、3 次元空間上の座標を使用して描画オブジェクトを定義できます。そのため、三角形を描画する前に座標を定義する必要があります。OpenGL で定義する場合、座標の浮動小数点数の頂点配列を定義するのが一般的な方法です。最大限に効率化するには、この座標を ByteBuffer に書き込みます。ByteBuffer は OpenGL ES グラフィックス パイプラインに渡されて処理されます。

Kotlin

    // number of coordinates per vertex in this array
    const val COORDS_PER_VERTEX = 3
    var triangleCoords = floatArrayOf(     // in counterclockwise order:
            0.0f, 0.622008459f, 0.0f,      // top
            -0.5f, -0.311004243f, 0.0f,    // bottom left
            0.5f, -0.311004243f, 0.0f      // bottom right
    )

    class Triangle {

        // Set color with red, green, blue and alpha (opacity) values
        val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)

        private var vertexBuffer: FloatBuffer =
                // (number of coordinate values * 4 bytes per float)
                ByteBuffer.allocateDirect(triangleCoords.size * 4).run {
                    // use the device hardware's native byte order
                    order(ByteOrder.nativeOrder())

                    // create a floating point buffer from the ByteBuffer
                    asFloatBuffer().apply {
                        // add the coordinates to the FloatBuffer
                        put(triangleCoords)
                        // set the buffer to read the first coordinate
                        position(0)
                    }
                }
    }
    

Java

    public class Triangle {

        private FloatBuffer vertexBuffer;

        // number of coordinates per vertex in this array
        static final int COORDS_PER_VERTEX = 3;
        static float triangleCoords[] = {   // in counterclockwise order:
                 0.0f,  0.622008459f, 0.0f, // top
                -0.5f, -0.311004243f, 0.0f, // bottom left
                 0.5f, -0.311004243f, 0.0f  // bottom right
        };

        // Set color with red, green, blue and alpha (opacity) values
        float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

        public Triangle() {
            // initialize vertex byte buffer for shape coordinates
            ByteBuffer bb = ByteBuffer.allocateDirect(
                    // (number of coordinate values * 4 bytes per float)
                    triangleCoords.length * 4);
            // use the device hardware's native byte order
            bb.order(ByteOrder.nativeOrder());

            // create a floating point buffer from the ByteBuffer
            vertexBuffer = bb.asFloatBuffer();
            // add the coordinates to the FloatBuffer
            vertexBuffer.put(triangleCoords);
            // set the buffer to read the first coordinate
            vertexBuffer.position(0);
        }
    }
    

デフォルトでは、[0,0,0] (X,Y,Z) は GLSurfaceView フレームの中心、[1,1,0] はフレームの右上隅、[-1,-1,0] はフレームの左下隅とみなされます。この座標系の図については、OpenGL ES デベロッパー ガイドをご覧ください。

この図形の座標は反時計回りで定義されているので、ご注意ください。描画順は重要です。描画順によって、どちらが図形の前面(通常、描画する面)か、背面(OpenGL ES 陰面消去機能により描画しないようにできる)かが定義されます。面と陰面消去の詳細については、OpenGL ES デベロッパー ガイドをご覧ください。

正方形を定義する

OpenGL における三角形の定義は簡単ですが、もっと複雑な場合はどうでしょうか。たとえば正方形の場合、定義方法はいくつかありますが、OpenGL ES で正方形を描画するには、2 つの三角形を同時に描画する方法が一般的です。

図 1. 2 つの三角形を使った正方形の描画。

繰り返しますが、この図形を表す 2 つの三角形について、反時計回りで頂点を定義し、その値を ByteBuffer に設定する必要があります。各三角形が共有する 2 つの座標を 2 回定義せずに済ませるには、描画リストを使用して、この頂点の描画方法を OpenGL ES グラフィックス パイプラインに伝えます。この図形を描画するためのコードは次のとおりです。

Kotlin

    // number of coordinates per vertex in this array
    const val COORDS_PER_VERTEX = 3
    var squareCoords = floatArrayOf(
            -0.5f,  0.5f, 0.0f,      // top left
            -0.5f, -0.5f, 0.0f,      // bottom left
             0.5f, -0.5f, 0.0f,      // bottom right
             0.5f,  0.5f, 0.0f       // top right
    )

    class Square2 {

        private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw vertices

        // initialize vertex byte buffer for shape coordinates
        private val vertexBuffer: FloatBuffer =
                // (# of coordinate values * 4 bytes per float)
                ByteBuffer.allocateDirect(squareCoords.size * 4).run {
                    order(ByteOrder.nativeOrder())
                    asFloatBuffer().apply {
                        put(squareCoords)
                        position(0)
                    }
                }

        // initialize byte buffer for the draw list
        private val drawListBuffer: ShortBuffer =
                // (# of coordinate values * 2 bytes per short)
                ByteBuffer.allocateDirect(drawOrder.size * 2).run {
                    order(ByteOrder.nativeOrder())
                    asShortBuffer().apply {
                        put(drawOrder)
                        position(0)
                    }
                }
    }
    

Java

    public class Square {

        private FloatBuffer vertexBuffer;
        private ShortBuffer drawListBuffer;

        // number of coordinates per vertex in this array
        static final int COORDS_PER_VERTEX = 3;
        static float squareCoords[] = {
                -0.5f,  0.5f, 0.0f,   // top left
                -0.5f, -0.5f, 0.0f,   // bottom left
                 0.5f, -0.5f, 0.0f,   // bottom right
                 0.5f,  0.5f, 0.0f }; // top right

        private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

        public Square() {
            // initialize vertex byte buffer for shape coordinates
            ByteBuffer bb = ByteBuffer.allocateDirect(
            // (# of coordinate values * 4 bytes per float)
                    squareCoords.length * 4);
            bb.order(ByteOrder.nativeOrder());
            vertexBuffer = bb.asFloatBuffer();
            vertexBuffer.put(squareCoords);
            vertexBuffer.position(0);

            // initialize byte buffer for the draw list
            ByteBuffer dlb = ByteBuffer.allocateDirect(
            // (# of coordinate values * 2 bytes per short)
                    drawOrder.length * 2);
            dlb.order(ByteOrder.nativeOrder());
            drawListBuffer = dlb.asShortBuffer();
            drawListBuffer.put(drawOrder);
            drawListBuffer.position(0);
        }
    }
    

この例では、OpenGL でより複雑な図形を作成する方法を説明しています。一般に、複数の三角形を組み合わせてオブジェクトを描画します。次のレッスンでは、こうした図形を画面に描画する方法を説明します。