IT_Programming/OpenGL

Android OpenGL

JJun ™ 2012. 8. 13. 21:10



 

 출처

 : http://blog.naver.com/gidools/20107806808

 : http://blog.naver.com/gidools/20107815689

 : http://blog.naver.com/gidools/20107823505

 : http://blog.naver.com/gidools/20107834525

 : http://blog.naver.com/gidools/20108005068

 : http://blog.naver.com/gidools/20108211751

 : http://blog.naver.com/gidools/20108224995

 : http://blog.naver.com/gidools/20115103197

 : http://blog.naver.com/gidools/20115104023

 : http://blog.naver.com/gidools/20115106539

 : http://blog.naver.com/gidools/20115107550

 : http://blog.naver.com/gidools/20115269317

 : http://blog.naver.com/gidools/20115270879

 : http://blog.naver.com/gidools/20116707834

 : http://blog.naver.com/gidools/20131734261

 : http://blog.naver.com/gidools/20134702411






Android Open GL Lesson #1

 

안드로이드 Open GL 기본 지식

1.     SurfaceView : 안드로이드에서 Surface 라는 것은 GUI 가 그려지는 화면이다그냥 이렇게 간단하게 이해하면 된다.
View 
를 상속 받는 클래스인데 Renderer 라는 인터페이스를 등록해서 3D 작업이 별도의 쓰레드로 돌아가도록 만들어져 있다
왜 별도로 돌아가도록 하냐면 메인 UI 에서 3D 작업을 같이 처리하려면 시간이 많이 걸려서 키 입력 지연 같은 현상이 발생하기 때문이다.

2.     Renderer : Surface create, change, draw 와 같은 3D 에 핵심적인 작업들을 수행한다.

3.     GL10 : 현재 안드로이드에서 지원하는 것은 Open GL ES 1.0 이기 때문에 GL10 이라고 하는 것 같다
안드로이드 문서에 보면 Open GL ES 1.0  Open GL 1.3 에 대응(correspond) 한다고 돼 있는 데 무슨 차이점이 있는 지는 모르겠다.


SurfaceView 
클래스는 Main UI (우리가 만든 Application) 에서 쓰는 것이고 GL10 관련 메소드들은 Display 를 위한 하드웨어에
필요한 설정을 하는 것이다이 두 가지가 연결돼서 3D 기능을 구현할 수 있는 것이다.

 

일단 프로젝트 시작은 아래와 같이 하면 된다이건 공식 같은 거라서 그냥 외우고 시작하는 것이 정신 건강에 좋다
다만CustomRenderer 클래스 안에 있는 코드들은 다르게 작성할 수도 있다.

 

public class AndroidOpenGL extends Activity {

           private CustomSurfaceView mSurfaceView;

          

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mSurfaceView = new CustomSurfaceView(this);

        setContentView(mSurfaceView);

    }

 

@Override

protected void onPause() {

           mSurfaceView.onPause();

           super.onPause();

}

 

@Override

protected void onResume() {

           mSurfaceView.onResume();

           super.onResume();

}

}

 

public class CustomSurfaceView extends GLSurfaceView {

           private CustomRenderer mRenderer;

 

           public CustomSurfaceView(Context context) {

                     super(context);

                     mRenderer = new CustomRenderer(this);

                     setRenderer(mRenderer);

           }

          

}

 

 

public class CustomRenderer implements Renderer {

          

           public CustomRenderer(CustomSurfaceView view) {

           }

 

           @Override

           public void onDrawFrame(GL10 gl) {

                     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

                     gl.glLoadIdentity();

                     gl.glTranslatef(0.0f, 0.0f, -6.0f);

           }

 

           @Override

           public void onSurfaceChanged(GL10 gl, int width, int height) {

                     gl.glViewport(0, 0, width, height);            

                     gl.glMatrixMode(GL10.GL_PROJECTION);

                     gl.glLoadIdentity();                   

                     GLU.gluPerspective(gl, 45.0f, (float) width / height, 1.0f, 30.0f);                    

                     gl.glMatrixMode(GL10.GL_MODELVIEW);

                     gl.glLoadIdentity();

           }

 

           @Override

           public void onSurfaceCreated(GL10 gl, EGLConfig config) {

                     gl.glShadeModel(GL10.GL_SMOOTH);

                     gl.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);

                     gl.glClearDepthf(1.0f);

                     gl.glEnable(GL10.GL_DEPTH_TEST);

                     gl.glDepthFunc(GL10.GL_LEQUAL);

                     gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

           }

 

}

 

onSurfaceChanged() : 화면 사이즈가 변경되는 등 어떤 변화가 생길 때 실행된다
화면 변화가 없더라도 클래스가 처음 생성될 때도 실행되기 때문에 필요한 몇 가지 gl 메소드들이 들어가 있다.

 

gl.glViewport(0, 0, width, height); 휴대폰 화면에 보이는 영역을 정한다.
width, height 
값을 찍이 보진 않았는데 전체 화면으로 나오는 걸로 봐서 Title 영역 제외한 전체 영역이 기본 설정되는 것 같다.    

 

gl.glMatrixMode(GL10.GL_PROJECTION); 3D 좌표를 2D 스크린 좌표로 변환하는 방식.
3D 영상이든 물체든 어쨋든 컴퓨터 모니터나 휴대폰 스크린에 보이기 위해서는 2D 평면에 그려야 한다.
이것을 투영(Projection) 이라고 하는데 회전이나 이동을 어떻게 할지 계산할 때 쓰는 행렬이 있다.
직교 투영은 물체의 거리에 상관 없이 크기가 변하지 않는 것이고
원근 투영은 멀리 있는 것은 작게 보이고 가까이 있는 것은 크게 보이도록 투영하는 방식이다.

 

gl.glLoadIdentity(); 변환 행렬을 초기화하는 것이다. 변환 행렬은 보통 4*4 배열인데 회전이나 이동을 한 다음
이 메소드를 실행하면 변환 행렬이 초기화 된다. 즉 행렬안에 요소 중에 대각선 부분에 있는 값들만 1 이고 나머지는 모두 0 으로 바뀐다.
바로 위에서 gl.glMatrixMode(GL10.GL_PROJECTION) 다음에 이 메소드를 실행했기 때문에 투영행렬을 초기화 한다는 뜻이다.

 

GLU.gluPerspective(gl, 45.0f, (float) width / height, 1.0f, 30.0f); 원근 투영 방식을 쓰겠다는 뜻이다.
그리고 Window 
 Aspect ratio 를 계산해서 Opengl 쪽에 알려 준다. 화면의 가로/세로 비율과 세로 시야각화면 깊이 등을 설정하는 메소드이다.
45.0f 
축 방향으로 보이는 각을 의미하는 데 사람 눈이 상하 45 도 정도만 볼 수 있어서 45.0 으로 설정한 것인 지.. 
마지막 매개 변수는 각각 가까운 z 좌표 z 좌표 값인데 1.0, 30.0 이라는 값은 화면에 보이는 가장 가까운 쪽은 1 만큼 떨어져 있고
가장 먼쪽은 30 만큼 떨어져 있다는 뜻이다. 인터넷에서 Opengl 로 검색하면 절두체라는 그림과 함께 자세한 설명이 있다.

 

gl.glMatrixMode(GL10.GL_MODELVIEW); Model View 라는 것은 3D 공간 상에 위치한 모델(객체? 물체?)가 회전이나 이동하는 것을 말한다.
이 메소드를 호출하면 이런 물체들의 이동과 회전을 계산할 때 쓰는 행렬을 로딩했다는 뜻이다.

 

gl.glLoadIdentity();  투영행렬을 쓰겠다고 gl.glMatrixMode(GL10.GL_PROJECTION) 실행하고 행렬 초기화를 했듯이 모델의 변환 행렬을 쓰겠다고 gl.glMatrixMode(GL10.GL_MODELVIEW) 를 호출하면 초기화를 해 줘야 하기 때문에 이 메소드를 실행해준다.

 

onSurfaceCreated():OpenGL 에 대한 모든 초기화 과정을 수행하는 메소드어떤 컬러 값으로 화면 배경을 칠할 것인지,
depth buffer 
를 사용할 것인지, smooth shading 을 사용할 것인지 등등..

 

gl.glShadeModel(GL10.GL_SMOOTH); 다면체를 그릴 때 컬러값들이 잘 혼합되서 보일 수 있도록 설정
삼각형 꼭지점에 각각 색을 지정하고 그리면 중간 지점에 컬러 값들이 이쁘게 섞이는 것을 볼 수 있다.

다른 강의에서 더 자세한 설명할 것이다.

 

gl.glClearColor(0.5f, 0.5f, 0.5f, 0.5f); 화면 배경색 설정각각 Red, Green, Blue, 투명도를 의미한다
값의 범위는 0 ~ 1.0f. 0에 가까울수록 어두운 색이고 1 에 가까울수록 밝은 색이 된다.

 

gl.glClearDepthf(1.0f); Depth Buffer 설정모든 픽셀은 깊이(z) 값을 갖는다
이것을 저장하기 위한 버퍼가Depth buffer 이고 이것을 clear 한다는 의미다.

 

gl.glEnable(GL10.GL_DEPTH_TEST); Depth 값 비교 가능하도록 설정
이게 Enable 상태가 돼야 아래 코드가 동작한다.

 

gl.glDepthFunc(GL10.GL_LEQUAL); 다면체를 그릴 때 깊이가 작은 것만 그린다는 의미로 시야에서 먼 쪽에 있어서
보이지 않는 면은 그리지 않도록 하는 것이다.

 

gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); we want the best perspective correction to be done. This causes a very tiny performance hit, but makes the perspective view look a bit better. 무슨 뜻인지

 

 

public void onDrawFrame(GL10 gl) : 드디어 화면에 실제로 그리는 부분이다.

 

gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); 화면을 깨끗이 한다는 의미.
Depth buffer 
 3D 이기 때문에 필요한 것이다평면이 아니기 때문에 화면의 픽셀이 어떤 깊이 값을 갖고 있는 지 저장하는 버퍼가 있는데
이것을 초기화 하는 것이다.

 

gl.glLoadIdentity(); 현재의 ModelView matrix 를 초기화 하는 과정일단 좌표계를 초기화 한다고 생각하면 될 것 같다.

 

gl.glTranslatef(0.0f, 0.0f, -6.0f); 좌표계를 이동시킨다. x, y 좌표는 그대로 두고 z 축만 이동시킨다는 의미다
다음 강의에서 삼각형을 그려 볼 것인데 이 때 z 축을 0.0f 로 해 놓고 실행하면 아무것도 안 보일 것이다
왜냐면 z 좌표가 0 이라는 것은 눈의 위치와 똑같다는 의미다

우리는 눈의 앞에 있는 것은 볼 수 있지만 눈과 같은 위치에 있거나 뒤 쪽에 있는 것은 볼 수 없는 것과 같은 이치
휴대폰 화면에서 좌표는 가로 축, y 는 세로 축, z 는 화면의 깊이이기 때문에 z 좌표에 – 값을 줘야지 비로소 화면에 그려지는 도형을
눈으로 볼 수 있는 것이다.

 

여기까지 코드 작성하고 실행하면 전체 화면이 회색 비슷한 색으로 칠해 질 것이다.

 

오늘은 여기까지..

 







Android OpenGL lesson #2

 

이전 강의에서 Open GL window 생성하는 것을 공부했는데, 이번 장에서는 삼각형과 사각형을 그려 보도록 하겠다.
화면 생성과 초기화 모두 해 놨기 때문에 도형을 그리는 것은
onDrawFrame() 에 넣으면 된다.

그런데 이전 강의에서 gl.glTranslatef(0.0f, 0.0f, -6.0f); 은 굳이 필요 없었는데 넣었다. 도형 그리는 것을 미리 생각하다 보니까 들어간 것 같다.
오늘은 onDrawFrame() 에 있는
glLoadIdentity() 부터 시작하겠다. 이 메소드를 실행하면 이전까지 해 놨던 좌표계 변환이 초기화 된다.
그래서 x 는 화면의 가로축, y 는 세로축, z 는 깊이 좌표를 나타내게 된다. 그래서 휴대폰 화면의 중앙점을 기준으로 왼쪽은 x 값을 갖고,
오른쪽은 +, 위쪽은 y +, 아래쪽은 -, 화면 앞쪽은 z +, 뒤쪽은 값을 갖게 되는 것이다.

 

먼저 gl.glTranslatef(0.0f, 0.0f, -6.0f); 를 추가한다. 어제 얘기한 것처럼 z 좌표를 뒤로 좀 밀어야 화면에 그림이 보이게 된다.

 

@Override

public void onDrawFrame(GL10 gl) {

           gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

           gl.glLoadIdentity();

           gl.glTranslatef(0.0f, 0.0f, -6.0f);

//         mTriable.draw(gl);

//         mQuad.draw(gl);

 

}

 

주석 처리한 부분은 각각 삼각형과 사각형을 그리는 코드이다. 이것을 지금부터 시작해 보려고 하는 것이다.
네헤의 사이트에 있는 설명을 보면 삼각형과 사각형 그리는 방법이 아주 간단하게 나와 있는데 Android 에서는 이 방법이 지원되지 않는 것 같다.
(
glBegin(GL_TRIANGLES) 이라는 메소드가 없다 -,.-;;). 그래서 몇 가지 복잡한 단계가 추가된다. (첨부 소스 참조할 것)


 


1) 삼각형 그리기 프로젝트에 아래의 클래스를 추가한다. Triangle 클래스 설명은 바로 이어서 나온다.

 

float[] mVertices = { 0.0f, 2.0f, 0.0f,

-0.5f, 1.0f, 0.0f,

 0.5f, 1.0f, 0.0f };

 

Triangle() {

           mVertexBuffer = getFloatBufferFromFloatArray(mVertices);

}

 

 

@Override

void draw(GL10 gl) {

           gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

           gl.glFrontFace(GL10.GL_CW);

           gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);

           gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

           gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);               

}

 

삼각형은 꼭지점 3개로 이뤄진 도형이기 때문에 그에 맞춰서 실행하는 메소드들이 있다.
처음에는 눈에 잘 안 들어 올 텐데 몇 번 하다 보면 익숙해 진다. 이해 안 되는 것은 외우다 보면 이해되는 것 같다.

 

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); 정점(꼭지점)값 배열을 이용한 도형을 그리도록 설정

 

gl.glFrontFace(GL10.GL_CW); 도형을 그릴 때 시계방향으로 돌면서 그리겠다는 의미. GL_CCW 는 반대 방향.
이 설정의 중요성에 대해서 잠깐 설명하자면.. 아래 그림처럼 그리는 방향이 시계 반대 방향과 시계 방향의 삼각형이 있을 때 CL_CCW 로 하면
왼쪽 삼각형이 앞면 오른쪽 삼각형이 뒷면이 되고 CL_CW 로 했을 때는 왼쪽이 뒷면 오른쪽이 앞면을 그리는 방식이 되는 것이다.

어려운가...그러니까 만약 GL_CCW 로 설정했을 때 시계 반대 방향으로 꼭지점 세개를 지정하고 종이에 그림을 그리고 난 후 종이를 뒤집으면
그려진 방향이 시계 방향이 되는 것이다. 다면체를 그릴 때 뒤쪽에 가려서 보이지 않는 면을 처리하는 방법을 고민할 때 확실하게 알게 될 것이다.


 

 

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); 꼭지점 값이 들어 있는 버퍼를 주고 버퍼에 들어 있는 값이 실수라는 것을 알려 준다.
Open GL
은 다면체를 그리던 도형을 그리던 삼각형만 그릴 수 있기 때문에 3 이라는 size 를 주는 것 같다. (하다 보면 알게 되겄지..)

 

gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3); 도형 내부가 채워진 삼각형을 그리겠다는 의미.
GL_LINE_LOOP
은 테두리만 있는 삼각형을 그리겠다는 옵션이다. GL_POINTS 는 점만 찍겠다는 의미.

 

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); 삼각형을 그리고 난 뒤에는 이 기능을 정지 시킨다.

 

getFloatBufferFromFloatArray() 가 정의돼 있는 SmallGLUT 클래스를 선언한다.
클래스로 따로 빼 놓은 이유는 앞으로 여기에 메소드 몇 가지 추가할 것이고 도형을 그리는 클래스들에서 공통으로 상속 받아서 작업할 것이기 때문이다.

 

draw 메소드는 삼각형, 사각형 공통으로 쓰기 때문에 상위 class 정의한다.

 

abstract class Shape {

public FloatBuffer mVertexBuffer;

public ByteBuffer mIndexBuffer;

 

abstract void draw(GL10 gl);

 

FloatBuffer getFloatBufferFromFloatArray(float array[]) {

        ByteBuffer tempBuffer = ByteBuffer.allocateDirect(array.length * 4);

        tempBuffer.order(ByteOrder.nativeOrder());

        FloatBuffer buffer = tempBuffer.asFloatBuffer();

        buffer.put(array);

        buffer.position(0);

        return buffer;

    }

}

 

책에서 본 기억으로는 float 배열을 Byte 배열로 만드는 이유는 Performance 향상을 위해서라고 했던 것 같은데 확실한 지 모르겠다.
암튼버퍼를 만들어야 하는데 float 자료형은 4 byte 크기를 갖기 때문에 배열 크기에 4 를 곱해서 버퍼 크기를 잡아야 한다. (참고, double 8 bytes).
위에 있는 코드의 내용은 그냥 눈으로 이해하면 될 것 같다. 어떤 자료를 봐도 똑 같은 내용이라서 수학 공식 같이 생각되기도 한다.

(float 배열 만큼 ByteBuffer 할당 -> ordering -> FloatBuffer 로 변환 -> float array 값 넣어 줌)

 

이렇게 코딩하고 ctrl + F11 키 누르면 다음과 같은 화면이 보일 것이다.

 


 



아래쪽에 네모를 그려야 하기에 y 좌표를 위로 좀 올렸다.

 

2) 네모 그리기 : Quad 라는 클래스 하나 만든다.

 

public class Quad extends Shape {

 

float[] mVertices = { -0.5f,  0.5f, 0.0f,

                          -0.5f, -0.5f, 0.0f,

                           0.5f, -0.5f, 0.0f,

                           0.5f,  0.5f, 0.0f };

 

byte[] mIndices = { 0, 3, 2,

                        0, 2, 1 };

Quad () {

        mVertexBuffer = getFloatBufferFromFloatArray(mVertices);

        mIndexBuffer = getByteBufferFromByteArray(mIndices);

}

   

    void draw(GL10 gl) {

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

        gl.glFrontFace(GL10.GL_CW);

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);

        gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);

        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

    }

}


네모는 좀 더 복잡한 느낌이 드는 데 index buffer 라는 것이 추가되기 때문이다.
이전 페이지에서도 말했지만 Open gl 은 모든 도형을 삼각형을 모아서 그리기 때문에 사각형부터는 하나의 과정이 더 추가 되는 것이다.

사각형이기 때문에 좌표 값 4개가 필요하고 이 4개의 꼭지점을 어떤 순서로 그릴 것인지 정해 줘야 하는데 이때 필요한 것이 index buffer .
이제 Shape 클래스에 다음 메소드를 추가해 준다.

 

ByteBuffer getByteBufferFromByteArray( byte array[]) {

                     ByteBuffer buffer = ByteBuffer.allocateDirect(array.length);

                     buffer.put(array);

                     buffer.position(0);

                     return buffer;

}

 

index 값이 256 개 를 넘어간다면 ShortBuffer 라는 것을 리턴해 줘야 할 테지만 아직은 몇 개 없기 때문에 걍 ByteBuffer 를 리턴하면 된다.

 

gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);

index
개수와 할당된 ByteBuffer 를 넣어서 네모를 그려주는 메소드. 삼각형은 gl.glDrawArray 를 써서 그렸지만
사각형은 이것을 써야 그릴 수 있다. (glDrawArray 를 쓰려면 index 개수만큼 vertex 설정해주면 된다)

 


저 위쪽에 있는

//         mTriable.draw(gl);

//         mQuad.draw(gl);


이 부분 주석을 풀고 실행하면 아래 화면과 같이 보일 것이다.


   


 





오늘은 여기까지..(아이고 힘들다!!!)







Android OepnGL Lesson #3

 

지난 강의에서는 세모, 네모 그리기 해 봤으니 이번에는 도형에 색깔 칠하는 것을 하겠다.

 

1) 삼각형 단색(빨강)으로 칠하기 각 꼭지점의 color 값 설정을 위해 mColorBuffer 를 추가한다
   
삼각형은 단색으로 칠하기 때문에 사용하지 않음.

 

public class Triangle extends ShapeBase{

       

 

Triangle () {

             mVertexBuffer = getFloatBufferFromFloatArray(mVertices);

       }

 

       @Override

       void draw(GL10 gl) {

             gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

             gl.glFrontFace(GL10.GL_CW);

             gl.glColor4f(1f, 0, 0, 0);  // 새로 추가

             gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);

             gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

             gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

       }

 

gl.glColor4f(1f, 0, 0, 0); Red 값을 가장 밝은 값으로 주고 나머지 green, blue, alpha 값은 0 으로 설정

 

 

2) 사각형 칠하기

 

public class Quad extends ShapeBase {  

 

QuadSmallGLUT() {

             mVertexBuffer = getFloatBufferFromFloatArray(mVertices);

             mIndexBuffer = getByteBufferFromByteArray(mIndices);

             mColorBuffer = getFloatBufferFromFloatArray(mColors); // 새로 추가

}

}

 

gl.glEnableClientState(GL10.GL_COLOR_ARRAY); 혼합된 색을 칠하기 위해 color array 것이라고 알려 .

 

gl.glColorPointer(4,GL10.GL_FLOAT, 0, mColorBuffer); size 값이 4 인데 RGBA (Red, Green, Blue, Alpha) 값을 쓴다는 의미. 3, 4 중에 하나만 있다. 컬러 값은 float 형태이고 그렇게 때문에 마지막 매개 변수도 FloatBuffer 돼야 한다.

3
번째 매개 변수인 0 stride 인데 기본이 0 이다. 값이 0 때는 색깔을 혼합할 인접한 픽셀의 컬러 값이
가장 비슷한 컬러 값이 되도록 칠하게 된다. 다른 값인 경우 어떻게 보이는 지는 테스트 보지 않았다. 

 

실행하면 화면에 이렇게 보인다.

 




 

삼각형 그릴 컬러 값을 주지 않으면 흰색으로  그려지는 것으로 짐작컨대, 기본 컬러 값은 흰색인 같다.







Android OpenGL Lesson #4

 

민방위 훈련이 있어서 반차 내 놓고는 별로 할 일도 없고 해서 계속 올리고 있다..
생각 보다는 빨리 진행되는 데 밑천 떨어지면 아마 세배는 더 시간이 걸릴 것 같다.

 

지난 강의 까지는 도형의 색 칠하는 것까지 했는데 오늘은 좌표 축을 중심으로 도형을 회전시키는 것을 알아 보도록 하겠다.
(Nehe
의 영어는 참 쉽게 설명 돼 있는데 한글로 옮기는 게 더 어렵다.)

 

잠깐 중학교 수학 시간으로 돌아가서 직교 좌표계에 대해서 공부.

 



 

공부할 게 없구나그럼 회전(Rotation) 에 대해서 공부!

 

만약 gl.glRotate(degree, x, y, z) 하는 메소드가 존재한다고 가정해 보자.
그랬을 때 gl.glRotate(90, 1, 0, 0) 이라고 하고 실행하면 x 축을 기준으로 반시계 방향(오른쪽) 으로 90도 만큼 회전하는 것이라고
대충 짐작할 수 있을 것이다
.

 

아래는 다른 사이트에서 참조한 것이니 자세한 것은 직접 방문해 보시기 바란다.

http://blog.jayway.com/2010/01/01/opengl-es-tutorial-for-android-–-part-iii-–-transformations/

 

gl.glRotatef(90f, 1.0f, 0.0f, 0.0f); // OpenGL docs.

gl.glRotatef(90f, 0.0f, 1.0f, 0.0f); // OpenGL docs.

gl.glRotatef(90f, 0.0f, 0.0f, 1.0f); // OpenGL docs.

 



 

중학교 IQ 테스트 문제 같다.. X 축을 기준으로 90 도 회전하면 y 축이 z 축의 방향이 되고 z 축은 –y 방향이 된다는 것만 이해하고 나면
Y:1, Z:1
도 금방 이해 될 것이다. 세 그림은 연속된 회전을 시도한 경우다. 어떤 책에 보면 벡터의 개념으로 설명한 것도 있는데
지금 다시 중학교 수학을 배우기는 힘든 노릇이고 지금 단계에서는 감각적으로만 이해해도 될 것 같다
.

 

그럼 실제로 도형을 한 번 회전 시켜 보도록 하자.

 

1) 세모 (세모와 삼각형의 차이는 뭘까?) 회전 시키기

 

Triangle 클래스에 있는 draw() 안에 다음 줄을 삽입한다.

 

gl.glRotatef(45.0f, 0, 0, 1); z 축을 기준으로 45 반시계 방향으로 회전시키라는 뜻이다.
z
축의 + 방향은 휴대폰 화면에서 사용자를 향하고 있기 때문에 그냥 삼각형이 평면에서 45 회전한 거랑 똑같다.
45
도만 돌린 이유는 90 돌리면 회전했는지 했는지 구분이 가기 때문이다
삼각형, 사각형이 같이 돌아간 것은 glRotatef() 메소드가 전체 좌표계를 돌려 버렸기 때문이다.

 


 

 

그래서 사각형 그릴 때는 좌표계를 초기화 해 줘야 한다.

Quad 클래스의 onDraw() gl.glLoadIdentity(); 를 추가해 줘야 한다.
그럼 이걸로 끝이냐? 본인도 이것만 넣으면 되는 줄 알고 실행했더니 사각형이 어디로 사라져서 보이지 않는 것이었다.
0.5
초 동안패닉에 빠져 있다가 깨어나서 한 줄 밑에다가 gl.glTranslatef(0.0f, 0.0f, -6.0f); 추가하고 실행했더니 기대했던 결과가 나왔다.

 


 

 

문제) , 그러면 세모는 화면 중앙점을 기준으로 큰 원을 그리면서 돌고 네모는 중앙점을 기준으로 제자리에서 빙글빙글 도는 코드를 만들어 보자
       
회전하는 각을 다르게 하면 좀 더 재밌는 화면이 나온다.

 

힌트) gl.glRotatef(mRotateDegree * 4, 0, 0, 1); mRotateDegree += 1;








Android OpenGL Lesson #5

 

민방위 훈련 담날부터 계속 출장이 있어서 공부 하나도 못했다. 수원 삼성 사업장에 파견 댕겨 왔는데,
인터넷도 안되고 할당 받은 IP 는 충돌 난다고 뜨고..개발폰으로 와이파이 잡아 인터넷 하면서 지루하게 하루를 보냈다. 비효율의 극치라고나 할까

 

오늘 어떤 문제의 해답을 하나 찾았다. gl.glTranslatef(1, 0, 0) 에서 매개 변수 값들이 정확히 어떤 의미를 가지는 몰랐는데 알게 됐다.


 

gl.glTranslatef(1, 0, 0) x 축을 따라 + 방향으로 1 만큼 이동한다는 것인데 도형의 x 방향 크기와 같은 거리를
이동한다는 것이다. 그래서 그림처럼 되는 것이다.

그러면 GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f) 에서 near Z 0.1 이고
far Z
100.0 이니까 – Z 방향으로 -99 만큼 이동 해도 도형이 보인다는 같다.(테스트 보지는 않았다).
, 1/100 크기까지 작게 있다는 뜻이겠지.. -100 하면 보이고..

무의식이 일인지 아닌지 모르겠지만 이런 작은 지식이라도 혼자 힘으로 알게 되면 기분이 상쾌해진다^^*

 

오늘은 간단하게 정육면체 그리는 것 하나만 하고 끝내야겠다.

소스는 http://blog.jayway.com/2009/12/03/opengl-es-tutorial-for-android-part-i/ 와 http://insanitydesign.com/wp/projects/nehe-android-ports/
에서 참조했다.

 

public class AndroidOpenGL extends Activity {

       private GLSurfaceView mSurfaceView;

 

       /** Called when the activity is first created. */

       @Override

       public void onCreate(Bundle savedInstanceState) {

             super.onCreate(savedInstanceState);

             mSurfaceView = new GLSurfaceView(this);

             mSurfaceView.setRenderer(new CustomRenderer());

             setContentView(mSurfaceView);

       }

}

 

지난 포스팅까지 GLSurfaceView 상속 받은 클래스 만들어 썼는데 실제로 하는 일이 아무 것도 없었다.
그렇게 썼는 모르겠다. 지금부터는 그냥 객체 하나 할당해서 해야겠다.

 

CustomRenderer.java 어제까지 소스와 다른 부분이 하나도 없다(첨부소스참조)

 

 

public class Cube {   

        ……..

       void draw(GL10 gl) {

             gl.glFrontFace(GL10.GL_CCW);

            

             gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

             gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);

 

             gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

             gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);

 

             gl.glRotatef(mRotateAngle, 1, 0, 0);

             gl.glRotatef(mRotateAngle, 0, 1, 0);

 

             //Draw the vertices as triangles, based on the Index Buffer information

             gl.glDrawElements(GL10.GL_TRIANGLES, mNumOfIndex, GL10.GL_UNSIGNED_BYTE,

mIndexBuffer);

            

             //Disable the client state before leaving

             gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

             gl.glDisableClientState(GL10.GL_COLOR_ARRAY);

            

             mRotateAngle += 0.5f;      // 회전 효과 주기 위해 삽입

}

}

 

그려야 하는 꼭지점의 수가 많이 늘어 났다는 것과 그리는 방향만 고려해 주면 평면 사각형이나 삼각형 그리는 것과 차이가 없다.


 

 


육면체를 그렸는데 육면체가 아닌 것처럼 보인다 

 

그래서 gl.glDrawElements(GL10.GL_LINE_LOOP, mNumOfIndices,

GL10.GL_UNSIGNED_SHORT, mIndicesBuffer);

이렇게 수정해서 실행했더니



 

mRotateAngle 회전하는 Cube 보여주기 위해 추가한 변수다.

 

오늘은 여기까지!!









Android OpenGL Lesson #6

 

오늘은 별거 없다. 소스 정리 좀 하고 사각 뿔 하나 추가하는 게 전부다.

 

Class 구성

1.     CustomRenderer.java : Cube Pyramid 객체 생성하고 좌표계 변환 후 draw() 메소드 호출한다.

2.     Pyramid.java : 생성자에서 사각뿔의 Top 꼭지점 좌표 및 바닥 사각형 좌표, index, 꼭지점 color 값들을 초기화한 후 버퍼 할당하는 메소드에 param 으로 넘겨준다.

4.     Cuber.java : 이름 그대로 육면체의 꼭지점 좌표, index, color 값 설정하는 클래스

 

첨부된 소스를 실행하면 사각뿔과 육면체가 회전하는 것을 볼 수 있다.

 

ScreenShot 은 아래와 같다.

 


 

피라미드는 아래 사각면, 꼭지점을 그리기 위한 좌표가 5개이고 color 값도 5개가 필요하다. 그리고 각 면을 모두

그려줘야 하기 때문에 index 개수가 좀 많다. Cube 는 지난 강의에서 해 봤으니까 다른 설명 필요 없을 것 같다.









Android Open gl Lesson #7

 

지금 까지는 평면에 단색을 칠하거나 꼭지점에 각각 색깔 지정해서 도형을 그렸는데 이제부터는 texture 라는 개념을 공부해 볼 것이다.

 

소스는 다음 사이트에서 참고했다.


http://insanitydesign.com/wp/projects/nehe-android-ports/

 

texture : 감촉, 질감, 조화. 평평한 면에 굴곡이나 불규칙 감을 주는 등 세트에성격을 부여하기 위해서 표면에 페인트 칠을 하거나 커버를 씌우는 일
출처 : 네이버 사전

 

나는 texture 라는 것을 - 2D 평면에 어떤 그림이나 색을 입히는 것으로 이해했다. 그리고 기본적인 메소드 설명은 아래 사이트 참고했다.

http://www.nullterminator.net/gltexture.html

 

textures 가 완전 표준이고 DirectX API cruel 하고 unusual 하다고 표현했는데, 나는 둘 다 안 해봐서 뭐가 좋은지 모른다. 
texture
를 적용하기 위해서는 몇 가지 parameter 값을 줘야 하는데 몇 군데 문서를 봤더니 비슷한 형태다.

 

일단 이미지 로딩하시고

Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.nehe);

 

gl.glGenTextures(1, mTextures, 0); texture 작업을 하기 위해서는 먼저 texture 의 이름이 필요하다.
이렇게 하면 texture 이름을 가져올 수 있다. AVD22 버전에서 이 값을 찍어 봤더니 1 이었다.
HTC G1
으로 했을 때도 1로 나왔다. (Default 1인가??)

 

glBindTexture( GL_TEXTURE_2D, texture ); 가지고 있는 texture 이름을 알아냈기 때문에 이 메소드를 이용해서 우리가 원하는 texture 와 묶어(binding)
쓸 수 있도록 설정한다
. – 써 놓고도 무슨 말인 지 모르겠다현재 단계에서 두 가지 형태의 texture 가 있는데 1D, 2D (그런데 1D를 쓸 일이 있을까??).
위에 링크된 사이트에서는 2D texture 만 쓸 것이라고 돼 있다. 

 

이제 parameter 설정할 차례다.

 

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

 

위 두 줄에 대한 설명은 아래 사이트 참조하시라..

http://mrhoya.tistory.com/entry/Filtering-MIPMAP%EC%9D%98-%EC%9D%B4%ED%95%B4

 

생성된 texture 가 이미지 크기보다 작을 때와 클 때 필터링을 어떻게 할 것인지 설정하는 메소드인데 GL_LINEAR 는 이미지를 깔끔하게 보이지만
그만큼
CPU 계산이 많이 필요하고 GL_NEAREST 는 이미지가 좀 거칠어 보이지만 저 사양에서도 빠르게 처리할 수 있다는 장점이 있다.

 

GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
A version of texImage2D that determines the internalFormat and type automatically.” 내부 포맷과 타입을 자동으로 결정해준다는 말 그대로의 뜻..
그냥 이렇게 쓰는가 보다.

 

, 이제 필요한 parameter 세팅을 했고 Cube 가 어떤 모양인지 vertex 등등을 설정할 차례다.

 

첨부된 소스를 보면 vertex texture index 가 상당히 많은 것을 볼 수 있다. 이게 이렇게 된 이유를 알려면 아래 링크를 참조하시기 바란다.
iphone
어플 개발을 위한 사이트인데 같은 내용이 있어서 링크 걸었다. 내용이 많아서 일일이 다 가져다 붙히기 어렵다.
하지만 반드시 한 번은 정독할 필요가 있다!

http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-6_25.html

 

여기서 Texture Coordinates 부분을 참고하면 된다. 각각의 면에 이미지를 붙히는 작업을 하기 때문에 각 면의 좌표 4개가 모두 필요한 것이라 생각된다.
어렵다 --;

 

마지막으로 남은 과정

 

draw()  {

….

gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTexturesBuffer);

}

 

그러면 이렇게 보인다.

 


 

이상하게도 y 축을 기준으로 뒤집어진 모양으로 보인다. 그래서 vertex texture 의 좌표를 다음과 같이 수정하면 이렇게 보인다.

 

float vertex[] = {

-1.0f, -1.0f, 0.0f, //Vertex 0

 1.0f, -1.0f, 0.0f,  //v1

       -1.0f,  1.0f, 0.0f,   //v2

 1.0f,  1.0f, 0.0f,  //v3   

};

 

float texture[] = {              

0.0f, 0.0f,

0.0f, 1.0f,

1.0f, 0.0f,

1.0f, 1.0f,

};

 

short index[] = {

0, 1, 2, 2, 1, 3,

};

 

 


이미지는 제대로 보이는데 여전히 방향이 맞지 않는다. 좌표를 수정해야 하는데 정확히 어떤 방법으로 하는 지 모르기 때문에 일단 이대로 진행하겠다.

 


만약 texture 의 좌표를 다음과 같이 수정하면 반복된 이미지를 볼 수 있다.

float texture[] = {              

0.0f, 0.0f,

2.0f, 0.0f,

2.0f, 2.0f,

0.0f, 2.0f,

};



 

ps) 인터넷에서 자료 검색하다가 들어간 곳에 다름과 같은 류광선생의 말씀이 있었다.

레퍼런스 없이 OpenGL을 배우는 것은 무모하구요. 레퍼런스로 좋은 책으로는 여러 가지가 있겠지만, 번역서가 있는 책이라면
"OpenGL
프로그래밍 가이드"라는 책을 추천합니다

 

100% 맞는 말이다.






Android Open gl Lesson #8

 

Cube texture 입히는 과정을 설명하겠다. 내용은 이전 강의와 똑같고 다른 점은 vertex, texture, index 배열의 내용이 늘어났다는 것 밖에 없다.

 

이렇게 보인다.

 


 

이전 강의에서처럼 여전히 이미지가 90 도 돌아가 보인다. --;;

 

ps)  첨부 소스 제목과 강의 번호가 맞지 않는데 신경 쓰시지 않아도 됩니당~






Android Open gl Lesson #9

 

이번 강의에서는 물체에 조명 효과를 주는 방법에 대해 설명하겠다.

소스는 다음 사이트에서 참고했다.

 

http://insanitydesign.com/wp/projects/nehe-android-ports/

 

사용하기 편하게 하기 위해 소스 수정을 조금 했는데 원래의 소스는 화면의 특정한 영역을 터치했을 때 light on/off 등의 기능이 동작하도록 했는데
불편한 거 같아 버튼 위젯을 추가해서 기능 수행하도록 했다
(그래서 MainActivity.java onCreate 안의 코드도 다르다).
그리고 Normal vector 의 좌표를 수정했다.

 

조명 효과를 내기 위해서 이해해야 할 필수 내용이 몇 가지가 있다.

1. 광원의 위치 정하기.

2. gl.glEnable(GL10.GL_LIGHT0) 추가하기.

3. Normal vector 방향 정하기.

 

public void onDrawFrame(GL10 gl) {

       if(light) {

             gl.glEnable(GL10.GL_LIGHTING);

       } else {

             gl.glDisable(GL10.GL_LIGHTING);

       }

       gl.glTranslatef(0.0f, 0.0f, z);

}

 

1) 광원의 위치 정하기 : 첨부된 소스를 보면 gl.glEnable(GL10.GL_LIGHT0) 호출해서 GL_LIGHT0 광원으로 사용한다고
  
설정한 gl.glEnable(GL10.GL_LIGHTING) 실행해서 조명 효과를 주도록 있다.
  
그리고 아래 코드가 gl.glTranslatef(0.0f, 0.0f, z)이고 z 초기값이 -5.0f 다.


상식적으로 생각해서 우리가 어떤 사물에 빛이 비춘다는 것을 인식하기 위해서는 물체가 우리 눈 앞에 있어야 하고 광원은 우리가 보고 있는 방향으로 물체에 비춰야 한다. 만약 광원이 물체의 뒤쪽에 있다면 빛이 어떻게 비추는 것인지 알 수 없는 것이다. 그래서 광원의 위치 좌표를 다음과 같이 주게 되는 것이다.


private float[] lightPosition = {0.0f, 0.0f, 2.0f, 1.0f}


여기서 z 축의 좌표를 -5.0 이하의 (-0.5f, -6.0f 등등) 으로 주면 화면에 물체의 모습이 보이지 않게 되는 것이다.
z
좌표가 -5.0f 보다 (-4.0f, -3.0f 등등) 경우에는 x, y 좌표를 조정해가면서 광원의 위치에 따라 그림자 효과가
어떻게 나타나는 확인할 있다. (참고로, 안드로이드 OpenGL ES 에서 모든 좌표값들은 FloatBuffer 변환해야 한다)

 

2) LIGHT 효과 주기 : gl.glEnable(GL10.GL_LIGHT0) gl.glEnable(GL10.GL_LIGHTING) 추가하면 된다.

 

3) Normal vector 방향 정하기 : 고등학교 수학 시간에 벡터 배울 나왔던 용어 같은데 빛이 비출 도형의 면이 어떤 방향으로
  
빛을 받을 것인 지정해 주는 것이라 예상된다(정확한 의미는 모르겠음). 우리가 사용하는 도형인 육면체는 면이 
   
꼭지점 4개로 이루어져 있는데 참고 자료를 보면 꼭지점의 Normal vector 방향을 모두 지정해 줘야 한다고 나와 있다.
  
완벽한 이해를 하기에는 시간이 걸릴 같고 일단은 면에 수직 방향으로 좌표를 지정해 줬더니 조명 효과가 제대로 보이기에
  
이대로 진행하겠다(계속 공부해 나가다 보면 언젠가 이해되는 순간이 있을 것이라 믿는다..).

 

main.xml 설명

RelativeLayout 쓰는 이유는 가로/세로 모드 전환할 (본인의 시료폰은 Nexusone) 각각 layout 따로 만들기 싫어서 이고 이런 경우에는 RelativeLayout 쓰기 편하기 때문이다. 버튼의 의미는 버튼 title 보면 금방 이해가 것이다. filter 버튼을 클릭하면 도형의 질감이 약간씩 변하는 것을 확인할 있다. 특히 이미지가 작을 filter 효과가 두드러진다.

 

첨부 소스를 실행하면 다음과 같이 보인다.

 


 

그림 위에 Touch 해서 여러 방향으로 움직이다 보면 move 방향과 반대로 회전하는 경우가 있는데 이유는 짐작이 가지만
본격 프로젝트가 아니기 때문에 굳이 수정하지는 않았다
.

 

lightPosition = {0.0f, 0.0f, 2.0f, 1.0f} 좌표 값들을 수정해 가면서 실제로 어떻게 보이는 반드시 확인해 보기 바란다.

 

아래 코드 줄이 어떤 역할을 하는 지는 문서상으로는 이해했는데 실제 동작은 이해가 안된다.
줄을 막고 실행해도 동일한 결과가 나온다.


gl.glLightfv(GL10.
GL_LIGHT0, GL10.GL_AMBIENT, lightAmbientBuffer);

gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuseBuffer);      









Android Open gl Lesson #10

 

이번 강의에서는 Blending 효과 주는 방법에 대해서 설명하겠다.

소스는 다음 사이트에서 참고했다.

http://insanitydesign.com/wp/projects/nehe-android-ports/

 

사용하기 편하게 하기 위해 소스 수정을 조금 했는데 원래의 소스는 화면의 특정한 영역을 터치했을 때 light on/off 등의 기능이 동작하도록 했는데
불편한 거 같아 버튼 위젯을 추가해서 기능 수행하도록 했다(그래서 MainActivity.java onCreate 안의 코드도 다르다).
그리고 Normal vector 의 좌표를 수정했다.

 

조명 효과는 지난 포스팅에서 했으니 생략하고 이번에는 blending 에 대해서만 설명하겠다.

먼저 첨부 소스 실행 결과부터..

 


 

조명 효과와 blending 효과가 동시에 보인다. Blending 효과를 없애면 다음처럼 보인다.

 



 

비슷한 거 같지만 실제 실행해보면 다른 점을 확인할 수 있을 것이다 *^^* 조명 효과를 주지 않고 확인하면 금방 알 수 있다.
빛이 앞쪽에서 나가기 때문에 아무래도 뒤쪽 면은 blending 효과가 적게 보이는 것이리라.

 

먼저 다음 사이트를 방문에서 blending 의 원리를 이해하는 시간을 갖도록 해 보자.

http://diehard98.tistory.com/entry/OpenGL-%EB%B8%94%EB%A0%8C%EB%94%A9

 

소스 코드 gl.glDisable(GL10.GL_DEPTH_TEST) 대한 설명을 하자면 깊이 테스팅(GL_DEPTH_TEST) 하지 않으면 새롭게 추가된 색상이 이전 색상을 덮어 쓰게 되며, 깊이 테스팅을 경우에는 이미 있던 값보다 사용자의 눈에 가까운 값인 경우에만 덮어 쓰게 된다.
하지만 이러한 모든 규칙들은 OpenGL 블렌딩이 켜지는 순간 무의미해진다. –“OpenGL superbible” 참조-

 

그러면 지금부터 Blending 핵심적인 부분을 설명하겠다.
바로 gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE) 호출하는 부분이다.

 

Blending 이미 그려진 물체(대상?) 앞이나 뒤에 다시 그림을 그릴 물체의 색을 혼합해서 표시하는 방법이다.
그리고 혼합하는 방식은 가지가 있고 혼합된 컬러 계산식은 다음과 같다.

 

Cf = (Cs * S) + (Cd * D)

 

Cf 최종적인 계산 결과, Cs source 색을 Cd destination 색을, S D source destination
불렌딩 인자에 해당한다. 위에 링크된 사이트에서 설명이 나오는데 블렌딩 인자는 바로 위에 언급한 함수에 의해 설정된다.

 

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE)

 

GL10.GL_SRC_ALPHA source (나중에 칠해지는 대상) 알파값을 블렌딩 인자로 쓴다는 것이고
GL10.
GL_ONE destination 블렌딩 인자로 1 쓴다는 설정이다. 소스 코드 부분에서

gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f) 같이 설정했기 때문에 source 알파값은 0.5f 된다.

그러면 계산식에 대입해 보자. 특정 위치의 점이 다음과 같은 컬러 값을 갖고 있다고 하자.

source color = (1.0f, 0.0f, 0.0f) destination color = {0.0f, 0.0f, 0.0f}


destination
알파 인자는 1 이기 때문에 변경이 없고 source color 다음처럼 변한다.


{0.5f, 0.0f, 0.0f}


그리고
가지 색을 더한 값은 {0.5f, 0.0f, 1.0f} 된다.

 

본인이 이해하지 못했기 때문에 글을 놓고도 무슨 말인지 모르겠다는 솔직한 심정이다.
일단은 이것저것 따질 없이 gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE) 같은 식으로 쓰면
Blending
효과 확인할 있다.


 

자세한 서명은 http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=08 에 가면 얻을 있다.









Android OpenGL lesson #11

 

이번 강의에서는 별 모양 그림을 써서 blending 효과를 구현해 볼 것이다.

소스는 아래 사이트에서 참고했다.

 

http://insanitydesign.com/wp/projects/nehe-android-ports/?sms_ss=dzone

 

이해하기 쉽도록 필요 없을 것 같은 것들(tilt 등등..)은 원래 소스에서 삭제했다.
그리고 twinkle 효과를 주는 부분도 if 조건 문으로 구분해서 구현했다.

 

먼저 실행 결과부터..

 


 


Twinkle
버튼을 클릭하면 각각의 별모양이 제자리에서 회전하는 것을 멈추고 Blending 효과를 꺼 버리면 검은 배경이 이전에 그려진 이미지를 덮어 써 버린다.

 

코드 설명하자면 아래 4 단계를 따라 실행된다.

1. 임의의 색상을 가진 star 객체를 50 개 만든다.

2. 각각의 star 객체에 화면 중심으로부터의 거리(dist) 를 지정한다. 나중에 만들어진 객체의 dist 값이 더 크도록 한다.

3. 화면이 갱신될 때 마다 반짝이는 효과(twinkle) 를 주기 위한 spin 의 값을 증가시킨다.

4. 화면이 갱신될 때 마다 dist 값을 감소시키다가 0 보다 작은 값이 되면 가장 큰 값으로 초기화 시킨다.

 

기본적인 동작이 어떻게 돌아가는 지 확인하기 위해 먼저 star 객체 하나만 생성하고 blend 효과도 꺼 버리고 테스트 해 보자 (배경색도 흰색으로 해야 확인하기 쉽다).


소스에서 수정할 부분은 다음이다.


1) gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f) // White background
설정해준다. 이미지 배경이 검은색이기 때문에 이렇게 해야 잘 보인다.

2) Public Stars(int num) { this.num = 1; ..} // 일단 star 객체를 하나만 만든다.

3) blend = false

 

이렇게 하고 테스트 하면 검은색 배경을 가진 외로운 별 하나가 화면 가장자리에서 만들어진 다음 중심을 향해 천천히 움직이다가 없어지고
다시 바깥쪽에서 만들어지는 것을 볼 수 있다.

 

전체 코드에서 이해가 필요한 중요 부분은 다음이다.

 

if(twinkle) {

           //Twinkle with an over drawn second star

           gl.glColor4f(       (float)stars[(num - loop) - 1].r/255,

                                (float)stars[(num - loop) - 1].g/255,

                                (float)stars[(num - loop) - 1].b/255,

                                1.0f);

           //Continuously iterate and spin all stars

           gl.glRotatef(spin, 0.0f, 0.0f, 1.0f);

} else {

           //Set the random star color

           gl.glColor4f((float)star.r/255, (float)star.g/255, (float)star.b/255, 1.0f);

}

 

gl.glColor4f 는 그려지는 대상의 컬러 값 비중을 지정하는 메소드다.  glColor4f(r,g,b,a) 을 실행하면 원래 있는 컬러 값에 r,g,b,a 를 곱하는 방식으로 색 변경을
하게 된다
. Star.png 파일은 검은색 배경과 흰색 별 모양으로 구성돼 있는데 검은색은 RGB 값으로 (0.0f, 0.0f, 0.0f) 이고 흰색은 (1.0f, 1.0f, 1.0f) 이다.

그래서 검은색은 곱해봐야 0 값이 나와 그대로 유지되고 흰색은 임의의 컬러 값으로 변하게 되는 것이다.
그래서 첨부된 소스를 실행하면 알록달록한 별들이 만들어 지는 것이다.(빅뱅!!)

 

ps) 이번 포스팅하면서 새로 알게 된 게 있다.

Star.java 에 보면 texture 를 입히는 대상인 사각형을 그릴 때

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3) 와 같이 구현했다. 본인은 사각형을 그리려면 glDrawArrays 를 쓸 때는 좌표를 6개 지정하거나 glDrawElements 를 쓸 때는 index 를 써야지만 가능할 것이라 생각했는데 GL_TRIANGLE_STRIP 인 경우에는 좌표를 4개만 쓰고도 사각형을 그릴 수 있다.
glDrawArrays (GL10.GL_TRIANGLES,..)
인 경우에는 제대로 된 사각형이 그려지지 않는다.

 

혹시 궁금한 점이 있으면 gidools@gmail.com 으로 메일 주시기 바랍니다. 저도 모르는 게 너무 많은 상태에서 진행하다 보니 틀린 내용도 많고 이해 안 되는 것도 많습니다. 그리고 다른 분들은 궁금한 내용을 빼 먹었을 수도 있을 것입니다..같이 연구하고 고민하면 배우는 속도가 더 빨라 질 것 같네요 ^^*







Android OpenGL lesson #12

 

이번 강의는 아래 위쪽이 텍스쳐로 막혀 있고 옆 면의 특정한 부분은 뚫려 있는 방(room)과 같은 공간을 그려 보는 방법에 대해 알아보겠다.
그리고 정점(,vertex) texture 값들을 소스에 하드 코딩하는 것이 아니라 외부 파일에 저장한 다음 불러 오는 방식을 보여준다.
이렇게 하면 좋은 점이 다시 컴파일 하는 과정 없이 외부 리소스 데이터만 바꿔서 새로운 화면을 그릴 수 있다는 것이다.

 

소스는 역시나 아래 사이트에서 참고했다.

http://insanitydesign.com/wp/projects/nehe-android-ports/

 

이번 장에서도 OpenGL 이해에 꼭 필요한 코드가 아닌 경우에는 삭제를 했고 앞 장에서도 언급했듯이
본인의 테스트 시료가 넥서스원이라 실제 폰에서 실행되지 않는 코드는 삭제 했다
.

 

먼저 실행 화면부터 보겠다. (먼저 소스 실행부터 해 볼 것을 권한다)

 

 


프로그램 실행 순서는 다음과 같다.

1. 외부 파일 /sdcard/world.txt 를 읽어서 파싱한다음 필요한 데이터 얻음.

2. 데이터를 배열로 저장한 다음 FloatBuffer 타입의 Vertext Buffer Texture Buffer 를 만든다.

3. 만들어진 Buffer 를 이용해서 화면에 그림을 그린다.

 

이번 장에서 가장 특이한 점은 index 배열이 없다는 것이다. 그래서 gl.glDrawElement() 메소드 대신에 gl.glDrawArrays() 메소드를 사용하는 것이다.
그렇기 때문에 vertex 배열의 크기가 다른 예제에 비해서 많이 크다는 것을 알 수 있을 것이다. Index 배열 없이 사각형의 texture를 그리기 위해서는
index
개수만큼(6) vertex 를 지정해 줘야 하기 때문이다.

 

코드 설명 :

CustomGLSurfaceView.java

이전 예제에서는 touch listener CustomGLSurfaceView 담당했는데 이번 장에서는 World.java 객체가 touch 받아야하기
때문에 world.loadGLTexture(gl, this.context) 추가했다(코드의 효유성 면에서는 방법이 나은 같기도 하다).
그리고 Touch 이벤트가 World 객체에 전달돼야 하기 때문에 아래 메소드 처리가 필요하다.

 

public boolean onTouchEvent(MotionEvent event) {

       return true;

}

 

부분이 없으면 화면 터치 움직여도 아무 반응을 보이지 않을 것이다.

 

World.java

/sdcard/world.txt 에서 데이터 추출하는 과정 외에는 이전 소스들과 차이가 없다.
다만 index 쓰지 않고 vertex 만으로 처리하기 때문에 편리를 위해 가지 inner class 정의해서 쓰고 있다는 차이다.

 

World.txt

종이에다 각각의 vertex 찍어서 직접 그려 것을 권한다.
일단 어떤 형태인지 머리 속에 그려 놓으면 다음의 내용들이 이해가 쉬울 것이다.

 

본인은 이번 장의 내용 이해를 위해 다음과 같은 변경을 보았다.

1. world.txt NUMPOLLIES  4 수정해서 테스트

그러면 다음과 같은 이미지가 보인다.

 


 

예상했겠지만 바닥과 천장만 있는 이미지가 보인다.

 

 

2. public void onDrawFrame(GL10 gl) 에서 gl.glTranslatef(0.0f, 0.0f, -7.0f) 추가.

 


 

 

Blending 효과를 on 시키고 회전시키면 다음과 같은 이미지가 보인다.


 

 

이제 다시 원래 값이 NUMPOLLIES 36 으로 수정하고 실행하면 보이는 화면


 

이리 저리 돌리다 보면 뚫린 구멍 개가 일치하는 순간도 보인다. Blending on 시키면 신기한 화면을 있을 것이다.

 

정도 테스트 봤다면 이번 장에서 얘기하고자 하는 바를 대충 이해했을 것이라 본다.

 

그럼 이만








Android OpenGl #13

 

오늘은 쉬어 가는 순서..

 

복습하는 겸해서 점으로 이루어진 원을 그려 보도록 하겠다. 생각해 보니까 도형을 그릴 때 점부터 시작해야 하는데 삼각형부터 시작했다.

 

먼저 소스 실행부터..


 

검은색 배경에 노란색 점들이 원을 이루고 있다.

 

gl.glDrawArrays(GL10.GL_POINTS, 0, mVertex.length / 3) 써서 정점을 점으로 그리도록 했다.

 

그럼..점의 크기를 늘여보자.

onDrawFrame() gl.glPointSize(2.0f) 추가한다.

 


 











OpenGL stencil Buffer                                                                                                                                                                            

오랜만에 하나 올린다. 오늘은 OpenGL ES 버전이 아니라 OpenGL 에 대한 내용이다.

 

Stencil buffer - 첨 봤을 때는 어떻게 쓰는 것인지 전혀 감이 안 왔었는데 다시 보니까 이해가 된다.
인터넷에서 검색해 봐도 제대로 된 설명이 안 나와 있어서 초보의 마음으로 접근해 본다.

 

전체 예제 소스는 첨부 파일을 열어서 실행하면 된다.

 

핵심 내용에 대해서만 설명하겠다.

 

void RenderScene(void)

{

           GLdouble dRadius = 0.1; // Initial radius of spiral

           GLdouble dAngle;        // Looping variable

 

           // Clear blue window

           glClearColor(0.0f, 0.0f, 1.0f, 0.0f);

 

           // Use 0 for clear stencil, enable stencil test

           glClearStencil(0.0f);

           glEnable(GL_STENCIL_TEST);

 

           // Clear color and stencil buffer

           glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

 

           // All drawing commands fail the stencil test, and are not

           // drawn, but increment the value in the stencil buffer.

           glStencilFunc(GL_NEVER, 0x0, 0x0);

           glStencilOp(GL_INCR, GL_INCR, GL_INCR);

 

           // Spiral pattern will create stencil pattern

           // Draw the spiral pattern with white lines. We

           // make the lines  white to demonstrate that the

           // stencil function prevents them from being drawn

           glColor3f(1.0f, 1.0f, 1.0f);

           glBegin(GL_LINE_STRIP);

           for(dAngle = 0; dAngle < 400.0; dAngle += 0.1)

           {

                     glVertex2d(dRadius * cos(dAngle), dRadius * sin(dAngle));

                     dRadius *= 1.002;

           }

           glEnd();

 

           // Now, allow drawing, except where the stencil pattern is 0x1

           // and do not make any further changes to the stencil buffer

           glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);

           glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

 

           // Now draw red bouncing square

           // (x and y) are modified by a timer function

           glColor3f(1.0f, 0.0f, 0.0f);

           glRectf(x, y, x + rsize, y - rsize);

 

           // All done, do the buffer swap

           glutSwapBuffers();

}

 

아무것도 수정하지 않은 상태의 결과 화면은 아래 그림이다.



 

그럼, 이제부터 설명..

 

glClearStencil(0.0f);

스텐실 버퍼의 값을 모두 0으로 채운다는 뜻이다. glClearColor(0.0f, 0.0f, 1.0f) 함수를 호출한 다음  glClear(GL_COLOR_BUFFER_BIT) 함수를 실행하면
컬러 버퍼의 값이 모두 파란색 컬러 값으로 채워지듯이 마찬가지로 스텐실 버퍼를
0 으로 채운다는 개념으로 이해하면 된다.
책 본문의 소스는 0.0f 값을 줬는데 사실 glClearStencil() 함수의 매개변수는 GLint 값으로 들어가야 한다.
타입 캐스팅 돼서 GLint 형으로 변경될 것이고 돌아가는데 문제는 없지만 바람직한 코드는 아니다.
따라서 glClearStencil(0) 과 같은 식으로 코드를 수정하는 것이 올바른 코드가 되겠다.

 

glEnable(GL_STENCIL_TEST);

스텐실 테스팅 기능을 사용할거라고 선언하는 부분이다. 이거 안 넣으면 나선원이던 붉은 사각형이던 모두 화면에 그냥 그려진다.

 

glStencilFunc(GL_NEVER, 0x0, 0x0);

이 함수의 첫번째 parameter 는 몇 가지가 있다. GL_ALWAYS, GL_LESS 등등이 있다. 그런데 여기서는 GL_NEVER 를 쓰고 있다.
, 스텐실 버퍼가 어떤 값이 들어가 있던지 무엇을 그리던지 아무것도 그려지지 않게 하겠다는 뜻이다. 그러므로 GL_NEVER 가 첫번째 값이면
두번째와 세번째 매개 변수가 의미가 없게 된다
. 비슷한 의미로 GL_ALWAYS 로 설정되면 모든 값을 통과 시킨다는 뜻.
즉 화면에 그리고자 하는 그림이 그대로 보인다는 뜻이다.

GL_ALWAYS 로 설정하고 테스트 해 보면 두번째 세번째 매개 변수의 값이 뭐가 되던지 아래 처럼 화면에 그려진다.

 

 


여기서는 glStencilFunc(GL_NOTEQUAL, 0x0, 0x0); glStencilFunc(GL_NEVER, 0x0, 0x0); 와 같은 의미가 된다.
왜냐면 스텐실 버퍼의 값이 모두 0 으로 채워져 있기 때문에 0 이 아닌 경우가 없어 모든 스텐실 테스팅이 실패하기 때문이다.

 

glStencilOp(GL_INCR, GL_INCR, GL_INCR);

교재에도 나와 있듯이 첫번째 매개 변수는 스텐실 테스트가 실패했을 때 그 다음 동작을 두번째 매개 변수는 스텐실 테스트가 성공하고 depth 테스트가 실패했을 때 세번째는 스텐실 테스트와 깊이 테스트가 모두 성공했을 때 그 다음 동작을 정의하는 것이다. 첫번째 매개변수가 GL_INCR 로 돼 있으므로 스텐실 테스트가 실패했을 때 스텐실 버퍼에 있는 값을 하나 증가시킨다는 뜻이다. 바로 위 함수에서 glStencilFunc(GL_NVER, 0, 0) 으로 했기 때문에 화면에 어떤 그림을 그리던지 모든 스텐실 테스트가 실패한다. , 화면에 아무것도 보이지 않게 된다. 스텐실 테스트가 성공해야 그 부분이 화면에 보이게 되는데 일단은 모든 테스트가 실패하도록 설정했기 때문에 화면에 아무것도 보이지 않는 것이고 그렇기 때문에 첫번째 옵션인 GL_INCR 이 동작하게 된다.

그래서 결국 glClearStencil(0.0f)을 실행해서 0 으로 채워진 스텐실 버퍼의 내용이 1로 바뀌게 된다. 화면에 뭔가를 그리고자 했을 때 보이지는 않지만 해당 위치의 버퍼 내용만 1로 바뀌게 되는 것이다(종이에 구멍이 뚫린다는 개념으로 이해하면 된다). 버퍼의 나머지 부분은 그대로 0 으로 유지된다.

 

glBegin(GL_LINE_STRIP);

화면에 나선형의 그림을 그리는 부분이다. 스텐실 테스트 기능을 꺼 버린다면 - glEnable(GL_STENCIL_TEST) 함수를 실행하지 않는다면 서로 이어지는 나선형(?) 원들이 화면에 보일 것이다. 그런데 스텐실 테스트 기능이 on 상태이고 glStencilFunc(GL_NEVER, 0x0, 0x0) 함수를 실행했기 때문에 화면에 아무것도 보이지 않는 상태인 것이다. 하지만 변화된 내용이 있는데 그것은 glStencilOp(GL_INCR, GL_INCR, GL_INCR) 를 실행하면서 나선원이 그려지는 부분에 있는 스텐실 버퍼의 값이 1 로 변경된다는 것이다. (일단 스텐실 버퍼의 위치와 화면 픽셀이 1:1 매핑이 된다고 생각하면 이해하기 편하다)

 

glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);

두번째 param 과 세번째 param And 연산한 값이 스텐실 버퍼에 있는 값과 같지 않을 때 통과 시키겠다는 뜻. 나선원을 그린 후에 스텐실 버퍼는 그리고자 하는 위치에 있는 버퍼의 값이 1로 변경됐는데 이 함수를 실행하면 1과 같지 않은 것만 통과 시키겠다는 뜻이다. 즉 화면에 뭔가를 그린다면 나선원 부분만 제외하고 그려질 것이다. 만약 glStencilFunc(GL_NOTEQUAL, 0x2, 0x2); 이렇게 하면 어떻게 그려질까? 0x2 & 0x2 = 0x2 이고 스텐실 버퍼에 있는 값 중에서 0x2 가 아닌 것은 모두 통과 시키겠다는 뜻이므로 결국 화면에 뭘 그리면 그대로 모두 그려지게 된다. 그럼 glStencilFunc(GL_NOTEQUAL, 0x2, 0x1); 요렇게 하면? 0x2 & 0x1 = 0x0 이므로 0 이 아닌 것만 통과 시키게다는 뜻. 결국 나선원 부분만 그려지게 된다.
직접 실행해 보면 아래처럼 보인다.



 

glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

첫 번째 매개변수는 스텐실 테스팅이 실패했을 경우 다음 동작이라고 설명했다. 나선원이 그려진 부분의 스텐실 버퍼값이 1 이고 나머지 부분은 모두 0 이기 때문에 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 함수를 실행하면 나선원이 있는 버퍼의 내용과 스텐실 테스트했을 때는 실패, 나머지 부분에서는 모두 성공이 나온다. 왜냐면 0x1 & 0x1 = 0x1 이 아닌 값은 모두 통과 시킨다고 했는데 나선원이 그려진 부분만 1 이기 때문이다. 그래서 0x1 인 버퍼와 테스트했을 때는 실패이므로 첫번째 매개변수인 GL_KEEP을 실행하고 0x0 인 스텐실 버퍼값과 테스트했을 때는 성공이므로 두번째 매개변수인 GL_KEEP 이 실행된다. 만약 두번째 매개변수를 GL_INCR로 하면 어떻게 될까나? 그러면 0 의 값이 들어가 있던 스텐실 버퍼의 내용이 모두 1로 되므로 두번째 프레임 부터는 화면에 아무것도 그려지지 않을 것이다라고 예상할 수 있으나 사실은 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

함수 실행은 이 예제에서 의미가 없는 코드다. 왜냐면 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 를 실행하면서 어떤 값들을 통과 시킬지 이미 결정이 나 있는 상태이기 때문에 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 를 실행한다고 해도 RenderScene() 함수가 다시 호출되면서 스텐실 버퍼의 값이 모두 0 으로 초기화 되기 때문이다.

만약 첫번째 glStencilOp(GL_ INCR, GL_INCR, GL_ INCR); 함수를 실행할 때 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 와 같은 식으로 실행했다면 스텐실 버퍼의 내용이 변하는 게 없어서 결국 glStencilFunc(GL_NOTEQUAL, 0x1, 0x1); 실행하면서 화면에 그리고자 하는 내용이 모두 그려지게 된다.

 

glColor3f(1.0f, 0.0f, 0.0f);

glRectf(x, y, x + rsize, y - rsize);

스텐실 테스트와 관련된 함수들을 차례대로 실행하면서 나선원이 그려진 부분은 스텐실 테스팅이 실패하고 나머지 부분에서는 모두 성공할 것이라는 세팅이 돼 있기 때문에 붉은 사각형을 그리면 나선원 부분만 보이지 않고 나머지는 모두 그려진다.

 

부디 이해가 되셨기를..

에고 힘들당..









Android opengl lesson 14

 

증강현실 어플 구현하다가 막히는 부분이 있었는데 임시로나마 해결해서 하나 포스팅한다.

 

증강현실 구현하려면 카메라 Preview 화면이 백그라운드에 보이면서 그 위에 하나의 View를 겹쳐 보이도록 구현해야 한다.
일반적인 View일 수도 있는데 보통은 GLSurfaceView를 많이 쓰는 것 같다.

 

필요한 것은 아래와 같다.

1. Camera preview를 위한 SurfaceView

2. Opengl 을 위한 GLSurfaceView

3. 화면에 그리기

 

그러면 step by step으로 구현해 보자.

 

1. Camera preview 구현하기

: 엄청 간단하다. SurfaceView를 상속 받은 클래스 하나 구현하기만 하면 된다. 인터넷에 예제 코드가 많을 것이므로 참고하면 된다.
     
그리고 Preview 화면은 일반적으로 전체 화면으로 그리는 경우가 많기 때문에 화면 사이즈에 가장 맞는 preview 사이즈를 구해서 뿌려주면 된다.
      Device
하나가 지원하는 preview size 가 여러가지이므로 적당한 것을 찾으면 된다. 아래 코드가 이것을 구현한 것이다.

 

double minDiff = Double.MAX_VALUE;

for (Camera.Size size : sizes) {

if (Math.abs(size.height - height) < minDiff) {

                      mFrameWidth = size.width;

            mFrameHeight = size.height;

            minDiff = Math.abs(size.height - height);

      }

}

 

params.setPreviewSize(getFrameWidth(), getFrameHeight());

mCamera.setParameters(params);


 


2. Opengl view 구현하기

 : 이것도 역시 간단하다. GLSurfaceView 상속 받은 클래스 하나 만들어서 그리면 된다.

  제일 중요한 부분이 아래 코드다. 백그라운드를 투명 처리해서 아래쪽 카메라 preview 가 보이도록 한다.

setEGLConfigChooser(8, 8, 8, 8, 16, 8);

getHolder().setFormat(PixelFormat.RGBA_8888);

 

setEGLConfigChooser() 메소드의 인자들이 어떤 의미를 갖고 있는지는 정확히 모르겠는데(rgba size, depth, stencil 등등..)
Irrlicht
와 같은 게임엔진을 돌리면서 그림자가 나오도록 하려면 마지막 인자가 0 이 아닌 값이 돼야 한다.

 

그리고 이 코드도 중요하다.

setZOrderOnTop(true);

위 코드는 GLSurfaceView Camera preview 보다 상위에서 그려지도록 한다.

이게 없을 때 어떤 문제가 생기냐면 Home 키 눌러 프로그램 종료하고 다시 실행했을 때 opengl 화면이 보이지 않는다.
(Back 키로 종료했을 때는 문제가 안 생긴다)

 

3. 화면에 그리기

 : SetContentView addContentView 를 쓰면 된다. 인터넷에 올라와 있는 자료에는 화면에 그리는 순서가 opengl 위에 camera preview로 돼 있다.
  
상식적으로 생각하면 camera preview 위에
opengl 일 거 같은데 그러면 카메라 preview 화면만 보인다. 왜 그런지는 모르겠다.
  
그런데
setZOrderOnTop (true) 를 넣어주면 순서에 상관 없이 잘 보인다.
  
버튼을 추가한 이유는
setZOrderOnTop(true) 코드 때문에 버튼 클릭이 안될수도 있을 거 같아 확인 차원에서 넣어본 것이다.

 

소스는 첨부파일로 확인하면 됩니당 ~~






AndroidOpenGL_10.zip


AndroidOpenGL_11.zip


AndroidOpenGL_12.rar


AndroidOpenGL_13.zip


AndroidOpenGL_14.zip


AndroidOpenGL_2.zip


AndroidOpenGL_3.zip


AndroidOpenGL_4.zip


AndroidOpenGL_5.zip


AndroidOpenGL_6.zip


AndroidOpenGL_7.zip


AndroidOpenGL_8.zip


AndroidOpenGL_9.zip


OpenGL Dev.zip




AndroidOpenGL_4.zip
0.05MB
AndroidOpenGL_9.zip
0.2MB
AndroidOpenGL_8.zip
1.78MB
AndroidOpenGL_3.zip
0.05MB
AndroidOpenGL_12.rar
0.37MB
AndroidOpenGL_5.zip
0.04MB
AndroidOpenGL_14.zip
0.02MB
OpenGL Dev.zip
0.09MB
AndroidOpenGL_13.zip
0.04MB
AndroidOpenGL_2.zip
0.01MB
AndroidOpenGL_10.zip
0.18MB
AndroidOpenGL_11.zip
0.06MB
AndroidOpenGL_6.zip
0.05MB
AndroidOpenGL_7.zip
0.44MB