안드로이드 OpenGL 예제 - andeuloideu OpenGL yeje

android.opengl.GLSurfaceview 클래스는 아래와 같은 기능을 제공해 어플리케이션에서 OpenGL ES 렌더링을 쉽게 사용할수 있도록 해준다.

  • View 시스템에 OpenGL ES를 연결할수 있는 연결코드를 제공한다.
  • OpenGL ES 작업이 Activity 라이프 싸이클과 함께 동작하도록 해주는 연결코드를 제공한다.
  • 적절한 프레임 버퍼 픽셀 형식을 쉽게 선택할수 있도록 해준다.
  • 부드러운 에니메이션이 가능하도록 분리된 렌더링 쓰레드를 생성하고 관리할수 있게 한다.
  • OpenGL ES API 호출 추적과 에러 체크를 위한 사용하기 쉬운 디버깅 툴을 제공한다.

GLSurfaceView는 부분적으로 혹은 전체적으로 렌더링에 사용하는 어플리케이션을 작성하기 위해 좋은 기반이 된다. Google Map StreetView 와 같은 2D, 3D 데이터 비쥬얼 어플리케이션과 같은 2D, 3D 액션 게임이 좋은 후보가 될 것이다.

단순한 GLSurfaceView 어플리케이션

가장 단순한 OpenGL ES 어플리케이션의 소스코드를 보자.

package com.example.android.apis.graphics;

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

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class ClearActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(this);
mGLView.setRenderer(new ClearRenderer());
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

private GLSurfaceView mGLView;
}

class ClearRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing special.
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
}

public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
}
}

이 프로그램은 많은 일을 하지는 않는다. 모든 프레임에 검은색으로 화면을 지운다. 하지만 안드로이드 activity 라이프 싸이클을 정확하게 구현한 완벽한 OpenGL 어플리케이션이다.이 프로그램은 activity가 pause 상태일때 렌더링을 pause 하고 activity가 resume 될때 렌더링을 resume 한다. 이 프로그램을 기본적인 비상호적 데모 프로그램으로 사용할 수 있다.  ClearRenderer.onDrawFrame()메서드를 호출하기 위해 단순히 몇개의 OpenGL 호출을 추가했다. GLSurfaceview 를 상속할 필요가 없음에 주목하자.

GLSurfaceView.Render 인터페이스는 3가지 메소드를 가지는데

  • onSurfaceCreate() 메서드는 렌더링의 시작 시점과 OpenGL ES 드로잉 context가 재생성될때마다 호출된다. (보통 드로잉 context는 activity가 pause나 resume 될때 소멸되어 다시 재생성된다.) OnSurfaceCreate()는 텍스쳐(역자 주 : 이미지를 OpenGL 객체에 입히는 작업)와 같은 생명 주기가 긴 OpenGL 리소스를 생성하기에 적절한 곳이다.
  • onSurfaceChanged() 메서드는 surface가 크기를 변경할 경우 호출된다. 이것은 OpenGL viewport를 설정하기에 적절한 곳이다. 장면주위를 움직이지 않는 고정된 카메라라면 여기서 카메라 설정을 할수 있다.
  • onDrawFrame() 메서드는 모든 프레임에서 호출되고 장면을 그리는 책임을 진다. 현재 장면을 그리기 위해 다른 OpenGL ES 호출 다음에 보통 프레임 버터를 삭제하기 위해 glClear를 호출할때 시작된다. 

사용자 입력은 어떡해?

게임과 같은 상호 작용을 하는 어플리케이션을 원한다면 입력 이벤트를 받는 쉬운 방법을 제공받기 위해 GLSurfaceView를 상속해야한다. 어떻게 하는지 보여주는 조금 긴 예제를 보자.

package com.google.android.ClearTest;

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

import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.MotionEvent;

public class ClearActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new ClearGLSurfaceView(this);
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

private GLSurfaceView mGLView;
}

class ClearGLSurfaceView extends GLSurfaceView {
public ClearGLSurfaceView(Context context) {
super(context);
mRenderer = new ClearRenderer();
setRenderer(mRenderer);
}

public boolean onTouchEvent(final MotionEvent event) {
queueEvent(new Runnable(){
public void run() {
mRenderer.setColor(event.getX() / getWidth(),
event.getY() / getHeight(), 1.0f);
}});
return true;
}

ClearRenderer mRenderer;
}

class ClearRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing special.
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
}

public void onDrawFrame(GL10 gl) {
gl.glClearColor(mRed, mGreen, mBlue, 1.0f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
}

public void setColor(float r, float g, float b) {
mRed = r;
mGreen = g;
mBlue = b;
}

private float mRed;
private float mGreen;
private float mBlue;
}

이 어플리케이션은 모든 프레임을 위해 화면을 지운다. 사용자가 화면을 tap할때 사용자 touch 이벤트의 좌표값(x,y)에 기반해 색을 지운다. ClearGLSurfaceView.onTouchEvent()의 queueEvent()사용에 주목하자. queueEvent() 메서드는 UI 쓰레드와 렌더링 쓰레드간의 안전한 통신을 위해 사용된다. 원한다면 Renderer클래스 자체에서 동기화 메서드와 같은 자바 쓰레드간 통신기술을 사용할 수 있다. 하지만, 이벤트를 큐에 담는 것이 쓰레드간 통신을 다루는 것보다 더 안전한 방법이다.

다른 GLSurfaceView 샘플

단순히 화면을 지우는 것이 지루한가? 안드로이드 SDK에 포함된 API데모를 통해 더 재미있는 예제들을 볼수 있다. 모든 OpenGL ES 샘플들은 GLSurfaceView를 사용하기 위해 변경 되었다.

  • GLSurfaceView - 삼각형 회전
  • Kube - 큐브 퍼즐 데모
  • 반투명 GLSurface 뷰 - 반투명 배경에서 3D 그래픽이 어떡해 디스플레이되는지를 보여준다.
  • 텍스쳐 삼각형 : 텍스쳐된 3D 삼각형을 어떡해 그리는지 보여준다.
  • 스프라이트 텍스트 - 텍스쳐에 텍스트를 그리고 3D 장면에 어떡해 구성되는지를 보여준다.
  • 터치 회전 : 사용자 입력에 반응하여 3D객체를 어떡해 회전시키는지를 보여준다.

surface 선택

GLSurfaceView는 렌더링하고자하는 surface의 유형을 선택할수 있도록 도와준다. 서로 다른 안드로이드 디바이스 공통 부분을 가지지 않는 서로 다른 Surface 유형을 지원한다. 이것은 각 디바이스별로 가장 적절한 Surface를 선택하는데 어려움을 준다.

디폴트로 GLSurfaceView는 16비트 깊이를 가지는 버퍼와 16비트 RGB 프레임 버퍼에 가능한 가까운 surface를 찾으려고 시도한다.  개발하고자하는 어플리케이션의 니즈에 따라 이 동작을 변경하고자 할수도 있을 것이다. 예를 들어 반투명 GLSurfaceView 샘플은 반투명 데이터를 렌더링 하기 위해 알파 채널을 필요로 할 것이다. GLSurfaceView는 어떤 surface 유형을 사용할지 선택할수 있도록 하기 위해 setEGLSurfaceChooser() 메서드를 재정의할수 있도록 한다.

setEGLConfigChooser(boolean needDepth)

16비트 프레임 버퍼를 갖지 않을수 있는 R5G6B5에 가장 가까운 구성을 선택한다.

setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)

적어도 생성자에서 명시된 것만큼의 채널당 비트수를 가지는 픽셀당 비트수가 가장 적은 구성을 선택한다.

setEGLConfigChooser(EGLConfigChooser configChooser)

구성 선택에 대한 총괄적인 제어를 가능하도록 해준다. 디바이스의 성능을 조사하여 구성을 선택할수 있는 EGLConfigChooser를 직접 구현하여 전달할수 있다.

지속적인 렌더링 v.s 필요할때 렌더링

게임과 시뮬레이션과 같은 대부분의 3D 어플리케이션은 지속적으로 에니메이션된다. 그러나 몇몇 3D어플리케이션은 더 많은 반응을 하게되는데 사용자가 무언가를 수행할때까지 기다리고 그것에 대응하는 것과 같은 것이다. 이런 유형의 어플리케이션들위해 화면을 지속적으로 다시 그리는 디폴트 GLSurfaceView 동작은 시간 낭비이다. 반응하는 어플리케이션을 개발한다면 지속적인 에니메이션 하지는 GLSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY) 를 호출 할 수 있고 다시 렌더링을 원할때는 GLSurfaceView.requestRender()를 호출 할 수 있다.

디버깅의 도움

GLSurfaceView는 OpenGL ES 어플리케이션 디버깅을 위해 유용한 내장 기능을 가지고 있다.GLSurface.setDebugFlags() 메서드는 OpenGL ES호출시 오류를 확인 하거나 로깅을 가능하게 하는데 사용될 수 있다. setRenderer()를 호출하기 전에  GLSurfaceView의 생성자에서 이메소드를 호출한다.

public ClearGLSurfaceView(Context context) {
super(context);
// 오류 확인과 로깅을 On
 setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
mRenderer = new ClearRenderer();
setRenderer(mRenderer);
}