図形の描画

OpenGL で描画する図形を定義したら、描画します。OpenGL ES 2.0 で図形を描画する場合、多くのコードが必要になります。API により、グラフィックス レンダリング パイプラインが詳細に制御されるためです。

このレッスンでは、OpenGL ES 2.0 API を使用して、前のレッスンで定義した図形を描画する方法について説明します。

図形を初期化する

描画する前に、描画する図形を初期化して読み込む必要があります。プログラムで使用する図形の構造(元の座標)が実行中に変更されない限り、メモリと処理の効率性のためにレンダラの onSurfaceCreated() メソッドで初期化する必要があります。

Kotlin

    class MyGLRenderer : GLSurfaceView.Renderer {
        ...
        private lateinit var mTriangle: Triangle
        private lateinit var mSquare: Square

        override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
            ...
            // initialize a triangle
            mTriangle = Triangle()
            // initialize a square
            mSquare = Square()
        }
        ...
    }
    

Java

    public class MyGLRenderer implements GLSurfaceView.Renderer {

        ...
        private Triangle mTriangle;
        private Square   mSquare;

        public void onSurfaceCreated(GL10 unused, EGLConfig config) {
            ...
            // initialize a triangle
            mTriangle = new Triangle();
            // initialize a square
            mSquare = new Square();
        }
        ...
    }
    

図形を描画する

OpenGL ES 2.0 を使用して定義された図形を描画する場合、グラフィックス レンダリング パイプラインに多くの詳細情報を渡す必要があるため、大量のコードが必要です。具体的には、以下の情報を定義する必要があります。

  • 頂点シェーダー - 図形の頂点をレンダリングするための OpenGL ES グラフィックス コード。
  • フラグメント シェーダー - 色やテクスチャで図形の面をレンダリングするための OpenGL ES コード。
  • プログラム - OpenGL ES オブジェクト(1 つ以上の図形の描画に使用するシェーダーを含む)。

図形を描画するには少なくとも 1 つの頂点シェーダーが、その図形に色を付けるには 1 つのフラグメント シェーダーが必要です。 こうしたシェーダーをコンパイルして OpenGL ES プログラムに追加し、図形の描画に使用します。基本的なシェーダーを定義して、Triangle クラスの図形の描画に使用する例を次に示します。

Kotlin

    class Triangle {

        private val vertexShaderCode =
                "attribute vec4 vPosition;" +
                "void main() {" +
                "  gl_Position = vPosition;" +
                "}"

        private val fragmentShaderCode =
                "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                "  gl_FragColor = vColor;" +
                "}"

        ...
    }
    

Java

    public class Triangle {

        private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}";

        private final String fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}";

        ...
    }
    

シェーダーには OpenGL シェーディング言語(GLSL)コードが含まれます。これは OpenGL ES 環境で使用する前にコンパイルする必要があります。このコードをコンパイルするには、レンダラクラスのユーティリティ メソッドを作成します。

Kotlin

    fun loadShader(type: Int, shaderCode: String): Int {

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        return GLES20.glCreateShader(type).also { shader ->

            // add the source code to the shader and compile it
            GLES20.glShaderSource(shader, shaderCode)
            GLES20.glCompileShader(shader)
        }
    }
    

Java

    public static int loadShader(int type, String shaderCode){

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
    

図形を描画するには、シェーダー コードをコンパイルして OpenGL ES プログラム オブジェクトに追加し、プログラムをリンクする必要があります。これは描画オブジェクトのコンストラクタで行うため、1 回だけ実行されます。

注: OpenGL ES シェーダーのコンパイルとプログラムのリンクは、CPU サイクルと処理時間の点でコストがかかるため、複数回は実行しないでください。ランタイム時にシェーダーのコンテンツがわからない場合、コードは一度だけ作成し、後で使用できるようにキャッシュしておく必要があります。

Kotlin

    class Triangle {
        ...

        private var mProgram: Int

        init {
            ...

            val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
            val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)

            // create empty OpenGL ES Program
            mProgram = GLES20.glCreateProgram().also {

                // add the vertex shader to program
                GLES20.glAttachShader(it, vertexShader)

                // add the fragment shader to program
                GLES20.glAttachShader(it, fragmentShader)

                // creates OpenGL ES program executables
                GLES20.glLinkProgram(it)
            }
        }
    }
    

Java

    public class Triangle() {
        ...

        private final int mProgram;

        public Triangle() {
            ...

            int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                            vertexShaderCode);
            int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                            fragmentShaderCode);

            // create empty OpenGL ES Program
            mProgram = GLES20.glCreateProgram();

            // add the vertex shader to program
            GLES20.glAttachShader(mProgram, vertexShader);

            // add the fragment shader to program
            GLES20.glAttachShader(mProgram, fragmentShader);

            // creates OpenGL ES program executables
            GLES20.glLinkProgram(mProgram);
        }
    }
    

これで、図形を描画する実際の呼び出しを追加できます。OpenGL ES で図形を描画するには、パラメータを指定して、描画する内容と方法をレンダリング パイプラインに伝える必要があります。描画オプションは図形によって異なるため、図形クラスに独自の描画ロジックを含めることをおすすめします。

図形を描画する draw() メソッドを作成します。このコードは、図形の頂点シェーダーとフラグメント シェーダーに位置と色に関する値を設定し、描画関数を実行します。

Kotlin

    private var positionHandle: Int = 0
    private var mColorHandle: Int = 0

    private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
    private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex

    fun draw() {
        // Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram)

        // get handle to vertex shader's vPosition member
        positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also {

            // Enable a handle to the triangle vertices
            GLES20.glEnableVertexAttribArray(it)

            // Prepare the triangle coordinate data
            GLES20.glVertexAttribPointer(
                    it,
                    COORDS_PER_VERTEX,
                    GLES20.GL_FLOAT,
                    false,
                    vertexStride,
                    vertexBuffer
            )

            // get handle to fragment shader's vColor member
            mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle ->

                // Set color for drawing the triangle
                GLES20.glUniform4fv(colorHandle, 1, color, 0)
            }

            // Draw the triangle
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)

            // Disable vertex array
            GLES20.glDisableVertexAttribArray(it)
        }
    }
    

Java

    private int positionHandle;
    private int colorHandle;

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    public void draw() {
        // Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram);

        // get handle to vertex shader's vPosition member
        positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(positionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                                     GLES20.GL_FLOAT, false,
                                     vertexStride, vertexBuffer);

        // get handle to fragment shader's vColor member
        colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(colorHandle, 1, color, 0);

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(positionHandle);
    }
    

コードをすべて用意できたら、あとはレンダラの onDrawFrame() メソッド内から draw() メソッドを呼び出すだけでこのオブジェクトを描画できます。

Kotlin

    override fun onDrawFrame(unused: GL10) {
        ...

        triangle.draw()
    }
    

Java

    public void onDrawFrame(GL10 unused) {
        ...

        triangle.draw();
    }
    

アプリケーションを実行すると、次のようになります。

図 1. 投影またはカメラビューなしで描画される三角形。

このコード例には問題がいくつかあります。まず、他人を感動させるような意図はありません。第 2 に、デバイスの画面の向きを変えると三角形が潰れて形が変わります。図形が歪んでいる理由は、オブジェクトの頂点が、GLSurfaceView が表示される画面領域の比率で修正されていないためです。次のレッスンで投影とカメラのビューを使用して、この問題を修正できます。

最後に、三角形は静止しており、少し物足りないかもしれません。モーションの追加レッスンでは、この図形を回転させて、OpenGL ES グラフィックス パイプラインをもっと応用的に使用します。