OpenGLES 開発環境構築
表題の通りにJava言語でのOpenGL-ESの開発環境を構築したいと思います。参考にする本は以下のものです。
参考書籍
Javaでの実装には役に立たなかったが、OpenGLの処理内容を理解するのには、役立った。
|
Android版のセットアップ
書籍のほうでは、EclipseにADT(Android開発プラグイン)を入れて実装していました。(参照したのが古い本でした)なので、Android Studioをインストールして実行します。
Windowsでのセットアップですので、インストーラーをダブルクリックしておしまいです。
セットアップはあまり重要でなく、プログラムの実装とOpenGLの仕組みを理解することがメインになります。ですので開発環境が多少違っても
OpenGLのテクノロジーを使用することには変わりないので影響しません。そしてOpenGLはクロスプラットフォームなのでどのOSにも対応しています。
まぁC++でできていればどのOSでもしようできますね。※Java言語も負けてないです。
<インストーラーを起動したときの画像>
Android Studio
上記のインストーラーを早速起動します。そしたら、下の用が画面が出たので、「Send usage statics to Google」をクリックします。
そして、インストールタイプ(Install Type)はスタンダードを指定して、各ライセンス規定に同意する必要があります。
その後、インストールが始まります。IntelliJ IDEAがベースになっているので、AndroidStudioはIntelliJ IDEAと操作方法が似ているはずです。
インストール完了後は下のように表示されした。
ここから先はAndroid仕様で、作成するアクティビティを選択してから実行していきます。
今回は、EmptyComposedActivityを選択してプロジェクトを作成しました。本家のチュートリアルを参照して作成しました。すごーくわかりやすいです。日本語で読めました。
OpenGLのセットアップ
本家のチュートリアルを参考に行いました。
子のチュートリアルは下のような項目を行っていました。
- OpenGL ES 環境の構築
- Android アプリケーションをセットアップして OpenGL グラフィックスを描画できるようにする方法について説明します。
- 図形の定義
- 図形を定義する方法と、面とワインディングについて認識しておく必要がある理由について説明します。
- 図形の描画
- アプリケーションで OpenGL 図形を描画する方法について説明します。
- 投影とカメラビューの適用
- 投影とカメラビューを使用して、描画されたオブジェクトの新しい視点を取得する方法について説明します。
- モーションの追加
- OpenGL を使って描画されたオブジェクトの基本的な動きとアニメーション化を行う方法について説明します。
- タップイベントへの応答
このうち
1から3まで実行したところです。
結果として、三角形と四角形の描画があるのですが、とりあえず三角形の描画を行いました。実機にデプロイできるようにエミュレータを使用しました。
補足、Activityは、初めの状態だと「AppCompatActivity」が継承(extends)されているのでそれを「Activity」に変更する必要がありました。
そして、実機でのテストをするのには、やはり本家のサイトを参考にしました。
- 自分のAndroid端末とPCのWifiを同じSSIDに接続する
- Android StudioでPair Device using Wifiを選択
- Android端末でデバックモードをONにする
- 表示されたQRコードをAndroid端末で読み込む
- AndroidStudioでテストを実行する
<実行結果>
サンプルコードの実装
作成したファイルは以下のファイルです。
- MainActivity: これはプロジェクトを作成したときに生成されたものです。中身を修正しました。
- MyGLRender: チュートリアルにあったクラスです。
- MyGLSurfaceView: チュートリアルにあったクラスです。
- Square: チュートリアルにあったクラスです。
- Triangle: チュートリアルにあったクラスです。
それぞれ以下のように作成しました。※ほぼコピペ
MainActivity
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class MainActivity extends Activity /* AppCompatActivity */ {
private GLSurfaceView gLView;
@Override
protected void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
super.onCreate(savedInstanceState);
gLView = new MyGLSurfaceView(this);
setContentView(gLView);
}
}
MyGLRender
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
public class MyGLRender implements GLSurfaceView.Renderer {
private Triangle mTriangle;
private Square mSquare;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// initialize a triangle
mTriangle = new Triangle();
// initialize a square
mSquare = new Square(); }
public void onDrawFrame(GL10 gl) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mTriangle.draw();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
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;
}
}
MyGLSurfaceView
import android.content.Context;
import android.opengl.GLSurfaceView;
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRender render;
public MyGLSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
render = new MyGLRender();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(render);
// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
Square
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
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);
}
}
Triangle
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
public class Triangle {
private FloatBuffer vertexBuffer;
private final int mProgram;
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
// 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
};
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;" +
"}";
// 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);
int vertexShader = MyGLRender.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRender.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);
}
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);
}
}
環境構築のまとめ
チュートリアルをそのまま実行すればすぐに環境構築ができました。KotlinでもJavaでも実装ができるので良いと思いました。
次は、2次元描画を行いたいところです。そして、この2次元描画を少し丹念に学習したいと思っております。
コード解析
下記の5クラスを実装しました。
- MainActivity
- MyGLRender
- MyGLSurfaceView
- Square
- Triangle
これらの関係性を表すのに、UMLを書きました。
こんな感じです。
処理概要
下のような感じです。
- MainActivityでAndroidの画面を作成します。
- MyGLSurfaceViewでOpenGLの描画を行うパレットのようなものを作成します。
- MyGLRenderで実際に描画する三角形とか四角形を描画
処理内容: MainActivity
これはAndroidの画面を作成するクラスです。アンドロイドのAPIで定義されているActivittyクラスを継承して実装します。
- 「extends AppCompatActivity」になっていたところを「Activity」に変更しています。
- 「onCreate」メソッドをオーバーライドしています。
- 親クラスで呼び出しているonCreateを上書きするので、こちらのメソッドが動きます。
- フィールド変数にOpenGLのGLSurfaceViewを定義しています。これに、自作のMyGLSurfaceViewをセットします。
public class MainActivity extends Activity /* AppCompatActivity */ {
private GLSurfaceView gLView;
@Override
protected void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
super.onCreate(savedInstanceState);
gLView = new MyGLSurfaceView(this);
setContentView(gLView);
}
}
処理内容: MyGLRender
このクラスは、「MyGLRender」クラスをGLSurfaceView#setRendererにセットしています。
その他、細かい設定をしています。
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRender render;
public MyGLSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
render = new MyGLRender();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(render);
// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
処理内容:
「GLSurfaceView.Renderer」をimeplementsすることで、描画処理を行います。
それぞれ「\@Override」アノテーションがついているメソッドがインタフェースでオーバーライドを強制するメソッドになります。
コメントに何をしているか書いています。
- 「Set the background frame color」=背景枠の色を設定する
- 「initialize a triangle」=三角形初期化
- 「initialize a square」=四角形初期化
- 「Redraw background color」=背景の再描画
public class MyGLRender implements GLSurfaceView.Renderer {
private Triangle mTriangle;
private Square mSquare;
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// initialize a triangle
mTriangle = new Triangle();
// initialize a square
mSquare = new Square();
}
@Override
public void onDrawFrame(GL10 gl) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mTriangle.draw();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
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;
}
}
処理内容:Triangle
これは、長くなってしまうので、文言のみにしますが、
- コンストラクタ(Triangle())で描画の準備
- draw()で描画処理
上記を行っています。
処理内容:Square
こちらのクラスの描画処理は見当たらなかったので、改めて実装しました。
結論から言うと、三角形の描画メソッドとほぼ同じでした。違うのは、描画のためのデータ、以下のものです。
- squareCoords: 四角形の各頂点定義
- 描画モードの設定
- GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // 三角形の描画
- GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, vertexCount); // 四角形の描画
package jp.zenryoku.firstapp;
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
public class Square {
private FloatBuffer vertexBuffer;
private ShortBuffer drawListBuffer;
private final int mProgram;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
private int positionHandle;
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
private int colorHandle;
private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
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;" +
"}";
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);
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
int vertexShader = MyGLRender.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRender.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// 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);
}
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_TRIANGLE_FAN, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(positionHandle);
}
}