cloverrose's blog

Python, Machine learning, Emacs, CI/CD, Webアプリなど

AndroidでOpenGL ES 2.0調査

現在、Androidで何かアプリを作ろうと思い、OpenGL周りを調査しています。

OpenGLで調べるとC++だったり、OpenGL 1.0, 1.1だったり、
OpenGL 2.0の
意外とWeb上に完全な情報がまとまっておらず線を引くだけでも、苦労しました。

(最初に)OpenGL 1.0について

OpenGL ES 2.0の情報になかなかありつけなかったため、OpenGL ES 1.0で実装しようと思いましたが、新しいAPIでは2.0を推奨しているようです。

The example code in this class uses the OpenGL ES 2.0 APIs, which is the recommended API version to use with current Android devices.

また、記憶が曖昧ですが、1.0だと自分のエミュレータでうまく動かず、なんだかんだ頑張って2.0で実装する事にしました。


参考にしたWebサイト

1. Displaying Graphics with OpenGL ES | Android Developers
Androidの公式チュートリアル

基本的には、このWebサイトのOpenGLの項目を順番に読んでいけばいいんだけど、
未定義なローカル変数やメンバ変数を参照していて、どんな値を設定すればいいかわからないという状態になります。


2. Displaying Graphics with OpenGL ES | Android Developers
先ほどのチュートリアルの最終結果の実際に動くコードを載せてくれているブログ

公式チュートリアルが、マジックナンバーを使っている部分も定数としてわかりやすくなっているので、理解が深まります。
また、未定義な変数にどんな値を設定すればいいかもわかると思います。

3. 4. OpenGL Primitives - Square (Version 2.0) – Swiftless Tutorials - OpenGL Tutorials
このサイトはOpenGLで線を引く方法がわからなかった時に、GLES20.GL_LINE_STRIPとかの違いを理解するのに役立ちました。

GL_LINESとGL_LINE_LOOP, GL_LINE_STRIPの違いを図示してくれてるサイト


4. OpenGL ES | Android Developers
OpenGLのことがおおまかに書いてあります。自分は最初にこのサイトに辿り着いたのですが、このサイトだけだと理解出来なかった。
1.のサイトを合わせて読んでようやく具体例がわかった。


5. Android OpenGL ES 2, Drawing squares - Stack Overflow
OpenGLでは四角形を描画する命令はなくて、三角形を描画する命令で描画する。
1. のサイトでも三角形を2つで四角形を描画するSquareクラスを追加しているけど、
肝心のGLES20.glDrawArrays()をどう変えればいいかが載っていない。

また、1.のサイトでは頂点を3×2の6個保持するのは、効率が悪いので、4個保持しておき、描画する順番を別途指定する効率化を行っているけど、その場合glDrawArrays()ではなくて、どうやら GLES20.glDrawElements()を使うようだ。

このAnswerAndroid OpenGL ES 2, Drawing squares - Stack Overflowを読むと、そこら辺のこともわかる。

Tipsになりそうなこと

  • Activityのタッチイベントで取得したx,yはOpenGLのx,yにそのままは使えない
        @Override
        public boolean onTouchEvent(MotionEvent e) {
            // タッチイベントで取得できるx, y
            float x = e.getX();
            float y = e.getY();
            int width = getWidth();
            int height = getHeight();
            
            // OpenGLは中心が(0, 0)
            float centerX = width / 2;
            float centerY = height / 2;
            float glX = (centerX - x) / centerX;
            float glY = (centerY - y) / centerY;
            
            // OpenGLは描画範囲を正方形として扱っており、
            // Rendererクラスでデバイスのアスペクト比に
           // 合わせるように行列演算をしているので
           // それに対応する(glYはそのままの値を使う!)
            glX /= (float)height / width;

            //  ↓適宜描画処理を記述↓
        }
  • 配列を何回も初期化するのを避けるために、2次元の描画に特化したfloat[]のラッパークラスを作った
package com.github.cloverrose.mag;

import java.util.Arrays;

public class FastArray {
    public float[] data;
    public int length;

    private int allocateSize;
    private int lastIndex;
    private int threshold; // use this variable to avoid calculate allocateSize - 1
    
    private static final int INITIAL_SIZE = 5;
    public FastArray(){
        data = new float[3 * INITIAL_SIZE];
        allocateSize = data.length;
        threshold = allocateSize - 1;
        lastIndex = -1;
        length = 0;
    }
    
    
    public FastArray(FastArray src){
        this.data = Arrays.copyOf(src.data, src.data.length);
        this.length = src.length;
        this.allocateSize = src.allocateSize;
        this.threshold = src.threshold;
        this.lastIndex = src.lastIndex;
    }
    
    
    public void append(float x, float y){
        if(lastIndex == threshold){
            data = Arrays.copyOf(data, allocateSize * 2);
            allocateSize = data.length;
            threshold = allocateSize - 1;
        }
        data[++lastIndex] = x;
        data[++lastIndex] = y;
        data[++lastIndex] = 0.0f;
        length += 3;
    }
}


いちいちクラス定義せずに、List使えばいいじゃんって思うけど、

List<Float>.toArray()

だとFloatBuffer.put()の引数に使えない。

疑問点とか

  • タッチで線を描く場合、今までの点を全て配列に保存する?
  • それとも画面をクリアせずに、最新の線だけを描画する?
  • (ちなみに)毎回全ての線を描画しなおしても動作速度は今のところ問題なく速い
  • 最新の線だけを描画する場合、画面の回転とかには対応できなさそう
  • 一定以上線が増えたらBitmap画像にして、直近N個の点だけを配列に保存ということを既存アプリはやってるのかな?
  • デバイスの回転に対応するのが難しい
  • デバイスを回転するとActivityも再度作成されるので、点の配列などが消えてしまう
  • そこでApplicationクラスを継承し、配列をフィールドに持つクラスを使うことで対応
  • しかし、デバイスを回転するとエラーが発生して落ちてしまった