今回は以前紹介したCardboardで実際にポリゴンを描画する方法を書いていこうと思います。
あまり色々書いてもわかりづらいので、最小構成で書いていきます。
今回描画するポリゴンは、Cardboardの公式サンプルに習ってプリミティブなキューブを描画します。
ライフサイクル
Cardboardでポリゴンを描画する場合、メソッドのライフサイクルは、
onCreate()→onSurfaceChanged()→onSurfaceCreated()→onNewFrame()→onDrawEye()→onDrawEye()→onFinishFrame()
と進み、onFinishFrameの後onNewFrameから繰り返します。
Activityの設定
デフォルトのActivityはだいたいこうなってると思います。
| 1 | public class MainActivity extends Activity | 
このextendsされているActivityをCardboardActivityに変更し、さらにimplementsを追加します。追加するのはCardboardView.StereoRendererです。
最終的にクラスはこうなります。
| 1 2 3 | public class MainActivity extends CardboardActivity implements CardboardView.StereoRenderer { } | 
implementsだけ変更してActivityを変更し忘れると地味にはまります。(はまってました。)
変数宣言
描画するために使用する変数は以下になります。
| 1 2 3 4 5 6 7 8 9 10 11 | private static final float CAMERA_Z = 0.01f; private int mGlProgram; private int mPositionParam; private int mModelViewProjectionParam; private float[] mCamera; private float[] mView; private float[] mHeadView; private float[] mModelViewProjection; private float[] mModelView; private float[] mModelCube; private FloatBuffer mCubeVertices; | 
CAMERA_Z
カメラの初期注視点を指定します。
mGlProgram
使用するOpenGLESを指定します。
mPositionParam
シェーダーに渡す頂点座標
mModelViewProjectionParam
シェーダーに渡す射影変換行列
mCamera、mView、mHeadView、mModelViewProjection、mModelView、mModelCube
4×4の行列を格納するための配列
mCubeVertices
GLに渡すキューブの頂点座標の配列Buffer
onCreate()
onCreateでは、CardboardViewの初期化と各変数を初期化していきます。
| 1 2 3 4 5 6 7 8 9 10 11 | CardboardView cardboardView = (CardboardView)findViewById(R.id.cardboard_view); cardboardView.setRenderer(this); setCardboardView(cardboardView); mModelCube = new float[16]; mCamera = new float[16]; mView = new float[16]; mModelViewProjection = new float[16]; mModelView = new float[16]; mModelFloor = new float[16]; mHeadView = new float[16]; | 
CardboardViewにlayoutファイルで作成したCardboardViewを指定し、レンダラーを設定します。
他の変数については、4×4のMatrixとして使用するため、配列として初期化しておきます。
onSurfaceCreated()
onSerfaceCreateでは表示するモデルの頂点座標の他にノーマルマッピングやUV座標などをByteBufferに変換します。
他には、使用するシェーダを読み込んだり、変換したBufferの座標を表示させる空間に配置します。
まずはBufferの変換について説明します。
| 1 2 3 4 5 | ByteBuffer bbVertices = ByteBuffer.allocateDirect(CUBE_COORDS.length * 4); bbVertices.order(ByteOrder.nativeOrder()); mCubeVertices = bbVertices.asFloatBuffer(); mCubeVertices.put(CUBE_COORDS); mCubeVertices.position(0); | 
CUBE_COORDSにはキューブを構成するための頂点座標が格納されています。
今回はキューブの各面を三角形2つによって正方形を作成しています。
そのため、3×2×6の数の頂点が必要になるため、36個の頂点が入っています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public static final float[] CUBE_COORDS = new float[] {             // Front face             -1.0f, 1.0f, 1.0f,             -1.0f, -1.0f, 1.0f,             1.0f, 1.0f, 1.0f,             -1.0f, -1.0f, 1.0f,             1.0f, -1.0f, 1.0f,             1.0f, 1.0f, 1.0f,             // Right face             1.0f, 1.0f, 1.0f,             1.0f, -1.0f, 1.0f,             1.0f, 1.0f, -1.0f,             1.0f, -1.0f, 1.0f,             1.0f, -1.0f, -1.0f,             1.0f, 1.0f, -1.0f,             // Back face             1.0f, 1.0f, -1.0f,             1.0f, -1.0f, -1.0f,             -1.0f, 1.0f, -1.0f,             1.0f, -1.0f, -1.0f,             -1.0f, -1.0f, -1.0f,             -1.0f, 1.0f, -1.0f,             // Left face             -1.0f, 1.0f, -1.0f,             -1.0f, -1.0f, -1.0f,             -1.0f, 1.0f, 1.0f,             -1.0f, -1.0f, -1.0f,             -1.0f, -1.0f, 1.0f,             -1.0f, 1.0f, 1.0f,             // Top face             -1.0f, 1.0f, -1.0f,             -1.0f, 1.0f, 1.0f,             1.0f, 1.0f, -1.0f,             -1.0f, 1.0f, 1.0f,             1.0f, 1.0f, 1.0f,             1.0f, 1.0f, -1.0f,             // Bottom face             1.0f, -1.0f, -1.0f,             1.0f, -1.0f, 1.0f,             -1.0f, -1.0f, -1.0f,             1.0f, -1.0f, 1.0f,             -1.0f, -1.0f, 1.0f,             -1.0f, -1.0f, -1.0f,     }; | 
次にシェーダの読み込みとモデルの配置を行います。
| 1 2 | int vertexShader = loadGLShader(GLES20.GL_VERTEX_SHADER, R.raw.simple_vertex.shader); int gridShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.simple_fragment.shader); | 
まずシェーダを読み込むためにloadGLShaderメソッドを作成し、これを使いシェーダを読み込みます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private int loadGLShader(int type, int resId) {         String code = readRawTextFile(resId);         int shader = GLES20.glCreateShader(type);         GLES20.glShaderSource(shader, code);         GLES20.glCompileShader(shader);         final int[] compileStatus = new int[1];         GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);         if (compileStatus[0] == 0) {             GLES20.glDeleteShader(shader);             shader = 0;         }         if (shader == 0) {             throw new RuntimeException("Error creating shader.");         }         return shader;     } | 
そして更に読み込むシェーダは改行が入っていないので、テキストに合わせて改行を入れるために テキストを変更しています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private String readRawTextFile(int resId) {         InputStream inputStream = getResources().openRawResource(resId);         try {             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));             StringBuilder sb = new StringBuilder();             String line;             while ((line = reader.readLine()) != null) {                 sb.append(line).append("\n");             }             reader.close();             return sb.toString();         } catch (IOException e) {             e.printStackTrace();         }         return "";     } | 
また、読み込むシェーダの内容は以下のようになっています。
まずは頂点シェーダ
| 1 2 3 4 5 6 | attribute vec4 a_Position; uniform mat4 u_MVP; void main() {   gl_Position = u_MVP * a_Position; } | 
次にピクセルシェーダ(フラグメントシェーダ)
| 1 2 3 4 5 | precision mediump float; void main() {   gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } | 
シェーダを読み込んだらそれをOpenGLESに渡してあげます。
| 1 2 3 4 5 6 7 8 9 | mGlProgram = GLES20.glCreateProgram(); GLES20.glAttachShader(mGlProgram, vertexShader); GLES20.glAttachShader(mGlProgram, gridShader); GLES20.glLinkProgram(mGlProgram); GLES20.glEnable(GLES20.GL_DEPTH_TEST); Matrix.setIdentityM(mModelCube, 0); Matrix.translateM(mModelCube, 0, 0, 0, -12.0f); | 
GLES20.glCreateProgram()によって使用するプログラムを作成し、それぞれのシェーダを当てはめていき、それらをGLES20.glLinkProgram(mGlProgram)によってリンクさせます。
またGLES20.glEnable(GLES20.GL_DEPTH_TEST)を記述することによって深度を測って描画することができるようになります。
そして、Matrix.setIdentityM(mModelCube, 0)でビューにモデルを配置しています。
Matrix.translateM(mModelCube, 0, 0, 0, -12.0f)では配置したモデルの初期座標を原点(カメラ位置)から奥に向かって移動させています。
onNewFrame()
onNewFrameは毎フレーム1度呼ばれるため、モデルの座標変換や、射影変換行列をOpenGLESに渡してあげます。
また、端末が動いた量をカメラに反映させています。
| 1 2 3 4 5 6 7 8 9 10 11 | public void onNewFrame(HeadTransform headTransform) {         GLES20.glUseProgram(mGlProgram);         mModelViewProjectionParam = GLES20.glGetUniformLocation(mGlProgram, "u_MVP");         Matrix.rotateM(mModelCube, 0, 3.0f, 0.5f, 0.5f, 1.0f);         Matrix.setLookAtM(mCamera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);         headTransform.getHeadView(mHeadView, 0);     } | 
onDrawEye()
onDrawEyeは毎フレーム2度呼ばれます。
ここでは描画処理を行います。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Override     public void onDrawEye(EyeTransform transform) {         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);         mPositionParam = GLES20.glGetAttribLocation(mGlProgram, "a_Position");         GLES20.glEnableVertexAttribArray(mPositionParam);         Matrix.multiplyMM(mView, 0, transform.getEyeView(), 0, mCamera, 0);         Matrix.multiplyMM(mModelView, 0, mView, 0, mModelCube, 0);         Matrix.multiplyMM(mModelViewProjection, 0, transform.getPerspective(), 0, mModelView, 0);         GLES20.glVertexAttribPointer(mPositionParam, 3, GLES20.GL_FLOAT,false, 0, mCubeVertices);         GLES20.glUniformMatrix4fv(mModelViewProjectionParam, 1, false, mModelViewProjection, 0);         GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);     } | 
GLES20.glClear()では背景を初期化しています。
次に頂点シェーダにGLES20.glGetAttribLocation(mGlProgram, “a_Position”)を使い頂点座標を渡すためのハンドルを設定し、GLES20.glEnableVertexAttribArray(mPositionParam)によって有効化させます。
そしてカメラからのビューを変換し、射影変換行列に変換します。
最後にGLES20.glDrawArrays()を使いモデルを描画させます。
描画させてみたものはこちらです。
これでやっとプリミティブな赤いキューブが空間にぽつんと浮いたものがくるくる回っているものが出来上がりました。
シェーダ周りが結構ややこしいので、簡単なものを作るのにも時間がかかりました。
とりあえず一度作りさえすれば色々使い回しができて作りやすくなります。
次回はポリゴンにテクスチャを貼り付け、更に透過させるのを書いていこうと思います。
では、また