package com.example.opengltest;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
public class myGlSurfaceView extends GLSurfaceView {
myRenderer myRender;
public myGlSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 3.0 context.
setEGLContextClientVersion(3);
super.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
// Set the Renderer for drawing on the GLSurfaceView
myRender = new myRenderer(context);
setRenderer(myRender);
// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
//private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
private static final float TOUCH_SCALE_FACTOR = 0.0015f;
private float mPreviousX;
private float mPreviousY;
@Override
public boolean onTouchEvent(MotionEvent e) {
// return super.onTouchEvent(event);
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = x - mPreviousX;
//subtract, so the cube moves the same direction as your finger.
//with plus it moves the opposite direction.
myRender.setX(myRender.getX() - (dx * TOUCH_SCALE_FACTOR));
float dy = y - mPreviousY;
myRender.setY(myRender.getY() - (dy * TOUCH_SCALE_FACTOR));
}
mPreviousX = x;
mPreviousY = y;
return true;
}
}
myRenderer.java
package com.example.opengltest;
import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class myRenderer implements GLSurfaceView.Renderer {
private int mWidth;
private int mHeight;
private static String TAG = "myRenderer";
public Cube mCube;
private float mAngle =0;
private float mTransY=0;
private float mTransX=0;
private static final float Z_NEAR = 1f;
private static final float Z_FAR = 40f;
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
private final float[] mRotationMatrix = new float[16];
public myRenderer(Context context) {
//cube can not be instianated here, because of "no egl context" no clue.
//do it in onSurfaceCreate and it is fine. odd, but workable solution.
}
public static int LoadShader(int type, String shaderSrc) {
int shader;
int[] compiled = new int[1];
// Create the shader object
shader = GLES30.glCreateShader(type);
if (shader == 0) {
return 0;
}
// Load the shader source
GLES30.glShaderSource(shader, shaderSrc);
// Compile the shader
GLES30.glCompileShader(shader);
// Check the compile status
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Erorr!!!!");
Log.e(TAG, GLES30.glGetShaderInfoLog(shader));
GLES30.glDeleteShader(shader);
return 0;
}
return shader;
}
public static void checkGlError(String glOperation) {
int error;
while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR) {
Log.e(TAG, glOperation + ": glError " + error);
throw new RuntimeException(glOperation + ": glError " + error);
}
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//set the clear buffer color to light gray.
//GLES30.glClearColor(0.9f, .9f, 0.9f, 0.9f);
//set the clear buffer color to a dark grey.
GLES30.glClearColor(0.1f, .1f, 0.1f, 0.9f);
//initialize the cube code for drawing.
mCube = new Cube();
//if we had other objects setup them up here as well.
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
// Set the viewport
GLES30.glViewport(0, 0, mWidth, mHeight);
float aspect = (float) width / height;
// this projection matrix is applied to object coordinates
//no idea why 53.13f, it was used in another example and it worked.
Matrix.perspectiveM(mProjectionMatrix, 0, 53.13f, aspect, Z_NEAR, Z_FAR);
}
@Override
public void onDrawFrame(GL10 gl) {
// Clear the color buffer set above by glClearColor.
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
//need this otherwise, it will over right stuff and the cube will look wrong!
GLES30.glEnable(GLES30.GL_DEPTH_TEST);
// Set the camera position (View matrix) note Matrix is an include, not a declared method.
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Create a rotation and translation for the cube
Matrix.setIdentityM(mRotationMatrix, 0);
//move the cube up/down and left/right
Matrix.translateM(mRotationMatrix, 0, mTransX, mTransY, 0);
//mangle is how fast, x,y,z which directions it rotates.
Matrix.rotateM(mRotationMatrix, 0, mAngle, 1.0f, 1.0f, 1.0f);
// combine the model with the view matrix
Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mRotationMatrix, 0);
// combine the model-view with the projection matrix
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
mCube.draw(mMVPMatrix);
//change the angle, so the cube will spin.
mAngle+=4;
}
//used the touch listener to move the cube up/down (y) and left/right (x)
public float getY() {
return mTransY;
}
public void setY(float mY) {
mTransY = mY;
}
public float getX() {
return mTransX;
}
public void setX(float mX) {
mTransX = mX;
}
}
Cube.java
package com.example.opengltest;
import android.opengl.GLES30;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
public class Cube {
private int mProgramObject;
private int mMVPMatrixHandle;
private int mColorHandle;
private FloatBuffer mVertices;
//initial size of the cube. set here, so it is easier to change later.
float size = 0.4f;
//this is the initial data, which will need to translated into the mVertices variable in the consturctor.
float[] mVerticesData = new float[]{
////////////////////////////////////////////////////////////////////
// FRONT
////////////////////////////////////////////////////////////////////
// Triangle 1
-size, size, size, // top-left
-size, -size, size, // bottom-left
size, -size, size, // bottom-right
// Triangle 2
size, -size, size, // bottom-right
size, size, size, // top-right
-size, size, size, // top-left
////////////////////////////////////////////////////////////////////
// BACK
////////////////////////////////////////////////////////////////////
// Triangle 1
-size, size, -size, // top-left
-size, -size, -size, // bottom-left
size, -size, -size, // bottom-right
// Triangle 2
size, -size, -size, // bottom-right
size, size, -size, // top-right
-size, size, -size, // top-left
////////////////////////////////////////////////////////////////////
// LEFT
////////////////////////////////////////////////////////////////////
// Triangle 1
-size, size, -size, // top-left
-size, -size, -size, // bottom-left
-size, -size, size, // bottom-right
// Triangle 2
-size, -size, size, // bottom-right
-size, size, size, // top-right
-size, size, -size, // top-left
////////////////////////////////////////////////////////////////////
// RIGHT
////////////////////////////////////////////////////////////////////
// Triangle 1
size, size, -size, // top-left
size, -size, -size, // bottom-left
size, -size, size, // bottom-right
// Triangle 2
size, -size, size, // bottom-right
size, size, size, // top-right
size, size, -size, // top-left
////////////////////////////////////////////////////////////////////
// TOP
////////////////////////////////////////////////////////////////////
// Triangle 1
-size, size, -size, // top-left
-size, size, size, // bottom-left
size, size, size, // bottom-right
// Triangle 2
size, size, size, // bottom-right
size, size, -size, // top-right
-size, size, -size, // top-left
////////////////////////////////////////////////////////////////////
// BOTTOM
////////////////////////////////////////////////////////////////////
// Triangle 1
-size, -size, -size, // top-left
-size, -size, size, // bottom-left
size, -size, size, // bottom-right
// Triangle 2
size, -size, size, // bottom-right
size, -size, -size, // top-right
-size, -size, -size // top-left
};
float colorcyan[] = myColor.cyan();
float colorblue[] = myColor.blue();
float colorred[] = myColor.red();
float colorgray[] = myColor.gray();
float colorgreen[] = myColor.green();
float coloryellow[] = myColor.yellow();
//vertex shader code
String vShaderStr =
"#version 300 es \n"
+ "uniform mat4 uMVPMatrix; \n"
+ "in vec4 vPosition; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_Position = uMVPMatrix * vPosition; \n"
+ "} \n";
//fragment shader code.
String fShaderStr =
"#version 300 es \n"
+ "precision mediump float; \n"
+ "uniform vec4 vColor; \n"
+ "out vec4 fragColor; \n"
+ "void main() \n"
+ "{ \n"
+ " fragColor = vColor; \n"
+ "} \n";
String TAG = "Cube";
//finally some methods
//constructor
public Cube() {
//first setup the mVertices correctly.
mVertices = ByteBuffer
.allocateDirect(mVerticesData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(mVerticesData);
mVertices.position(0);
//setup the shaders
int vertexShader;
int fragmentShader;
int programObject;
int[] linked = new int[1];
// Load the vertex/fragment shaders
vertexShader = myRenderer.LoadShader(GLES30.GL_VERTEX_SHADER, vShaderStr);
fragmentShader = myRenderer.LoadShader(GLES30.GL_FRAGMENT_SHADER, fShaderStr);
// Create the program object
programObject = GLES30.glCreateProgram();
if (programObject == 0) {
Log.e(TAG, "So some kind of error, but what?");
return;
}
GLES30.glAttachShader(programObject, vertexShader);
GLES30.glAttachShader(programObject, fragmentShader);
// Bind vPosition to attribute 0
GLES30.glBindAttribLocation(programObject, 0, "vPosition");
// Link the program
GLES30.glLinkProgram(programObject);
// Check the link status
GLES30.glGetProgramiv(programObject, GLES30.GL_LINK_STATUS, linked, 0);
if (linked[0] == 0) {
Log.e(TAG, "Error linking program:");
Log.e(TAG, GLES30.glGetProgramInfoLog(programObject));
GLES30.glDeleteProgram(programObject);
return;
}
// Store the program object
mProgramObject = programObject;
//now everything is setup and ready to draw.
}
public void draw(float[] mvpMatrix) {
// Use the program object
GLES30.glUseProgram(mProgramObject);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES30.glGetUniformLocation(mProgramObject, "uMVPMatrix");
myRenderer.checkGlError("glGetUniformLocation");
// get handle to fragment shader's vColor member
mColorHandle = GLES30.glGetUniformLocation(mProgramObject, "vColor");
// Apply the projection and view transformation
GLES30.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
myRenderer.checkGlError("glUniformMatrix4fv");
int VERTEX_POS_INDX = 0;
mVertices.position(VERTEX_POS_INDX); //just in case. We did it already though.
//add all the points to the space, so they can be correct by the transformations.
//would need to do this even if there were no transformations actually.
GLES30.glVertexAttribPointer(VERTEX_POS_INDX, 3, GLES30.GL_FLOAT,
false, 0, mVertices);
GLES30.glEnableVertexAttribArray(VERTEX_POS_INDX);
//Now we are ready to draw the cube finally.
int startPos =0;
int verticesPerface = 6;
//draw front face
GLES30.glUniform4fv(mColorHandle, 1, colorblue, 0);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);
startPos += verticesPerface;
//draw back face
GLES30.glUniform4fv(mColorHandle, 1, colorcyan, 0);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, startPos, verticesPerface);
startPos += verticesPerface;
//draw left face
GLES30.glUniform4fv(mColorHandle, 1, colorred, 0);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);
startPos += verticesPerface;
//draw right face
GLES30.glUniform4fv(mColorHandle, 1, colorgray, 0);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);
startPos += verticesPerface;
//draw top face
GLES30.glUniform4fv(mColorHandle, 1, colorgreen, 0);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);
startPos += verticesPerface;
//draw bottom face
GLES30.glUniform4fv(mColorHandle, 1, coloryellow, 0);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,startPos,verticesPerface);
//last face, so no need to increment.
}
}
package com.example.opengltest;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
if (detectOpenGLES30()) {
//so we know it a opengl 3.0 and use our extended GLsurfaceview.
setContentView(new myGlSurfaceView(this));
} else {
// This is where you could create an OpenGL ES 2.0 and/or 1.x compatible
// renderer if you wanted to support both ES 1 and ES 2.
Log.e("openglcube", "OpenGL ES 3.0 not supported on device. Exiting...");
finish();
}
}
private boolean detectOpenGLES30() {
ActivityManager am =
(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
return (info.reqGlEsVersion >= 0x30000);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
}
그래서 자세한 설명은 이전 글을 참고하시기 바라며 이번에는 동일한 소스를 사용하여 사각형을 그리는 부분만 추가하여 테스트를 진행합니다.
삼각형을 그릴때는 Triangle.java 파일 만들었는데 사각형을 그리기 위해서 Square.java 파일을 생성하는데 일부 변경이 되고 Renderer 파일도 조금 수정해주면 됩니다.
다른 소스는 전에 생성한 내용을 그대로 사용하고 여기서는 Square와 Renderer 파일의 변경된 부분만 설명합니다.
실행했을 때 결과화면은 아래와 같이 멋없는 사각형입니다.
Square.java 파일을 생성하고 사각형 용으로 도형의 좌표를 표기합니다.
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
그림에 있는 좌표를 표시하고 그리는 순서를 정하여 꼭지점의 순서를 정하게 됩니다.
특이한점은 사각형을 사각형으로 그리지 않고 3각형 2개를 그리는 방법으로 진행합니다.
private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
이렇게 시계 반대방향으로 순서를 나열하여 좌측과 우측의 삼각형을 그리도록 정의합니다.
// 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);
이런 작업 순서는 ByteBuffer에 저장해 둡니다.
나머지 소스들은 삼각형일때 triangleCoords로 되어있던 부분을 squareCoords로 변경합니다.
draw() 함수에서 삼각형은 꼭지점을 이용하여 그리고 사각형은 Buffer에 있는 순서를 이용하여 그리도록 수정됩니다.
// Draw the triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // Draw the square GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
MyGLRenderer.java 파일에서는 MVP Matrix가 사용됩니다.
MVP(Model, View, Projection)은 카메라에서 모델을 바라보는 형상을 표시하는 것인데
삼각형에서는 사용하지 않았는데 사각형에서는 왜 필요한지 지식이 짧아 좀더 학습이 필요하네요.
@Override public void onDrawFrame(GL10 gl) { float[] scratch = new float[16]; // Draw background color GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT); // Set the camera position (View matrix) Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f); // Calculate the projection and view transformation Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0); // Draw square mSquare.draw(mMVPMatrix); }
그리는 부분에서 삼각형은 Array, 사각형은 Element의 차이인데 나중에 알게되면 내용 추가를 해야 할 것 같습니다.
public class MainActivity extends AppCompatActivity { private GLSurfaceView gLView;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); // Create a GLSurfaceView instance and set it // as the ContentView for this Activity. gLView = new MyGLSurfaceView(this); setContentView(gLView); } }
MyGLRenderer.java
package com.copycoding.openglsquare;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
// private Triangle mTriangle;
private Square mSquare;
private static final String TAG = "MyGLRenderer";
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
private final float[] mRotationMatrix = new float[16];
private float mAngle;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Set the background frame color
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// initialize a triangle
mSquare = new Square();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
@Override
public void onDrawFrame(GL10 gl) {
float[] scratch = new float[16];
// Draw background color
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
// Set the camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
// Draw square
mSquare.draw(mMVPMatrix);
}
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 = GLES30.glCreateShader(type);
// add the source code to the shader and compile it
GLES30.glShaderSource(shader, shaderCode);
GLES30.glCompileShader(shader);
return shader;
}
public static void checkGlError(String glOperation) {
int error;
while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR) {
Log.e(TAG, glOperation + ": glError " + error);
throw new RuntimeException(glOperation + ": glError " + error);
}
}
}
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer renderer; public MyGLSurfaceView(Context context) { super(context);
// Create an OpenGL ES 2.0 context setEGLContextClientVersion(2);
renderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView setRenderer(renderer); } }
Square.java
package com.copycoding.openglsquare;
import android.opengl.GLES30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
public class Square {
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// The matrix must be included as a modifier of gl_Position.
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
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
// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
private final int mProgram;
private int positionHandle;
private int colorHandle;
private int mVPMatrixHandle;
// private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
public Square() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (number of coordinate values * 4 bytes per float)
squareCoords.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(squareCoords);
// set the buffer to read the first coordinate
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);
int vertexShader = MyGLRenderer.loadShader(GLES30.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES30.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// create empty OpenGL ES Program
mProgram = GLES30.glCreateProgram();
// add the vertex shader to program
GLES30.glAttachShader(mProgram, vertexShader);
// add the fragment shader to program
GLES30.glAttachShader(mProgram, fragmentShader);
// creates OpenGL ES program executables
GLES30.glLinkProgram(mProgram);
}
public void draw(float[] mvpMatrix) {
// Add program to OpenGL ES environment
GLES30.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES30.glEnableVertexAttribArray(positionHandle);
// Prepare the triangle coordinate data
GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES30.GL_FLOAT, false,
vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member
colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
GLES30.glUniform4fv(colorHandle, 1, color, 0);
// get handle to shape's transformation matrix
mVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
MyGLRenderer.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
GLES30.glUniformMatrix4fv(mVPMatrixHandle, 1, false, mvpMatrix, 0);
MyGLRenderer.checkGlError("glUniformMatrix4fv");
// Draw the square
GLES30.glDrawElements(
GLES30.GL_TRIANGLES, drawOrder.length,
GLES30.GL_UNSIGNED_SHORT, drawListBuffer);
// Draw the triangle
// GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);
// Disable vertex array
GLES30.glDisableVertexAttribArray(positionHandle);
}
}
·OpenGL ES 1.0 및 1.1 - 이 API 사양은 Android 1.0 이상에서지원됩니다. ·OpenGL ES 2.0 - 이 API 사양은 Android 2.2(API 레벨 8) 이상에서지원됩니다. ·OpenGL ES 3.0 - 이 API 사양은 Android 4.3(API 레벨 18) 이상에서지원됩니다. ·OpenGL ES 3.1 - 이 API 사양은 Android 5.0(API 레벨 21) 이상에서지원됩니다.
OpenGL ES 버전별로 지원하는 안드로이드 버전에 차이가 있는데 저는 OpenGL ES 3.0을 기준으로 작업을 진행 합니다.프로젝트를 생성할 때 버전에 맞추어 선택을 합니다.
프로젝트 생성시 Minimum SDK를 선택한 것에 맞추어 OpenGL 3.0 사용 설정을 합니다.
2. 이미지 그리기
먼저 그리고자 하는 대상을 선택 하겠습니다.세모, 네모가 제일 간단한데 그중에 좀더 간단할것 같아 보이는 세모를 정의해 보겠습니다.
Google에 있는 설명을 그대로 이용해서 작성을 하려고 하는데 사이트에는 설명을 위해 프로그램을 조각내서 이곳 저곳에 설명을 해놓았는데 한번 붙여서 구현을 해봅니다.
원점을 기준으로 삼각형의 좌표상 위치를 설정합니다.구글 사이트에 있는 삼각형 정의입니다.
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 };
상단은 y 축만 값을 가지면 되고 좌우측 좌표 점 값으로 삼각형을 정의합니다.
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);
좌표점을 이용하여 삼각형 꼭지점을 정의합니다.
꼭지점을 이용하여 삼각형을 그립니다.
public void draw() { // Add program to OpenGL ES environment GLES30.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices GLES30.glEnableVertexAttribArray(positionHandle);
// Prepare the triangle coordinate data GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle GLES30.glUniform4fv(colorHandle, 1, color, 0);
// Draw the triangle GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);
Google에서는 OpenGL ES 2.0으로 소스를 설명을 하고 있는데 3.0으로 변경을 해주었습니다.
3. 도형 렌더링
삼각형을 그리긴 했지만 Surface에 표현하는 작업들이 남았습니다.
위에서 설명한 2개의 기본 클래스인 Surface를 정의하고 Rendering 을 하는 두개의 프로그램이 추가되어야 합니다.
먼저 Renderer 파일을 생성합니다.
Google에 있는대로 MyGLRenderer.java 파일을 생성하고
필수 메소드를 추가해 줍니다.
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // Set the background frame color GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // initialize a triangle mTriangle = new Triangle(); }
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES30.glViewport(0, 0, width, height); }
@Override public void onDrawFrame(GL10 gl) { // Redraw background color GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT); }
추가한 메소드에 코드를 추가합니다.
onSurfaceCreated() - 뷰의 OpenGL ES 환경을 설정하기 위해 한 번 호출됩니다.
초기값을 설정해야 하면 여기에 정의합니다.
onDrawFrame() - 뷰를 다시 그릴 때마다 호출됩니다.
onSurfaceChanged() - 기기의 화면 방향이 변경되는 경우와 같이 뷰의 도형이 변경되면 호출됩니다.
이제 Surface 파일을 작성합니다.
MyGLSurfaceView.java 파일을 생성하고 GLSurfaceView로부터 상속을 받습니다.
그리고 생성자를 추가하는데 첫번째 생성자를 선택 합니다.
private final MyGLRenderer renderer; public MyGLSurfaceView(Context context) { super(context);
// Create an OpenGL ES 2.0 context setEGLContextClientVersion(2);
renderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView setRenderer(renderer); }
소스는 간단합니다.
생성한 삼격형 렌더링을 SurfaceView에 넣어주면 됩니다.
4. 프로젝트 실행
이제 모든 작업이 완료되었으니 MainActivity에서 생성시 layout에 지금까지의 작업을 실행하기만 하면 되겠네요.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); // Create a GLSurfaceView instance and set it // as the ContentView for this Activity. gLView = new MyGLSurfaceView(this); setContentView(gLView); }
MainActivity 작업이 제일 간단 합니다.
Android layout을 사용하지 않고 OpenGL에서 생성하여 사용한다고 설명하였듯이 새로 생성한 SerfaceView를 추가해주면 끝입니다.
구글에 있는 소스가 너무 나뉘어있고 이곳저곳에서 생략된 표시도 있어서 작동을 하기는 할까? 하는 걱정이 있었는데 위에 있는 소스를 실행하면 좀 단순 하지만 결과가 나옵니다.
Layout이나 Gradle에 추가한 작업은 없고 그냥 3개의 신규 파일이 추가되고 2개의 파일(AndroidManifest.xml, MainActivity.java)에 몇 줄 소스 작업만 진행하였습니다.
public class MainActivity extends AppCompatActivity { private GLSurfaceView gLView;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); // Create a GLSurfaceView instance and set it // as the ContentView for this Activity. gLView = new MyGLSurfaceView(this); setContentView(gLView); } }
public class MyGLRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // Set the background frame color GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // initialize a triangle mTriangle = new Triangle(); }
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { GLES30.glViewport(0, 0, width, height); }
@Override public void onDrawFrame(GL10 gl) { // Redraw background color GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
mTriangle.draw(); } 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 = GLES30.glCreateShader(type);
// add the source code to the shader and compile it GLES30.glShaderSource(shader, shaderCode); GLES30.glCompileShader(shader);
// 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 };
// Set color with red, green, blue and alpha (opacity) values float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
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
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 = MyGLRenderer.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = MyGLRenderer.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode);
// create empty OpenGL ES Program mProgram = GLES30.glCreateProgram();
// add the vertex shader to program GLES30.glAttachShader(mProgram, vertexShader);
// add the fragment shader to program GLES30.glAttachShader(mProgram, fragmentShader);
// creates OpenGL ES program executables GLES30.glLinkProgram(mProgram); }
public void draw() { // Add program to OpenGL ES environment GLES30.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices GLES30.glEnableVertexAttribArray(positionHandle);
// Prepare the triangle coordinate data GLES30.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle GLES30.glUniform4fv(colorHandle, 1, color, 0);
// Draw the triangle GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertexCount);