예제 소스
GLES20Activity.java
GLES20TriangleRenderer.java
GLES20Activity.java
ES 2.0 을 사용하기 위해서 다음과 같이 버전 설정을 해야하고, 사용할 renderer 을 설정 해야 한다.
mGLSurfaceView.setEGLContextClientVersion(2) ;
mGLSurfaceView.setRenderer(new GLES20TriangleRenderer(this));
여기 소스에는 나오지 않지만 다음과 같이 render mode 를 설정할 수 있다.
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINOUSLY);
또는
mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
초기 설정 값은 RENDERMODE_CONTINOUSLY 라서 소스의 render mode 는 이것이다. 이 모드는 매번 반복해서 그려주는 것으로 모션이 들어갈때에는 이 걸로 설정되어 있어야 한다. RENDERMODE_WHEN_DIRTY 는 surface 가 만들어질 때, requestRender() 메서드가 호출될 때만 그려지는 모드 이다. 자세한 내용은 GLSurfaceView.setRenderMode() 를 참고하기 바란다.
GLES20TriangleRenderer.java
OpenGL ES 2.0(이하 ES 2.0) 이 하는 일을 아주 간단하게 말하면,
점 찍고 그 점을 기준으로 색을 채운다?
라고 아주 어설프게 생각하고 있다.
소스를 보면 mProgram 이라는 변수가 나온다. 이 녀석은 ES 2.0 의 program 을 java 에서 다룰 수 있게 변환된 변수 이다. ES 2.0 에서 사용되는 주요 요소들은 int 형으로 변환되어 java 에서 사용된다(소스에서 나오는 mTextureID, muMVPMatrixHandle, maPositionHandle, maTextureHandle). 점 찍고 그 점을 기준으로 색을 채우기 까지를 하나로 볼 수 있는데, 이 역할을 하는 것이 program 이라고 볼 수 있다. 점을 어떤식으로 찍고, 그에 따라 어떤식으로 색을 채우는 방법에 따라, 여러 program 들이 만들어 진다. ES 2.0 을 통한 구현은 필요한 program 을 만들어서, 이를 활용하는 것이라고 보면 될 것 같다. 소스를 보면 3개의 점을 찍고 로봇이미지(이미지는 색에 대한 정보라서 간단하게 색으로 생각함)를 채워 삼각형을 그리는 mProgram 을 만들고, 이것을 활용해서 3개의 점을 위치를 변화시켜서 삼각형이 돌고 있는 것을 구현하였다. 참고로 여기에 사각형을 그리는 program, 원을 그리는 program, 기타 등의 program 들을 추가해서 같이 사용할 수 있다.
program 은 shader 라는 것을 다룬다. 그것도 2 가지, vertex shader 와 fragment shader 이다. vertex shader 는 점, fragment shader 는 색을 다룬다고 보면 되겠다. 정확히 따지면 점과 색으로 단정짓기에는 무리가 따를 수도 있다. 개인적인 어설픈 생각과 짧은 경험으로 이렇게 표현하는 것이 이해하기 쉬울것 같아서 그런것이니, 이 부분에 대해 인지하고 있기를 바란다.
shader 는 GPU 에 바로 접근한다고 볼 수 있다. 소스에서 mVertexShader 와 mFragmentShader 를 보면 String 으로 shader 언어가 작성되어 있다. 실제로 이 언어가 GPU 에서 작동한다고 볼 수 있다. java 프로그램에서 보면 직접 코딩한 java 파일은 Java VM 에서 실행 시키기 위해서 class 파일로 컴파일되어야 한다. 이 처럼 String 으로 되어 있는 shader 언어(mVertexShader, mFragmentShader)도 실제로 사용되기위해 컴파일 과정을 거쳐야한다.
소스에서 loadShader(int shaderType, String source) 메서드를 보자. 이 메서드를 보면 어떻게 컴파일하는지 알 수 있다. VertexShader 와 FragmentShader 둘 다 똑같은 과정을 거치기 때문에 메서드로 만든 것 같다. 앞에서 이야기했듯이 java 에서 사용되기 위해 int 형으로 반환 되고 있다. shader 는 program 을 통해서 다루어지기 때문에, 컴파일된 shader 를 사용할 program 에 연결 시켜야 한다. createProgram(String vertexSource, String fragmentSource) 를 보면 어떻게 되는지 알 수 있다. program 은 한번만 만들고 화면에 그릴때 계속해서 사용한다. 그래서 onSurfaceCreated(GL10 glUnused, EGLConfig config) 에서 mProgram 이 실제로 만들어 지는걸 볼 수 있다.
onDrawFrame(GL10 glUnused) 메서드를 보자. 이 메서드는 GLThread 에 의해서 매번 실행되는 메서드이다(render mode 가 RENDERMODE_WHEN_DIRTY 일땐 예외). 성능에 따라 다르지만 초당 60 번 정도(?) 실행 된다. 메서드 안의 코드에서 GLES20.glUseProgram(mProgram); ~ GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3); 를 program 을 사용해서 화면에 그려주기 위한 묶음으로 보면 된다. 위에서 여러가지 program 들(사각형, 원 등을 그리는 program)을 사용할 수 있다고 했다. 여기서 주의할 점이 있다. 묶음 중간에 program 이 바뀌면, 마지막에 적용된 program 이 그려진다. shader 언어에 사용되는 변수들이 같으면 에러가 발생하지 않는데, 다를 경우 에러가 발생한다. 에러가 발생하지 않더라도, 원하지 않던 모습이 그려지는 것을 볼 수 있을 것이다. 여러 program 들을 사용할 때 하나의 그리기 묶음이 끝나면 다음 그리기 묶음이 시작하도록 주의해서 구현하도록 해야 한다.
묶음 중간의 코드들은 점과 색에 대한 정보를 program 에 넣어주는 거라고 간단하게 생각할 수 있다. 일종의 메서드와 비슷하다고 볼 수 있다. 메서드에 매개변수를 넣어 주면 매개변수에 따라 메서드가 실행된다. 이와 마찬가지로 program 에 점과 색에 대한 정보를 넣어 주면 그에 따라 화면이 그려진다고 보면 된다.
mVertexShader 와 mFragmentShader 가 선언된 부분을 보자. shader 언어를 보면 uniform, attribute, varying 을 볼 수 있다. 간단하게 보면 uniform 과 attribute 는 java 코드와 shader 의 연결이고, varying 은 vertex shader 와 fragment shader 의 연결이다. 세세하게 따지면 이는 잘못된 설명일 수 있다. 자세한 내용은 ES 2.0 Reference card 에서 Shading Language 1.0 의 Qualifiers 부분과 ES 2.0 Pipeline 을 참조하기 바란다.
mVertexShader 의 shader 언어에서 gl_Position = uMVPMatrix * aPosition; 을 보자. 직관적으로 uMVPMatrix 는 matrix 이고, aPosition 은 점이라고 느낄 수 있다(아닌가? 나만 그런가? 뭐... 그렇다고 보고). 수학에서 matrix 곱하기 점은 matrix 에 따른 점의 이동이다. 결국 이 식의 결과 값은 점인 셈이다. gl_Position 에 점이 대입되는데, vertex shader 는 gl_Position 의 정보에 따라 점을 찍어 준다고 보면 될 듯 하다. (ES 2.0 Reference card 에서 Shading Language 1.0 의 Built-In Inputs, Outputs, and Constants 부분을 참조하기 바란다.)
aPosition 이 선언 되는 부분 앞에 attribute 를 볼 수 있다. java 코드에서 관련된 값을 전달 받는다는 걸 예상할 수 있다. mTriangleVerticesData, mTriangleVertices, maPositionHandle 이 변수들이 전달에 사용되는 변수들이다. 실제 삼각형의 좌표 데이터는 mTriangleVerticesData 이다. float[] 배열로 되어있는데, 이 값들을 ES 2.0 으로 전달하기 위해선 FloatBuffer 에 넣고 aPosition 과 연결 시켜야 한다. 생성자 GLES20TriangleRenderer(Context context) 안을 보면 어떻게 FloatBuffer 에 데이터들을 넣는지 알 수 있다. 다음을 보자.
onSurfaceCreated() 안의
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
onDrawFrame() 안의 mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
GLES20.glEnableVertexAttribArray(maPositionHandle);
실제로 aPosition 에 데이터를 전달하기 위한 방법이다. 여기서 TRIANGLE_VERTICES_DATA_POS_OFFSET(0), 3, TRIANGLE_VERTICES_DATA_STRIDE_BYTES(5 * FLOAT_SIZE_BYTES(4)) 의 의미를 이해 해야한다. mTriangleVerticesData 를 보자.
private final float[] mTriangleVerticesData = {
// X, Y, Z, U, V
-1.0f, -0.5f, 0, -0.5f, 0.0f,
1.0f, -0.5f, 0, 1.5f, -0.0f,
0.0f, 1.11803399f, 0, 0.5f, 1.61803399f
}
주석을 보고 잘 파악하면, (-1.0f, -0.5f, 0), (1.0f, -0.5f, 0), (0.0f, 1.11803399f, 0) 이 세 점들이 삼각형의 좌표가 된다는걸 알 수 있을 것이다(u, v 는 아래에서 texture 다룰때). 소스에서 알기 쉽게 쓰여져 있지만, 실제로는 {-1.0f,-0.5f,0,-0.5f,0.0f,1.0f,-0.5f,0,1.5f,-0.0f,0.0f,1.11803399f,0,0.5f,1.61803399f} 이렇게 순서대로 있는 것이고, mTriangleVertices(FloatBuffer) 에도 이렇게 들어 간다. TRIANGLE_VERTICES_DATA_STRIDE_BYTES 의 값을 보면 5 x 4 가 된다. 여기서 4 는 float 형이 4 byte 를 차지하는것을 의미한다. 위에서 TRIANGLE_VERTICES_DATA_POS_OFFSET, TRIANGLE_VERTICES_DATA_STRIDE_BYTES 의 실제 값을 넣어서 보면 다음과 같다.
mTriangleVertices.position(0);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
5 * 4, mTriangleVertices);
여기서 눈여겨 봐야할 숫자들은 0, 3, 5 이다. 먼저 0, 5 부터 보면 제일 처음에 mTriangleVertices 안의 위치를 0에 위치 시킨다. ES 2.0 에 mTriangleVertices 의 값들을 넘겨 줄때 처음 위치 0 부터해서, 다음은 5, 그 다음은 10, 이렇게 간격이 5 가 된다. 그리고 각 간격의 처음 위치에서 3 개가 maPositionHandle 에 적용된다. 적용되는 값들을 보면 삼각형의 세 점이란 것을 알 수 있을 것이다. 뭐 대충 이런식으로 된다. ES 2.0 Reference 에서 glVertexAttribPointer 부분을 참고하기 바란다.
mVertexShader 가 선언된 곳의 shader 언어 안에서 aPosition 이 선언 되는 부분을 보자.
attribute vec4 aPosition;
여기서 vec4 에서 의문점이 생긴다. shader 언어를 접하다 보면 vec4(1.0, 1.0, 1.0, 1.0) 이런 것을 볼 수 있을 것이다. 즉 vec4 는 실제로 4개의 값을 가진다. 그런데 위에서 삼각형의 점 x, y, z 값만 전달한다. 나머지 하나가 부족한데, 이 값은 w 에 해당한다. ES 2.0 Reference card 에서 Shading Language 1.0 의 Operators and Expressions 안의 Vector Components [5.5] 부분을 보면 {x, y, z, w} 를 볼 수 있다. w 에 대해서는 Homogeneous coordinates 를 참조하기 바란다.
mVertexShader 가 선언된 곳의 shader 언어 안에 uMVPMatrix 가 보일 것이다. aPosition 즉 mTriangleVerticesData 는 고정되어 있다. 그런데 예제 소스는 삼각형을 회전 시키고 있다. 삼각형의 세 점들이 매번 위치를 옮기고 있는 것이다. 이걸 가능하게 하는 것이 uMVPMatrix 이다. 그리고 폰 마다 해상도가 달라서 실제로 화면에 보이는 삼각형의 모양이 다를 수가 있다. 삼각형의 모양이 어느 폰에서나 같게 유지하기 위해서 필요한 것도 uMVPMatrix 이다. matrix 가 선언된 부분을 보자. mMVPMatrix, mProjMatrix, mMMatrix, mVMatrix 이상한 matrix 들이 4개나 나온다. 이 matrix 들은 OpenGL 의 3D 좌표 개념을 나타낸다. uMVPMatrix 의 실제 값은 mMVPMatrix 라고 보면 된다. int 형 변수 중에 muMVPMatrixHandle 을 통해서 값이 전달된다. 다음을 보자.
onSurfaceCreated() 에서
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
onDrawFrame() 에서
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
uMVPMatrix 에 mMVPMatrix 를 전달하는 방법이다.
mMMatrix 는 Model Matrix 로 객체(여기서는 삼각형)의 좌표를 실제 공간의 좌표로 변환시키는 matrix 이다. mVMatrix 는 View Matrix 로 카메라의 좌표로 변환시키는 matrix 이다. 일반적으로 3D 관련해서 우리가 보는 3D화면은 카메라로 본다는 개념을 사용한다. 그냥 우리의 눈의 위치라고 생각해도 될것 같다. mProjMatrix 는 Projection Matrix 로 위에서 w 와 관련된 Homogeneous coordinates 로 변환시키는 matrix 이다. 실제로 우리가 보는 화면이라고 보면 될 것이다. 다음을 보자.
onSurfaceCreated() 에서
Matrix.setLookAtM(mVMatrix, 0, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
mVMatrix 가 어떻게 만들어 지는가 알 수 있다(Matrix.setLookAtM() 참조).
onSurfaceChanged() 를 보자.
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
mProjMatrix 가 어떻게 만들어 지는가 알 수 있다(Matrix.frustumM() 참조). 여기서 매개변수로 들어오는 width 와 height 는 폰의 디스플레이 영역이다. GLES20.glViewport() (ES 2.0 Reference 의 glViewport 참조)는 viewport 를 설정하는 것이다. viewport 는 실제로 보이는 영역, 실제로 그려지는 영역이라고 보면 되겠다. viewport 영역을 벗어나면 실제로 그려지지 않는다. 그리고 3D 좌표에 따라 좌표 범위가 달라지는데, 이와 관련된게 frustum 이다. 실제로 그려지는 3D 좌표 범위를 나타내는 피라미드 절단체 모양인데... Viewing frustum , OpenGL Transformation 에서 Projection Matrix (GL_PROJECTION) , OpenGL Projection Matrix 에서 Perspective Projection, GL Programming Chapter03 의 Projection Transformations 를 참고하기 바란다. ratio 가 적용되는 부분을 잘 보기 바란다. -ratio, ratio 그 뒤에 -1, 1 이들은 각각 frustum 의 left, right, bottom, top 에 해당한다. 디스플레이 영역의 높이를 1 로 두고 그에 따른 너비 비율을 적용했다는 것을 잘 인지하기 바란다.
onDrawFrame() 안에 있는 다음 코드들을 보자.
long time = SystemClock.uptimeMillis() % 4000L;
float angle = 0.090f * ((int)time);
Matrix.setRotateM(mMMatrix, 0, angle, 0, 0, 1.0f);
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
Matrix.setRotateM() 메서드와 Matrix.multiplyMM() 메서드가 뭔지 알면 mMVPMatrix 가 어떻게 구해지는 알 수 있다. 삼각형을 회전시키려고 하니깐, mMMatrix(Model Matrix) 에 angle 값에 따른 회전을 적용 시켰다. 해당 matrix 의 좌표적용은 matrix 의 곱으로 보면 될 것이다. matrix 의 특성 상 곱하는 순서에 따라 값이 달라지기 때문에 순서를 주의해야 할 것이다. 코드를 식으로 보면 mMVPMatrix = mProjMatrix * mVMatrix * mMMatrix 일 것이다. Tutorial 3 : Matrices 의 The Model, View and Projection matrices 를 참고하기 바란다.
texture 쓰는중...
참조 링크
Tutorials OpenGL ES 2.0
Learn OpenGL ES
Songho OpenGL
OpenGL tutorial
김형준 GIS 연구소
ttttttt
댓글 없음:
댓글 쓰기