59
OpenGL en Android LSUB, GYSC, URJC

OpenGL en Android - Laboratorio de Sistemaslsub.org/mov/13.opengl.pdf · •Multiplicación de matrices! ... • ¿Por qué algunas matrices tienen 4D (en lugar de 3D)?! ... • Hay

Embed Size (px)

Citation preview

OpenGL en

Android

LSUB, GYSC, URJC

OpenGL

• OpenGL en Android es la versión ES

• OpenGL es un estándar de rendering

• ES for embedded systems

• La versión actual es 3.0, pero en Android 2.0

OpenGL

•http://developer.android.com/training/graphics/opengl/index.html

•Tutorial básico introductorio

•Sin texturas

OpenGL

• Transformaciones 3D

• Rendering basado en polígonos

• Define un pipeline de rendering en la tarjeta, se descargan programas (shaders)

OpenGL: Este ejemplo

•http://lsub.org/mov/glandroid.tgz

OpenGL ejemplo

• Antes del <application></application>

• En el Manifest, (sólo si voy a usar texto):

<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />

<supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />

• En el Manifest, voy a usar OpenGL 2.0:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

Activity

•Creo la SurfaceView y la expando

•No hace falta layout (pantalla completa)

•Se puede poner como widget (no el ejemplo)

OpenGL Activity

package org.lsub.paurea.surfaceview;

import android.app.Activity;

import android.os.Bundle;

import android.opengl.GLSurfaceView;

!

!

OpenGL Activitypublic class MainActivity extends Activity {

private GLSurfaceView mGLView;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mGLView = new MyGLSurfaceView(this);

setContentView(mGLView);

}

}

OpenGL Activity

• Hay que llamar a onResume y onPause • En los callbacks de la Activity

OpenGL Activity @Override

protected void onResume()

{

super.onResume();

mGLView.onResume();

}

@Override

protected void onPause()

{

super.onPause();

mGLView.onPause()

}}

!

GLSurfaceView

•Varios modos de render

•Mejor renderizar cuando está sucio

• Se puede renderizar en demanda o de forma continua

GLSurfaceView

•Maneja los eventos de tacto

•En el ejemplo cambia el ángulo del dibujo

GLSurfaceViewclass MyGLSurfaceView extends GLSurfaceView {

private final MyGLRenderer mRenderer;

public MyGLSurfaceView(Context context){

super(context);

setEGLContextClientVersion(2);

mRenderer = new MyGLRenderer(context);

setRenderer(mRenderer);

setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

}

GLSurfaceView!

private final float TOUCH_SCALE_FACTOR = 180.0f / 320;

private float mPreviousX;

private float mPreviousY;

@Override

public boolean onTouchEvent(MotionEvent e) {

float x = e.getX();

float y = e.getY();

switch (e.getAction()) {

case MotionEvent.ACTION_MOVE:

float dx = x - mPreviousX;

float dy = y - mPreviousY;

// reverse direction of rotation above the mid-line

if (y > getHeight() / 2) {

dx = dx * -1 ;

GLSurfaceView!

}

// reverse direction of rotation to left of the mid-line

if (x < getWidth() / 2) {

dy = dy * -1 ;

}

float angle = mRenderer.getAngle() +

((dx + dy) * TOUCH_SCALE_FACTOR);

mRenderer.setAngle(angle); // = 180.0f / 320

requestRender();

}

mPreviousX = x;

mPreviousY = y;

return true;

}

GLRenderer

package org.lsub.paurea.surfaceview;

import android.opengl.GLES20;

import android.opengl.GLSurfaceView;

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.Matrix;

import android.content.Context;

GLRenderer

• Tiene 3 callbacks importantes

• onSurfaceCreated: la primera vez o si se pierde el contexto de openGL y hay que recrearlo

• onSurfaceChanged: si hay un cambio importante (e.g. landscape) y la primera vez

• onDrawFrame: cada vez que hay que dibujar un frame

GLRenderer

• En onSurfaceCreated

• Colorea el fondo

• Crea la imagen

GLRendererpublic class MyGLRenderer implements GLSurfaceView.Renderer {

private final Context mActivityContext;

//private Triangle triangle;

private Square sprite;

//Matrix Initializations

private final float[] mMVPMatrix = new float[16];

private final float[] mProjMatrix = new float[16];

private final float[] mVMatrix = new float[16];

private float[] mRotationMatrix = new float[16];

OpenGL GLRenderer

//Declare as volatile (race?)

public volatile float mAngle;

public MyGLRenderer(final Context context) { mActivityContext = context }

public void onSurfaceCreated(GL10 unused, EGLConfig config) {

//Set the background frame color

GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

//Initialize Shapes

sprite = new Square(mActivityContext);

}

GLRenderer

•En onDrawFrame

•Se pasa la imagen por las transformaciones

•Modelo, Vista, Proyección (MVP)

•Multiplicación de matrices

•Luego se dibuja

GLRenderer

• MVP

•Model, cambio de coordenadas de objeto al mundo

• View, cambio de coordenadas de la cámara

• Projection, proyección sobre la cámara

GLRenderer

• Projection matrix

• Un poco especial, aplica la distorsión de la perspectiva (puede ser ortográfica)

GLRenderer

Coordenadas Homogéneas

• ¿Por qué algunas matrices tienen 4D (en lugar de 3D)?

• Porque están en coordenadas homogéneas

• Es un tipo de álgebra que se llama álgebra proyectiva

Coordenadas Homogéneas

• Idea del álgebra proyectiva

• Hay puntos en el infinito (como cuando dibujamos con puntos de fuga con perspectiva)

Coordenadas Homogéneas

Coordenadas Homogéneas

• ¿Como se representa un vector que apunta al infinito?

• En coordenadas homogéneas

Coordenadas Homogéneas

• ¿Como se representa un punto en el infinito?

• Una coordenada extra P

•  (X, Y, Z, P) (X/P, Y/P, Z/P)

• Si P vale 0, el punto está en el infinito

• El punto (0, 0, 0, 0) no vale, el origen es (0, 0, 0, 1)

• Dos coordenadas u, v representan el mismo punto si ku = v

Coordenadas Homogéneas

• (kX, kY, kZ, kP) y (X, Y, Z, P) son el mismo punto

• Es una clase de equivalencia o espacio cociente

• Todos los puntos/vectores en una recta son el mismo (4D - 1D = 3D)

Coordenadas Homogéneas

• Puedo representar transformaciones algebraicas

• Con “perspectiva”, por ejemplo proyectar sobre el viewport

Matrices

• La mayor parte de las veces, no hace falta usar directamente coordenadas homogéneas (ni matrices)

• Las construyo describiéndolas (como en el ejemplo)

• Pero los arrays son de 4x4 = 16 entradas

public void onDrawFrame(GL10 unused) {

float[] scratch = new float[16];

//Redraw background color

GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

//Set the camera position (View Matrix)

Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

//Calculate the projection and view transformation

Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);

//Create a rotation transformation for the triangle

Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);

//Combine the rotation matrix with the projection and camera view

Matrix.multiplyMM(scratch, 0, mRotationMatrix, 0, mMVPMatrix, 0);

//Draw Shape

//triangle.Draw(mMVPMatrix);

sprite.Draw(scratch);

}

public void onSurfaceChanged(GL10 unused, int width, int height) {

GLES20.glViewport(0, 0, width, height);

float ratio = (float) width / height;

//This Projection Matrix is applied to object coordinates in the onDrawFrame() method

Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);

}

GLRenderer

•onSurfaceChanged se crea el ViewPort

•Que es la ventana al mundo

•Se obtiene de la transformación inducida por el frustrum (pirámide truncada)

GLRenderer

GLRenderer

•loadShader carga los shaders

•Se verán más adelante

public static int loadShader(int type, String shaderCode) {

int shader = GLES20.glCreateShader(type);

//Add The Source Code and Compile it

GLES20.glShaderSource(shader, shaderCode);

GLES20.glCompileShader(shader);

return shader;

}

!

public float getAngle() {

return mAngle;

}

public void setAngle(float angle) {

mAngle = angle;

}

}

Squarepackage org.lsub.paurea.surfaceview;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.nio.FloatBuffer;

import java.nio.ShortBuffer;

import android.opengl.GLES20;

import android.content.Context;

import android.opengl.GLUtils;

import android.graphics.BitmapFactory;

import android.graphics.Bitmap;

public class Square {

//Reference to Activity Context

private final Context mActivityContext;

//Added for Textures

private final FloatBuffer mSquareTextureCoordinates;

private int mTextureUniformHandle;

private int mTextureCoordinateHandle;

private final int mTextureCoordinateDataSize = 2;

Shaders

•Código que se compila, enlaza y carga en tiempo de ejecución en la tarjeta de video

•En el ejemplo de vértices y fragmentos

•Son un pipeline de transformaciones

•En el shader de fragmentos se aplican las texturas

Shaders

private final String vertexShaderCode =

"attribute vec2 a_TexCoordinate;" +

"varying vec2 v_TexCoordinate;" +

"uniform mat4 uMVPMatrix;" +

"attribute vec4 vPosition;" +

"void main() {" +

" gl_Position = vPosition * uMVPMatrix;" +

" v_TexCoordinate = a_TexCoordinate;" +

"}";

private final String fragmentShaderCode =

"precision mediump float;" +

"uniform vec4 vColor;" +

"uniform sampler2D u_Texture;" +

"varying vec2 v_TexCoordinate;" +

"void main() {" +

" gl_FragColor = (vColor * texture2D(u_Texture, v_TexCoordinate));" +

"}";

Square

• Coordenadas

• Se pueden usar con 2D o 3D (ver constante COORDS_PER_VERTEX

• Las otras valen 0

Square

Square

• Hay que serializar las cosas para OpenGL

• Se usa ByteBuffer

• Hay que convertir el endianness de Java al nativo y ponerlo en un formato que entiende OpenGL

private final int shaderProgram;

private final FloatBuffer vertexBuffer;

private final ShortBuffer drawListBuffer;

private int mPositionHandle;

private int mColorHandle;

private int mMVPMatrixHandle;

// number of coordinates per vertex in this array

static final int COORDS_PER_VERTEX = 2;

static float spriteCoords[] = {

-0.5f, 0.5f, // top left

-0.5f, -0.5f, // bottom left

0.5f, -0.5f, // bottom right

0.5f, 0.5f}; //top right

private short drawOrder[] = {0, 1, 2, 0, 2, 3}; //Order to draw vertices

private final int vertexStride = COORDS_PER_VERTEX * 4; //Bytes per vertex

// Set color with red, green, blue and alpha (opacity) values

float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};

public Square(final Context activityContext) {

mActivityContext = activityContext;

! //Initialize Vertex Byte Buffer for Shape Coordinates / # of coordinate values * 4 bytes per float

ByteBuffer bb = ByteBuffer.allocateDirect(spriteCoords.length * 4);

//Use the Device'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(spriteCoords);

//Set the Buffer to Read the first coordinate

vertexBuffer.position(0);

// S, T (or X, Y)

// Texture coordinate data.

// Because images have a Y axis pointing downward (values increase as you move down) while

// OpenGL has a Y axis pointing upward, we adjust for that here by flipping the Y axis.

// What's more is that the texture coordinates are the same for every face.

final float[] SquareTextureCoordinateData = {

-0.5f, 0.5f,

-0.5f, -0.5f,

0.5f, -0.5f,

0.5f, 0.5f

};

mSquareTextureCoordinates = ByteBuffer.allocateDirect(SquareTextureCoordinateData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();

mSquareTextureCoordinates.put(SquareTextureCoordinateData).position(0);

ByteBuffer dlb = ByteBuffer.allocateDirect(spriteCoords.length * 2);

dlb.order(ByteOrder.nativeOrder());

drawListBuffer = dlb.asShortBuffer();

drawListBuffer.put(drawOrder);

drawListBuffer.position(0);

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

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

shaderProgram = GLES20.glCreateProgram();

GLES20.glAttachShader(shaderProgram, vertexShader);

GLES20.glAttachShader(shaderProgram, fragmentShader);

//Texture Code

GLES20.glBindAttribLocation(shaderProgram, 0, "a_TexCoordinate");

GLES20.glLinkProgram(shaderProgram);

//Load the texture

mTextureDataHandle = loadTexture(mActivityContext, R.drawable.texture);

}

public void Draw(float[] mvpMatrix) {

//Add program to OpenGL ES Environment

GLES20.glUseProgram(shaderProgram);

//Get handle to vertex shader's vPosition member

mPositionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");

//Enable a handle to the triangle vertices

GLES20.glEnableVertexAttribArray(mPositionHandle);

//Prepare the triangle coordinate data

GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);

//Get Handle to Fragment Shader's vColor member

mColorHandle = GLES20.glGetUniformLocation(shaderProgram, "vColor");

//Set the Color for drawing the triangle

GLES20.glUniform4fv(mColorHandle, 1, color, 0);

//Set Texture Handles and bind Texture

mTextureUniformHandle = GLES20.glGetAttribLocation(shaderProgram, "u_Texture");

mTextureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "a_TexCoordinate");

//Set the active texture unit to texture unit 0.

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

! //Bind the texture to this unit.

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);

//Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.

GLES20.glUniform1i(mTextureUniformHandle, 0);

//Pass in the texture coordinate information

mSquareTextureCoordinates.position(0);

GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0,

mSquareTextureCoordinates);

GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);

//Get Handle to Shape's Transformation Matrix

mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");

//Apply the projection and view transformation

GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

//Draw the square

GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT,

drawListBuffer);

GLES20.glDisableVertexAttribArray(mPositionHandle);

}

public static int loadTexture(final Context context, final int resourceId) {

final int[] textureHandle = new int[1];

GLES20.glGenTextures(1, textureHandle, 0);

if (textureHandle[0] != 0) {

final BitmapFactory.Options options = new BitmapFactory.Options();

options.inScaled = false; // No pre-scaling

// Read in the resource

final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

// Bind to the texture in OpenGL

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

// Set filtering

GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);

GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

!

// Load the bitmap into the bound texture.

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

// Recycle the bitmap, since its data has been loaded into OpenGL.

bitmap.recycle();

}

if (textureHandle[0] == 0) {

throw new RuntimeException("Error loading texture.");

}

return textureHandle[0];

}

}

OpenGL• Muchas más cosas

• Iluminación, shaders más complicados, blending etc.

• Más tutoriales: http://insanitydesign.com/wp/projects/nehe-android-ports/ http://www.learnopengles.com/android-lesson-one-getting-started/

• Shading language: https://www.opengl.org/documentation/glsl/

• Recetas shaders: http://www.amazon.com/OpenGL-Shading-Language-Cookbook-Edition/dp/1782167021