반응형



간단한 OpenGL ES 2.0 예제입니다.




1. 새로운 프로젝트를 생성하고 안드로이드 매니페스트 파일 AndroidManifest.xml에 사용하려는 OpenGL ES 버전을 2.0으로 지정해줍니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.tistory.webnautes.openglesexample">

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

   <application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"





2. 안드로이드 어플리케이션에서 OpenGL ES를 사용하여 그래픽 객체를 그리려면 GLSurfaceView 클래스와 GLSurfaceView.Renderer 메소드를 구현해야 합니다.


GLSurfaceView 클래스는 그래픽 객체가 그려지게 되는 View입니다.  

이 클래스를 사용하기 위해서는 GLSurfaceView의 인스턴스를 생성하고 별도의 클래스로 구현한 renderer를 GLSurfaceView.setRenderer 메소드를 사용하여 GLSurfaceView의 인스턴스에 attatch합니다.


GLSurfaceView.Renderer 인터페이스는 GLSurfaceView에 그래픽 객체를 그리기 위해 필요한 메소드를  정의합니다.

별도의 클래스로써 이 인터페이스의 구현을 해주어야 합니다.




액티비티를 위한 ContentView로써 GLSurfaceView를 사용하도록 합니다.


MainActivity.java


package com.tistory.webnautes.openglesexample;

import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

   private GLSurfaceView mGLView;

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

       // GLSurfaceView 인스턴스를 생성하여 Activity를 위한 Content View로 설정한다.
       mGLView = new MyGLSurfaceView(this);
       setContentView(mGLView);
   }
}




GLSurfaceView는 OpenGL ES를 이용하여 생성한 그래픽 객체를 화면에 보여주기 위해 필요한 View입니다.  

그래픽 객체를 View에 그리는 GLSurfaceView.Renderer의 인스턴스를 생성하여 현재 View와 연결해줍니다.


MyGLSurfaceView.java


package com.tistory.webnautes.openglesexample;

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

class MyGLSurfaceView extends GLSurfaceView {

   private final MyGLRenderer mRenderer;

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

       // OpenGL ES 2.0 context를 생성합니다.
       setEGLContextClientVersion(2);

       mRenderer = new MyGLRenderer();

       // GLSurfaceView에 그래픽 객체를 그리는 처리를 하는 renderer를 설정합니다.
       setRenderer(mRenderer);

       
       //Surface가 생성될때와 GLSurfaceView클래스의 requestRender 메소드가 호출될때에만
       //화면을 다시 그리게 됩니다.
       setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
   }
}




GLSurfaceView 상에 그래픽스 객체들을 그리는 처리는 GLSurfaceView.Renderer에서 이루어집니다.


MyGLRenderer.java


package com.tistory.webnautes.openglesexample;

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

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

public class MyGLRenderer implements GLSurfaceView.Renderer {

   //GLSurfaceView가 생성되었을때 한번 호출되는 메소드입니다.
     //OpenGL 환경 설정, OpenGL 그래픽 객체 초기화 등과 같은 처리를 할때 사용됩니다.
   @Override
   public void onSurfaceCreated(GL10 gl, EGLConfig config) {
       //color buffer를 클리어할 때 사용할 색을 지정합니다.
       //red, green, blue, alpha 순으로 0~1사이의 값을 지정합니다.
       //여기에서는 검은색으로 지정하고 있습니다.
       GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
   }

   //GLSurfaceView가 다시 그려질때 마다 호출되는 메소드입니다.
   public void onDrawFrame(GL10 unused) {
       //glClearColor에서 설정한 값으로 color buffer를 클리어합니다.
       //glClear메소드를 사용하여 클리어할 수 있는 버퍼는 다음 3가지 입니다.
       //Color buffer (GL_COLOR_BUFFER_BIT)
       //depth buffer (GL_DEPTH_BUFFER_BIT)
       //stencil buffer (GL_STENCIL_BUFFER_BIT)
       GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
   }
  
   //GLSurfaceView의 크기 변경 또는 디바이스 화면의 방향 전환 등으로 인해
     //GLSurfaceView의 geometry가 바뀔때 호출되는 메소드입니다.
   public void onSurfaceChanged(GL10 unused, int width, int height) {
       //viewport를 설정합니다.
       //specifies the affine transformation of x and y from
       //normalized device coordinates to window coordinates
       //viewport rectangle의 왼쪽 아래를 (0,0)으로 지정하고
       //viewport의 width와 height를 지정합니다.
       GLES20.glViewport(0, 0, width, height);
   }
}




여기까지의 실행결과는 GLSurfaceView를 생성하여 검은색으로 채워주는 것입니다.






3.  Shape를 GLSurfaceView에 그리기


이제 화면에 그래픽 객체를 그려보겠습니다. 먼저 Shapes를 정의해주어야 합니다.


안드로이드 프로그래밍은 자바를 이용하여 이루어지는데  OpenGL ES의 구현은 C로 작성되어 있습니다.

자바와 디바이스 하드웨어가 같은 순서로 바이트를 저장하지 않을 수 있기 때문에 자바에서 정의한 그래픽 객체의 vertex를 저장한 배열을 C로 구현된 OpenGL로 넘겨주기 위해서는 변환이 필요합니다.


ByteBuffer를 생성한 후 FloatBuffer로 변환하여 vertex 좌표들을 저장하게 되는데 주된 이유는 ByteBuffer를 이용시 렌더링 성능이 개선되기 때문이다.


Triangle.java


package com.tistory.webnautes.openglesexample;

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

public class Triangle {
   //float buffer 타입으로 vertexBuffer를 선언합니다.
   private FloatBuffer vertexBuffer;

   //0. float 배열에 삼각형의 vertex를 위한 좌표를 넣습니다.
   static final int COORDS_PER_VERTEX = 3;
   static float triangleCoords[] = {   //넣는 순서는 반시계 방향입니다.
           0.0f,  0.622008459f, 0.0f, // 상단 vertex
           -0.5f, -0.311004243f, 0.0f, // 왼쪽 아래 vertex
           0.5f, -0.311004243f, 0.0f  // 오른쪽 아래 vertex
   };

   //red, green, blue, alpha 값을 float 배열 color에 넣습니다.
   float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

   public Triangle() {
       //1.ByteBuffer를 할당 받습니다.
       ByteBuffer bb = ByteBuffer.allocateDirect(
               // (number of coordinate values * 4 bytes per float)
               triangleCoords.length * 4);

       //2. ByteBuffer에서 사용할 엔디안을 지정합니다.
       //버퍼의 byte order로써 디바이스 하드웨어의 native byte order를 사용
       bb.order(ByteOrder.nativeOrder());

       //3. ByteBuffer를 FloatBuffer로 변환합니다.
       vertexBuffer = bb.asFloatBuffer();

       //4. float 배열에 정의된 좌표들을 FloatBuffer에 저장합니다.
       vertexBuffer.put(triangleCoords);

       //5. 읽어올 버퍼의 위치를 0으로 설정한다. 첫번째 좌표부터 읽어오게됨
       vertexBuffer.position(0);
   }
}




OpenGL ES에선 좌표계(coordinate system)의 원점 (0,0,0)을  GLSurfaceView의 중앙으로 하는게 디폴트입니다.

3차원 좌표 (x, y, z)를 사용합니다. 좌표 (1,1,0)은 GLSurfaceView의 상단 오른쪽 코너이고 좌표(-1,-1,0)은 하단 왼쪽 코너입니다.


shape의 좌표를 정의할때 반시계방향으로 정의한다는 점에 유의해야 합니다.

예를 들어 삼각형의 경우 상단 위에 있는 점,  왼쪽 아래에 있는 점, 오른쪽 아래에 있는 점 순으로 정의합니다.




  static float triangleCoords[] = {   //넣는 순서는 반시계 방향입니다.

           0.0f, 0.622008459f, 0.0f, // 상단 vertex

           -0.5f, -0.311004243f, 0.0f, // 왼쪽 아래 vertex

           0.5f, -0.311004243f, 0.0f  // 오른쪽 아래 vertex

   };





정의한 shape는  GLSurfaceView.Renderer의 onSurfaceCreated 메소드에서 초기화되야 합니다.


MyGLRenderer.java


public class MyGLRenderer implements GLSurfaceView.Renderer {

   private Triangle mTriangle;

   //GLSurfaceView가 생성되었을때 한번 호출되는 메소드입니다.
   //OpenGL 환경 설정, OpenGL 그래픽 객체 초기화 등과 같은 처리를 할때 사용됩니다.
   @Override
   public void onSurfaceCreated(GL10 gl, EGLConfig config) {

       //shape가 정의된 Triangle 클래스의 인스턴스를 생성합니다.
       mTriangle = new Triangle();
       
       //color buffer를 클리어할 때 사용할 색을 지정합니다.
       //red, green, blue, alpha 순으로 0~1사이의 값을 지정합니다.
       //여기에서는 검은색으로 지정하고 있습니다.
       GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
   }




Shape를 화면에 그리기 위해서는 다음 3가지에 대한 코드를 작성해야 합니다.


  • Vertex Shader - shape의 vertex를 렌더링하기 위한 OpenGL ES 그래픽스 코드

  • Fragment Shader - shape의 색 또는 텍스처를 렌더링 하기 위한 OpenGL ES 그래픽스 코드

  • Program - shape를 그리기 위해 사용되는 shader를 포함하는  OpenGL ES 객체



vertex shader는 polygon의 코너점인 vertex들의 속성을 다룹니다.

fragment shader는 vertex들에 의해 생성되는 도형 내부 공간의 픽셀이 어떻게 보일지를 결정합니다.



shape를 그리기 위해서는 최소한 하나의 vertex shader가 필요합니다.  fragment shader는 shape의 표면을 채우게 되는 색 또는 텍스쳐를 정의하는 것 입니다.  

이 두가지 shader를 정의하기 위해  Triangle 클래스에 다음 코드를 추가해줍니다.


Triangle.java


public class Triangle {

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

   private final String fragmentShaderCode =
           "precision mediump float;" +
                   "uniform vec4 vColor;" +
                   "void main() {" +
                   "  gl_FragColor = vColor;" +
                   "}";
   
   //float buffer 타입으로 vertexBuffer를 선언합니다.
   private FloatBuffer vertexBuffer;




Shader는 OpenGL Shading Language (GLSL)를 사용하여 코드가 작성됩니다.  

OpenGL ES 환경에서 사용하기 위해서는 이 코드들이 먼저 컴파일 되어야 하는데 이를 위해서 rednerer 클래스인 MyGLRenderer에 loadShader함수를 추가해줍니다.


MyGLRenderer.java


public class MyGLRenderer implements GLSurfaceView.Renderer {

   private Triangle mTriangle;


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

       // 다음 2가지 타입 중 하나로 shader객체를 생성한다.
       // vertex shader type (GLES20.GL_VERTEX_SHADER)
       // 또는 fragment shader type (GLES20.GL_FRAGMENT_SHADER)
       int shader = GLES20.glCreateShader(type);

       // shader객체에 shader source code를 로드합니다.
       GLES20.glShaderSource(shader, shaderCode);

       //shader객체를 컴파일 합니다.
       GLES20.glCompileShader(shader);

       return shader;
   }




shape를 화면에 그리기 위해서는 shader 코드를  컴파일 해서 컴파일된 결과물을 OpenGL ES program 객체에 추가해주어야 합니다.

이 과정은 Triangle 클래스의 생성자 함수에서 이루어집니다. 다음 코드를 추가합니다.


Triangle.java


   private final int mProgram;


   public Triangle() {
       //1.ByteBuffer를 할당 받습니다.
       ByteBuffer bb = ByteBuffer.allocateDirect(
               // (number of coordinate values * 4 bytes per float)
               triangleCoords.length * 4);

       //2. ByteBuffer에서 사용할 엔디안을 지정합니다.
       //버퍼의 byte order로써 디바이스 하드웨어의 native byte order를 사용
       bb.order(ByteOrder.nativeOrder());

       //3. ByteBuffer를 FloatBuffer로 변환합니다.
       vertexBuffer = bb.asFloatBuffer();

       //4. float 배열에 정의된 좌표들을 FloatBuffer에 저장합니다.
       vertexBuffer.put(triangleCoords);

       //5. 읽어올 버퍼의 위치를 0으로 설정한다. 첫번째 좌표부터 읽어오게됨
       vertexBuffer.position(0);


       //vertex shader 타입의 객체를 생성하여 vertexShaderCode에 저장된 소스코드를 로드한 후,
       //   컴파일합니다.
       int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
               vertexShaderCode);

       //fragment shader 타입의 객체를 생성하여 fragmentShaderCode에 저장된 소스코드를 로드한 후,
       //  컴파일합니다.
       int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
               fragmentShaderCode);

       // Program 객체를 생성한다.
       mProgram = GLES20.glCreateProgram();

       // vertex shader를 program 객체에 추가
       GLES20.glAttachShader(mProgram, vertexShader);

       // fragment shader를 program 객체에 추가
       GLES20.glAttachShader(mProgram, fragmentShader);

       // program객체를 OpenGL에 연결한다. program에 추가된 shader들이 OpenGL에 연결된다.
       GLES20.glLinkProgram(mProgram);
   }




이제 실제로 shape를 그리기 위해 필요한 처리들을 Triangle 클래스에 draw 라는 이름으로 메소드를 추가해줍니다.


Triangle.java


   private int mPositionHandle;
   private int mColorHandle;

   private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
   private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

   public void draw() {
       //렌더링 상태(Rendering State)의 일부분으로 program을 추가한다.
       GLES20.glUseProgram(mProgram);

       // program 객체로부터 vertex shader의'vPosition 멤버에 대한 핸들을 가져옴
       mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

       //triangle vertex 속성을 활성화 시켜야 렌더링시 반영되서 그려짐
       GLES20.glEnableVertexAttribArray(mPositionHandle);

       // triangle vertex 속성을 vertexBuffer에 저장되어 있는 vertex 좌표들로 정의한다.
       GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
               GLES20.GL_FLOAT, false,
               vertexStride, vertexBuffer);


       // program 객체로부터 fragment shader의 vColor 멤버에 대한 핸들을 가져옴
       mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

       //triangle 렌더링시 사용할 색으로 color변수에 정의한 값을 사용한다.
       GLES20.glUniform4fv(mColorHandle, 1, color, 0);



       //vertex 갯수만큼 tiangle을 렌더링한다.
       GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

       //vertex 속성을 비활성화 한다.
       GLES20.glDisableVertexAttribArray(mPositionHandle);
   }




화면에 삼각형이 그려지게하려면 renderer인 MyGLRenderer 클래스의 onDrawFrame() 메소드내에서 Triangle 클래스의 draw() 메소드를 호출을 하도록 해야 합니다.


MyGLRenderer.java


   //GLSurfaceView가 다시 그려질때 마다 호출되는 메소드입니다.
   public void onDrawFrame(GL10 unused) {
       //glClearColor에서 설정한 값으로 color buffer를 클리어합니다.
       //glClear메소드를 사용하여 클리어할 수 있는 버퍼는 다음 3가지 입니다.
       //Color buffer (GL_COLOR_BUFFER_BIT)
       //depth buffer (GL_DEPTH_BUFFER_BIT)
       //stencil buffer (GL_STENCIL_BUFFER_BIT)
       GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

       mTriangle.draw();
   }




실행한 결과입니다.  

디바이스 화면의 방향에 따라 다른 모습의 삼각형이 그려지는 문제가 있습니다.








왼쪽 그림은 OpenGL에서 사용한 좌표계이고 오른쪽은 landscape orientation인 디바이스 화면에 실제로 매핑된 모습입니다.  디바이스 화면에 삼각형이 다른 모습으로 그려지는 문제를 해결하기 위해서는 projection과 camera view를 정의하여 그래픽 객체에 적용시켜 주어야 합니다.

그러면 디바이스 화면이 세로방향이든 가로방향이든 그래픽 객체를 거의 유사한 모습으로 볼 수 있게됩니다.


projection과 camera view를 적용하기 위해서는 projection matrix와 camera view matrix를 정의하여 OpenGL 렌더링 파이프라인에 적용하면 됩니다.


projection matrix는 그래픽 객체의 좌표를 재계산하여 디바이스 스크린에 올바르게 매핑되게 해줍니다.


camera view matrix는 그래픽 객체를 바라보는 시선에 따라 다르게 보이게될 그래픽 객체를 올바르게 그려주기 위해 필요한 변환을 생성합니다.


projection transformation은 그래픽 객체가 출력될 GLSurfaceView의 너비와 높이에 기초하여 그래픽 객체의 좌표를 조정합니다.  디바이스 화면의 방향이 변해도 똑같이 보이는 그래픽 객체를 볼 수 있게됩니다.

OpenGL view의 크기가 정해졌을 때  또는 renderer의 onSurfaceChanged 메소드에서 변경되었을 때  projection transformation이 계산됩니다.


Camera View transformation은 가상 카메라 위치에 기초하여 그래픽 객체의 좌표를 조정합니다.  

여기에서 중요한 점은 OpenGL ES에서는 실제 카메라 객체를 정의하지 않고 그래픽 객체가 보여질 모습에 따라 필요한 카메라 위치를 시뮬레이션하기 위한 유틸리티 메소드를 제공합니다.

camera view transformation는 GLSurfaceView가 생성될때 또는 사용자의 터치 스크린 입력 등으로 카메라 위치가 변경될 필요가 있을 때 계산됩니다.





4. projection과 camera view를 적용하기


GLSurfaceView.Renderer를 구현한 MyGLRenderer 클래스의  onSurfaceChanged 메소드에서 GLSurfaceView 너비와 높이 사이의 비율를 계산하여 projection transformation Matrix를 mProjectionMatrix 변수에 저장합니다.


MyGLRenderer.java


   // 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];

   //GLSurfaceView의 크기 변경 또는 디바이스 화면의 방향 전환 등으로 인해
   //GLSurfaceView의 geometry가 바뀔때 호출되는 메소드입니다.
   public void onSurfaceChanged(GL10 unused, int width, int height) {
       //viewport를 설정합니다.
       //specifies the affine transformation of x and y from
       //normalized device coordinates to window coordinates
       //viewport rectangle의 왼쪽 아래를 (0,0)으로 지정하고
       //viewport의 width와 height를 지정합니다.
       GLES20.glViewport(0, 0, width, height);


       //GLSurfaceView 너비와 높이 사이의 비율을 계산합니다.
       float ratio = (float) width / height;

       //3차원 공간의 점을 2차원 화면에 보여주기 위해 사용되는 projection matrix를 정의
       Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
   }




GLSurfaceView.Renderer를 구현한 MyGLRenderer 클래스의 onDrawFrame 메소드에 추가되는 코드입니다.

Matrix.setLookAtM 메소드를 사용하여 특정 카메라 위치에 대한 camera view matrix을 계산하고 Matrix.multiplyMM 메소드를 사용하여 4x4 크기의 camera view matrix와 4x4 크기의 projection matrix를 곱하여  4x4 크기인 mMVPMatrix matrix에 저장합니다.

마지막으로 계산된 matrix를  shape를 surface에 그리는 작업을 하고 있는 Triangle 클래스의 draw 메소드에 넘겨줍니다.


MyGLRenderer.java


   // 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];

   
   //GLSurfaceView가 다시 그려질때 마다 호출되는 메소드입니다.
   public void onDrawFrame(GL10 unused) {
       //glClearColor에서 설정한 값으로 color buffer를 클리어합니다.
       //glClear메소드를 사용하여 클리어할 수 있는 버퍼는 다음 3가지 입니다.
       //Color buffer (GL_COLOR_BUFFER_BIT)
       //depth buffer (GL_DEPTH_BUFFER_BIT)
       //stencil buffer (GL_STENCIL_BUFFER_BIT)
       GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

       //카메라 위치를 나타내는 Camera view matirx를 정의
       Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

       //projection matrix와 camera view matrix를 곱하여 mMVPMatrix 변수에 저장
       Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

       //triangle를 그리는 처리를 하고 있느 draw메소드에 mMVPMatrix 변수를 넘겨준다.
       mTriangle.draw(mMVPMatrix);
   }




projection과 camera view를 적용하기 위해서 자바 코드에서 계산한 matrix를  vertex shader에 넘겨주어야 합니다.

이를 위해 아래처럼 vertex shader에 uMVPMatrix 변수를 선언해 놓고 이 변수를 자바코드에서 접근하여  matrix를 넘겨주게 합니다. 이에 대해서는 다음에 설명합니다.


projection matrix와 camera view matrix를 곱하여 얻어진  mMVPMatrix 변수의 값을 vertex shader에 선언된 uMVPMatrix 변수가 넘겨받은 후,  uMVPMatrix는 shader의 position과 곱해지게 됩니다.

uMVPMatrix를 vPosition에 곱해줌으로써  이 shader를 사용하게 될 객체의 좌표에 projection matrix와 camera view matrix를 적용하게 됩니다


Triangle.java


public class Triangle {

   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;" +
                   "}";

   // Use to access and set the view transformation
   private int mMVPMatrixHandle;



projection matrix와 camera view matrix를 그래픽 객체에 적용하기


이제 Triangle 클래스의 draw 메소드를 수정하여 vertex shader에 선언된 uMVPMatrix 변수에 대한 핸들을 얻어  camera view matrix와 projection matrix를 곱한 결과를 저장하고 있는 mvpMatrix 변수의 값을 넘겨줍니다.


Triangle.java


   public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix
       //렌더링 상태(Rendering State)의 일부분으로 program을 추가한다.
       GLES20.glUseProgram(mProgram);

       // program 객체로부터 vertex shader의'vPosition 멤버에 대한 핸들을 가져옴
       mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

       //triangle vertex 속성을 활성화 시켜야 렌더링시 반영되서 그려짐
       GLES20.glEnableVertexAttribArray(mPositionHandle);

       // triangle vertex 속성을 vertexBuffer에 저장되어 있는 vertex 좌표들로 정의한다.
       GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
               GLES20.GL_FLOAT, false,
               vertexStride, vertexBuffer);


       // program 객체로부터 fragment shader의 vColor 멤버에 대한 핸들을 가져옴
       mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

       //triangle 렌더링시 사용할 색으로 color변수에 정의한 값을 사용한다.
       GLES20.glUniform4fv(mColorHandle, 1, color, 0);


       //program 객체로부터 vertex shader 타입의 객체에 정의된 uMVPMatrix에 대한 핸들을 획득합니다.
       mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

       //projection matrix와 camera view matrix를 곱하여 얻어진  mMVPMatrix 변수의 값을
       // vertex shader 객체에 선언된 uMVPMatrix 멤버에게로 넘겨줍니다.
       GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
       

       //vertex 갯수만큼 tiangle을 렌더링한다.
       GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

       //vertex 속성을 비활성화 한다.
       GLES20.glDisableVertexAttribArray(mPositionHandle);
   }




projection transformation과 camera view transformation를 그래픽 객체에 적용시켜 주면 디바이스 화면이 바뀌더라도 똑같은 모양의 삼각형이 그려지게 됩니다.






5. 터치 이벤트로 그래픽 객체 움직이기


MyGLSurfaceView 클래스에 onTouchEvent 메소드를 구현합니다.

MotionEvent.ACTION_MOVE 이벤트를 이용하여 삼각형을 회전시키기 위한 각도 계산을 합니다.  

그리고 나서 requestRender를 호출하여 renderer가 화면을 다시 그리도록 합니다.


MyGLSurfaceView.java


class MyGLSurfaceView extends GLSurfaceView {

   private final MyGLRenderer mRenderer;


   private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
   private float mPreviousX;
   private float mPreviousY;

   @Override
   public boolean onTouchEvent(MotionEvent e) {
       // MotionEvent reports input details from the touch screen
       // and other input controls. In this case, you are only
       // interested in events where the touch position changed.

       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 ;
               }

               // reverse direction of rotation to left of the mid-line
               if (x < getWidth() / 2) {
                   dy = dy * -1 ;
               }

               mRenderer.setAngle(
                       mRenderer.getAngle() +
                               ((dx + dy) * TOUCH_SCALE_FACTOR));
               requestRender();
       }

       mPreviousX = x;
       mPreviousY = y;
       return true;
   }




MyGLSurfaceView 클래스에서 화면에 그릴 데이터가 바뀔 때에만 화면을 다시 그리도록 설정해두고 있습니다.

만약 삼각형이 일정시간마다 회전하도록 했다면 계속 화면을 다시 그려야 하기 때문에  주석처리를 해주어야 합니다.




public MyGLSurfaceView(Context context) {

   ...

   //Surface가 생성될때와 GLSurfaceView클래스의 requestRender 메소드가 호출될때에만

   //화면을 다시 그리게 됩니다.

   setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

}





MyGLRenderer  클래스에 mAngle 멤버 변수를 선언하고 getter와 setter를 정의합니다.


MyGLRenderer.java


public class MyGLRenderer implements GLSurfaceView.Renderer {
   ...

   public volatile float mAngle;

   public float getAngle() {
       return mAngle;
   }

   public void setAngle(float angle) {
       mAngle = angle;
   }
}




터치 이벤트에 의해 계산된 회전 각도를 사용하여 삼각형을 회전시키기 위한 처리를 해줍니다.


MyGLRenderer.java


 //GLSurfaceView가 다시 그려질때 마다 호출되는 메소드입니다.
   public void onDrawFrame(GL10 unused) {
       //glClearColor에서 설정한 값으로 color buffer를 클리어합니다.
       //glClear메소드를 사용하여 클리어할 수 있는 버퍼는 다음 3가지 입니다.
       //Color buffer (GL_COLOR_BUFFER_BIT)
       //depth buffer (GL_DEPTH_BUFFER_BIT)
       //stencil buffer (GL_STENCIL_BUFFER_BIT)
       GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

       //카메라 위치를 나타내는 Camera view matirx를 정의
       Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

       //projection matrix와 camera view matrix를 곱하여 mMVPMatrix 변수에 저장
       Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

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

       //mAngle 각도값을 이용하여 (x,y,z)=(0,0,-1) 축 주위를 회전하는 회전 matrix를 정의합니다.
       Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);

       //projection matrix와 camera view matrix를 곱하여 얻은 matrix인 mMVPMatrix와
       //회전 matrix mRotationMatrix를 결합합니다.
       Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);

       //triangle를 그리는 처리를 하고 있는 draw메소드에 scratch 변수를 넘겨준다.
       mTriangle.draw(scratch);
   }




이제 터치에 의해서 삼각형이 회전하는 것을 볼 수 있습니다.







참고


https://developer.android.com/training/graphics/opengl/index.html

https://developer.android.com/guide/topics/graphics/opengl.html

http://stackoverflow.com/questions/10697161/why-floatbuffer-instead-of-float



최초작성  2016.07.19




반응형

'Android > OpenGL ES' 카테고리의 다른 글

Android에서 OpenGL ES 2.0 프로그래밍  (10) 2016.07.19

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. nandroid 2017.08.05 16:42

    큰도움이 됬습니다
    감사합니다!

  2. chandong83 2019.01.20 16:56

    좋은 강좌 잘 봤습니다.
    감사합니다^^

  3. Meditman 2019.07.03 10:15

    본 포스트와 glfw를 사용한 예제를 잘보았습니다.
    Glfw가 크로스 플랫폼을 지원한다고 알고있는데
    안드로이드에서 c++로 작성한 glfw를 사용할순 없나요?
    안드로이드 ndk를 이용해서 c++코드를 사용하곤 있으나 이부분은 전혀 감이안와서요

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.03 10:28 신고

      아직은 안드로이드용 glfw 패키지가 없는듯합니다.

      깃허브를 찾아보니 안드로이드에서 glfw를 사용해본 분이 있네요.

      https://github.com/xCuri0/glfw-android-example

    • Meditman 2019.07.03 11:14

      아.. 빠른 답변 감사합니다.
      glfw 사용해서 데스크탑용 앱
      모바일 앱 모두 적용하려고 했는데..

      일단 해봐야 알거같네요.

  4. R. 2019.12.10 20:20

    버츄얼 기기(API 29)로 실행시 검은 배경까진 나오는데 그 다음 초록 삼각형이 나오지 않습니다. 코드가 틀린것 같진 않은데 혹시 이유를 알 수 있을까요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.12.10 21:33 신고

      원인은 로그캣에서 찾아야 합니다. 가능하다면 안드로이드폰에서 테스트해보면 좋을듯합니다.

      간혹 최신 API에서 바뀐점이 있어서 문제가 되는 경우가 있긴합니다.

  5. R. 2019.12.16 19:51

    혹시 삼각형을 회전이 아닌 이동을 시키려 할때도 onTouchEvent를 쓰면 될까요? 검색해봐도 예제들이 다 회전을 다루고 있어서 힘드네요...

+ Recent posts