[컴][안드로이드] GLSurfaceView 사용하는 방법



openGL ES 를 사용해 보자. 안드로이드에서 API 를 제공한다. GLSurfaceView 라는 모양으로 제공한다.


GLSurfaceView

간단한 GLSurfaceView 를 이용한 application 을 만들어보자. 예제는 ref. 1을 참고했다.


GLSurfaceView 사용

public class ClearActivity extends Activity {

    
    private GLSurfaceView mGLView;
    

    @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();
        
    }

    
}


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);
    }
}
  • onSurfaceCreated() :
    rendering(렌더링) 을 시작할 때와 OpenGL ES drawing 컨텍스트가 다시 만들어져야 할 때 호출된다.
  • onSurfaceChanged() :
    surface 의 size 가 바뀔 때 호출된다. 여기서 OpneGL viewport 를 설정하면 된다.
    scene 주위를 움직이지 않는 fixed camera 라면, camera 도 여기서 set 하면 된다.
  • onDrawFrame() :
    모든 frame 마다 이 함수가 호출된다. 이 함수에서 scene 을 그린다.
    일반적으로 시작할 때 framebuffer 를 clear 하기 위해 glClear 를 호출한다. 그리고 그 뒤로 현재 scene 을 그리기 위한 다른 OpenGl ES 호출들이 따라온다.


User Input 을 위한 GLSurfaceView 상속

User Input 을 받기 위해서는 GLSurfaceView 를 상속해야 한다. 소스를 보면 어떻게 해야 할 지 알것이다. 실제 draw 부분은 renderer 에서 작업해 주고, GLSurfaceView 에서는 event 를 날려주기만 하면 된다.

queueEvent
event 를 renderer thread 에게 던져주는 함수이다. renderer thread 에게 event 를 주는 것이기 때문에 renderer 가 set 되어 있어야 한다.

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

    public boolean onTouchEvent(final MotionEvent event) {
        queueEvent(new Runnable(){
            public void run() {
            
                mRenderer.setColor(event.getX() / getWidth(),
                                event.getY() / getHeight(),
                                1.0f);
            
                
                requestRender();
                
            }});
            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;
}

Activity

GLSurfaceView 에서 Renderer 를 set 해주기 때문에, Activity 에서는 GLSurfaceView 만 set 해주면 된다.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    mGLView = new ClearGLSurfaceView(this);
    
    setContentView(mGLView);
}

Fragment

Fragment 에서는 아래처럼 해주면 된다.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View fragment = inflater.inflate(R.layout.main_fragment, container, false);
    mGLView = new ClearGLSurfaceView(fragment.getContext());
    return mGLView;

}


GLThread in GLSurfaceView.java

  1. GLSurfaceView 는 Window 에 attach 될 때 GLThread 를 만들게 된다.
  2. 이 GLThread 가 renderer thread 인데 이 thread 가 guardedRun() 을 호출 한다.
  3. 이 gurdedRun() 이 무한 루프를 돌면서 mEventQueue 에 있는 event 들을 run 시킨다.
@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    ...
    if (mDetached && (mRenderer != null)) {
       ...
        mGLThread = new GLThread(mThisWeakRef);
        if (renderMode != RENDERMODE_CONTINUOUSLY) {
            mGLThread.setRenderMode(renderMode);
        }
        mGLThread.start();
    }
    mDetached = false;
}

////////////////////////////
// inner static class
////////////////////////////
static class GLThread extends Thread {
    ...

    @Override
    public void run() {
        setName("GLThread " + getId());
        ...
        try {
            guardedRun();
        } catch (InterruptedException e) {
            // fall thru and exit normally
        } finally {
            sGLThreadManager.threadExiting(this);
        }
    }
}
@Override
protected void onDetachedFromWindow() {
    ...
    if (mGLThread != null) {
        mGLThread.requestExitAndWait();
    }
    mDetached = true;
    super.onDetachedFromWindow();
}

private ArrayList mEventQueue = new ArrayList();

public void queueEvent(Runnable r) {
    if (r == null) {
        throw new IllegalArgumentException("r must not be null");
    }
    synchronized(sGLThreadManager) {
        mEventQueue.add(r);
        sGLThreadManager.notifyAll();
    }
}


private void guardedRun() throws InterruptedException {
    while (true) {
        synchronized (sGLThreadManager) {
            while (true) {
                if (mShouldExit) {
                    return;
                }

                if (! mEventQueue.isEmpty()) {
                    event = mEventQueue.remove(0);
                    break;
                }
                ...
            }
        } // end of synchronized(sGLThreadManager)

        if (event != null) {
            event.run();
            event = null;
            continue;
        }

    }
}



RENDERMODE_WHEN_DIRTY

3D 게임같은 프로그램에서는 계속해서 화면을 모두 다 그려야 줄 필요가 있지만, 일반적인 어플리케이션에서는 굳이 그렇게 할 필요가 없다. 그저 변경된 부분만 다시 그려주면 된다. 이것은 아래처럼 render mode 를 WHEN_DIRTY 로 설정 해 주면 된다.
  • GLSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY),

그리고 나서, 필요한 곳에서 rendering 을 요청하면 된다. rendering 의 요청은 아래로 가능하다.
  • GLSurfaceView.requestRender() 

glFrontFace(GL10.GL_CW)

GL_CW 는 clockwise, GL_CCW 는 count-clockwise 이다. 이 방향이 앞면 폴리곤(front-facing polygon) 의 방향이 된다. (Specifies the orientation of front-facing polygons)

아래같이 vertex 가 4개 있다고 하자. 이때 vertex 의 순서를 glDrawElements() 로 넘겨줘서 polygon 을 그리게 한다.(IndexBuffer 를 만드는 과정은 일단 생략한다.)

  • byte indices[] = { 1, 2, 3,    2, 4, 3 }
  • gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);

이 때
glFrontFace(GL10.GL_CW)
라면, 오른쪽 방향의 순서대로 vertex 들의 순서를 정하면, 그 순서로 만들어진 polygon 이 정면이 된다. 만약 순서를 vertex 순서를 시계반대방향(왼쪽)으로 순서를 정하면 그 polygon 은 뒷면이  된다.

예를 들어 만약 indices 를 아래처럼 set 하면, 아마 반쪽짜리 삼각형 polygon 을 볼 것이다. 그 이유는 1 -> 2 -> 3 은 정상적으로 삼각형을 보여줄 테지만, 3 -> 4 -> 2 는 한바퀴 돌려야 보인다.
  • byte indices[] = { 1, 2, 3,    3, 4, 2 }
테스트는 Other example 의 Touch Rotate 로 해보면 이해가 갈 것이다.




Other examples

ApiDemo 에 보면 예제들이 더 있다.
  • <Android_sdk>\samples\android-19\legacy\ApiDemos\src\com\example\android\apis\graphics\
    • GLSurfaceView - a spinning triangle
    • Kube - a cube puzzle demo
    • Translucent GLSurfaceView - shows how to display 3D graphics on a translucent background
    • Textured Triangle - shows how to draw a textured 3D triangle
    • Sprite Text - shows how to draw text into a texture and then composite it into a 3D scene
    • Touch Rotate - shows how to rotate a 3D object in response to user input.



GLSurfaceView 에 대한 좀 더 많은 정보는 ref. 1 을 참고하자.



See Also

  1. How to capture the app's screen
    How to programatically take a screenshot on Android?, StackOverflow



Reference

  1. Introducing GLSurfaceView | Android Developers Blog
  2. http://openaphid.github.io/blog/2012/05/21/how-to-implement-flipboard-animation-on-android/

댓글 없음:

댓글 쓰기