Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

MOVERIO + OpenGL ES 2.0

目標

MOVERIOの画面にOpenGL ESで描いた三角形のCGを表示する.尚,前回記事は以下を参照のこと.

mugichoko.hatenablog.com

開発環境

本記事の役割り

本記事に載せているサンプルプログラムは,以下の公式のチュートリアルを参考にしています.というより,ほぼそのままです.よって,日本語で書いてあることと,動くプログラムがいっぺんにまとめて確認できるメリット以外,本記事を読む利点はありませんので悪しからず.

developer.android.com

さて,Android StudioからサンプルプログラムのHelloGLがダウンロードできるが,C++を使ってOpenGLの部分を書くのでややこしく感じていた.一方で,公式のチュートリアルにあるサンプルプログラムはJavaだけで実行できる!ただし,当然ながらOpenGL ESを使うので,GLSLの知識は必要.私はGLSLについてはC++でしか触ったことがなかったが,ほとんど同じなので問題なくOpenGL ESも使えた.GLSLに自信のない方は以下の記事を参照のこと.

mugichoko.hatenablog.com

サンプルプログラム

OpenGLES20Activity.java

本体のプログラムです.基本的には,次に紹介するMyGLSurfaceViewクラスのインスタンスを生成しているだけです.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名とプロジェクト名が入ります

import android.support.v7.app.AppCompatActivity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.os.Bundle;

public class OpenGLES20Activity extends AppCompatActivity {

    private MyGLSurfaceView mGLView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mGLView = new MyGLSurfaceView(this);

        final ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        final boolean supportsES2 = configurationInfo.reqGlEsVersion >= 0x20000;
        if (supportsES2)
        {
            setContentView(mGLView);
        }
        else
        {
            return;
        }
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        if (mGLView != null)
        {
            mGLView.onPause();
        }
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        if (mGLView != null)
        {
            mGLView.onResume();
        }
    }
}

MyGLSurfaceView.java

主にOpenGL ESを開始したり,具体的なレンダリング処理が書かれたMyGLRendererクラスのインスタンスを生成しているだけです.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名とプロジェクト名が入ります

import android.content.Context;
import android.opengl.GLSurfaceView;

class MyGLSurfaceView extends GLSurfaceView
{
    private final MyGLRenderer mRenderer;

    public MyGLSurfaceView(Context context)
    {
        super(context);
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRenderer();
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}

MyGLRenderer

このクラスが具体的なレンダリング周りの処理を引き受けています.見たままですが,onSurfaceCreatedで,このクラスがインスタンス化された時に,次に紹介するTriangleクラスをインスタンス化しています.onDrawFrameでは,描画が行われる際の操作を記述しています.今回は,MOVERIOの仕様に合わせてプロジェクション行列を計算し,原点にある三角形を,500だけ離れて真正面から見る想定でビュー行列を計算しています.

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名とプロジェクト名が入ります


import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

public class MyGLRenderer implements GLSurfaceView.Renderer
{
    private Triangle mTriangle;
    private final float[] mMVPMatrix = new float[16];

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config)
    {
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        mTriangle = new Triangle();
    }

    @Override
    public void onDrawFrame(GL10 unused)
    {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // FoV Ref: https://tech.moverio.epson.com/en/pdf/comparison.pdf
        float[] projectionMatrix = new float[16];
        float fovy = 23.0f * (3.0f / 5.0f);
        float aspect = (float)1280 / 720;
        Matrix.perspectiveM(projectionMatrix, 0, fovy, aspect, 10.0f, 3000.0f);

        float[] position = { 0.0f, 0.0f, 500.0f };

        float[] viewMatrix = new float[16];
        Matrix.setLookAtM(viewMatrix, 0,
                position[0], position[1], position[2],
               0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
        );

        Matrix.multiplyMM(mMVPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        mTriangle.draw(mMVPMatrix);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height)
    {
        GLES20.glViewport(0, 0, width, height);
        //float ratio = (float) width / height;
        //Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

    public static int loadShader(int type, String shaderCode)
    {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}

Triangle.java

さて,これで最後です.三角形を定義しています.シェーダに自信のない方は以下の記事を参照のこと.

mugichoko.hatenablog.com

package com.example.XXXXX.XXXXX;
// XXXXXにはユーザ名とプロジェクト名が入ります

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;

public class Triangle
{
    private final int mProgram;
    private FloatBuffer vertexBuffer;
    private int mPositionHandle;
    private int mColorHandle;
    private int mMVPMatrixHandle;

    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] =
            {
                    0.0f,  10.0f * (float)Math.sqrt(3.0f), 0.0f, // top
                    -10.0f, -0.0f, 0.0f, // bottom left
                    10.f, -0.0f, 0.0f  // bottom right
            };
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4;

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

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

    public Triangle()
    {
        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());

        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position(0);

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

        mProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mProgram, vertexShader);
        GLES20.glAttachShader(mProgram, fragmentShader);
        GLES20.glLinkProgram(mProgram);
    }

    public void draw(float[] mvpMatrix)
    {
        GLES20.glUseProgram(mProgram);

        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);

        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

activity_main.xml

画面のレイアウトです.単にOpenGL用の窓を用意しているだけです.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="16dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp"
    android:background="#000"
    tools:context="com.example.XXXXX.XXXXX.OpenGLES20Activity">
    <!-- XXXXXにはユーザ名とプロジェクト名が入ります -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
    </LinearLayout>
</LinearLayout>

結果

f:id:Mugichoko:20171020041615j:plain

画面に三角形が描画された.目標達成です!ちょっとややこしいですが,三角形はPCの画面に写っている訳ではなく,MOVERIO (HMD) の画面に写っています.写真は,それをiPhoneで撮影したものです.