teaching machines

CS 436 Lecture 21 – OpenGL ES

November 4, 2014 by . Filed under cs436, fall 2014, lectures.

Code

activity_main.xml

<RelativeLayout 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"
                tools:context=".MainActivity">

  <android.opengl.GLSurfaceView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/surfaceView"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"/>

</RelativeLayout>

MainActivity.java

package org.twodee.cuber;

import android.graphics.PointF;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.MotionEvent;


public class MainActivity extends ActionBarActivity {
  private GLSurfaceView surfaceView;
  private BoxRenderer renderer;
  private PointF lastTouchAt;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    surfaceView = (GLSurfaceView) findViewById(R.id.surfaceView);

    surfaceView.setEGLContextClientVersion(2);

    renderer = new BoxRenderer();

    surfaceView.setRenderer(renderer);
    surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      lastTouchAt = new PointF(event.getX(), event.getY());
    } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
      final float delta = event.getX() - lastTouchAt.x;
      lastTouchAt = new PointF(event.getX(), event.getY());

      surfaceView.queueEvent(new Runnable() {
        @Override
        public void run() {
          renderer.rotate(delta / 720.0f * 360.0f);
        }
      });
    }

    return true;
  }
}

CuberView.java

package org.twodee.cuber;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.SurfaceHolder;

public class CuberView extends GLSurfaceView {

  public CuberView(Context context,
                   AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public void surfaceCreated(SurfaceHolder holder) {
    super.surfaceCreated(holder);
  }

  @Override
  public void surfaceChanged(SurfaceHolder holder,
                             int format,
                             int w,
                             int h) {
    super.surfaceChanged(holder, format, w, h);
  }

  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
    super.surfaceDestroyed(holder);
  }


}

BoxRenderer.java

package org.twodee.cuber;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.Matrix;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

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

public class BoxRenderer implements Renderer {
  private int[] vbos;
  private int[] ibos;
  private int shaderProgram;
  private float[] rotation;
  private float[] projectionLookAt;
  private float rotationX;
  private int positionHandle;
  private int colorHandle;
  private int rotationHandle;
  private int projectionLookAtHandle;

  @Override
  public void onSurfaceCreated(GL10 gl,
                               EGLConfig config) {
    rotation = new float[16];
    projectionLookAt = new float[16];
    rotationX = 0.0f;

    Matrix.setIdentityM(rotation, 0);

    makeVBOs();
    makeShaderProgram();

    updateHandles();
    updateUniforms();

    GLES20.glClearColor(0.6f, 0.6f, 0.6f, 1.0f);
    GLES20.glEnable(GLES20.GL_DEPTH_TEST);
  }

  private void makeVBOs() {
    // Vertex positions
    float[] positions = {
      -0.5f, -0.5f, 0.5f,
      0.5f, -0.5f, 0.5f,
      -0.5f,  0.5f, 0.5f,
      0.5f,  0.5f, 0.5f,

      0.5f, -0.5f, 0.5f,
      0.5f, -0.5f, -0.5f,
      0.5f, 0.5f, 0.5f,
      0.5f, 0.5f, -0.5f,

      -0.5f, -0.5f, -0.5f,
      -0.5f, -0.5f, 0.5f,
      -0.5f, 0.5f, -0.5f,
      -0.5f, 0.5f, 0.5f,

      0.5f, -0.5f, -0.5f,
      -0.5f, -0.5f, -0.5f,
      0.5f, 0.5f, -0.5f,
      -0.5f, 0.5f, -0.5f
    };

    Log.d("FOO", "allocating floats native buffer");
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(positions.length * 4);
    byteBuffer.order(ByteOrder.nativeOrder());
    FloatBuffer positionsBuffer = byteBuffer.asFloatBuffer();
    positionsBuffer.put(positions);
    positionsBuffer.position(0);

    // Vertex positions
    float[] colors = {
      1.0f, 0.0f, 0.0f,
      1.0f, 0.0f, 0.0f,
      1.0f, 0.0f, 0.0f,
      1.0f, 0.0f, 0.0f,

      0.0f, 1.0f, 0.0f,
      0.0f, 1.0f, 0.0f,
      0.0f, 1.0f, 0.0f,
      0.0f, 1.0f, 0.0f,

      0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 1.0f,

      1.0f, 1.0f, 1.0f,
      1.0f, 0.0f, 1.0f,
      1.0f, 0.0f, 1.0f,
      0.0f, 0.0f, 0.0f,
    };

    byteBuffer = ByteBuffer.allocateDirect(colors.length * 4);
    byteBuffer.order(ByteOrder.nativeOrder());
    FloatBuffer colorsBuffer = byteBuffer.asFloatBuffer();
    colorsBuffer.put(colors);
    colorsBuffer.position(0);

    Log.d("FOO", "allocating floats GPU buffer");
    vbos = new int[2];
    GLES20.glGenBuffers(2, vbos, 0);

    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[0]);
    GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, positionsBuffer.capacity() * 4, positionsBuffer, GLES20.GL_STATIC_DRAW);
    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[1]);
    GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, colorsBuffer.capacity() * 4, colorsBuffer, GLES20.GL_STATIC_DRAW);
    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

    // Connectivity
    short[] faces = {
      0, 1, 2,
      2, 1, 3,
      4, 5, 6,
      6, 5, 7,
      8, 9, 10,
      10, 9, 11,
      12, 13, 14,
      14, 13, 15,
    };

    Log.d("FOO", "allocating indices native buffer");
    byteBuffer = ByteBuffer.allocateDirect(faces.length * 2);
    byteBuffer.order(ByteOrder.nativeOrder());
    ShortBuffer indicesBuffer = byteBuffer.asShortBuffer();
    indicesBuffer.put(faces);
    indicesBuffer.position(0);

    ibos = new int[1];
    GLES20.glGenBuffers(1, ibos, 0);

    GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ibos[0]);
    GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer.capacity() * 2, indicesBuffer, GLES20.GL_STATIC_DRAW);
    GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
  }

  private void makeShaderProgram() {
    String vertexShaderCode =
      "uniform mat4 rotation;" +
        "uniform mat4 projectionLookAt;" +
        "attribute vec3 position;" +
        "attribute vec3 color;" +
        "varying vec3 fcolor;" +
        "void main() {" +
        "  gl_Position = projectionLookAt * rotation * vec4(position, 1.0);" +
        "  fcolor = color;" +
        "}";

    String fragmentShaderCode =
      "precision mediump float;" +
        "varying vec3 fcolor;" +
        "void main() {" +
        "  gl_FragColor = vec4(fcolor, 1.0);" +
        "}";

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

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

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

    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
  }

  @Override
  public void onSurfaceChanged(GL10 gl,
                               int width,
                               int height) {
    GLES20.glViewport(0, 0, width, height);

    float ratio = (float) width / height;

    float[] projection = new float[16];
    Matrix.frustumM(projection, 0, -ratio, ratio, -1, 1, 2.1f, 10.0f);

    float[] lookAt = new float[16];
    Matrix.setLookAtM(lookAt, 0, 0.0f, 0.0f, -3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

    Matrix.multiplyMM(projectionLookAt, 0, projection, 0, lookAt, 0);

    updateUniforms();
  }

  private void updateUniforms() {
    GLES20.glUseProgram(shaderProgram);

    GLES20.glUniformMatrix4fv(rotationHandle, 1, false, rotation, 0);
    GLES20.glUniformMatrix4fv(projectionLookAtHandle, 1, false, projectionLookAt, 0);

    GLES20.glUseProgram(0);
  }

  private void updateHandles() {
    GLES20.glUseProgram(shaderProgram);

    positionHandle = GLES20.glGetAttribLocation(shaderProgram, "position");
    colorHandle = GLES20.glGetAttribLocation(shaderProgram, "color");
    projectionLookAtHandle = GLES20.glGetUniformLocation(shaderProgram, "projectionLookAt");
    rotationHandle = GLES20.glGetUniformLocation(shaderProgram, "rotation");

    GLES20.glUseProgram(0);
  }

  @Override
  public void onDrawFrame(GL10 gl) {
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

    GLES20.glUseProgram(shaderProgram);

    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[0]);
    GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, 0);
    GLES20.glEnableVertexAttribArray(positionHandle);

    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[1]);
    GLES20.glVertexAttribPointer(colorHandle, 3, GLES20.GL_FLOAT, false, 0, 0);
    GLES20.glEnableVertexAttribArray(colorHandle);

    GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ibos[0]);
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, 24, GLES20.GL_UNSIGNED_SHORT, 0);

    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);

    GLES20.glUseProgram(0);
  }

  public void rotate(float degrees) {
    rotationX += degrees;
    Matrix.setRotateM(rotation, 0, rotationX, 0.0f, 1.0f, 0.0f);
    updateUniforms();
  }
}