brunch

You can make anything
by writing

C.S.Lewis

by 서준수 Nov 25. 2019

OpenGL ES 2.0 예제

3D 사각뿔 그리기

OpenGL ES는 3차원 컴퓨터 그래픽스 API인 OpenGL(Open Graphic Library)의 임베디드 시스템을 위한 버전이다. ES가 Embaedded System을 의미한다.


OpenGL은 다양한 API를 제공하며 해당 API들을 통해 점, 선, 삼각형, 사각형, 빛 등을 화면에 그릴 수 있다. 즉, OpenGL은 화면에 뭔가 그릴 수 있도록 도와주는 API들로 이루어진 라이브러리다.


비슷한 API로 Windows에는 DirectX가 있다. 차이점은 OpenGL은 크로스 플랫폼을 지원한다.


OpenGL ES가 화면에 Object를 그리는 방법


기본 개념

1. Vertex (정점)

대표적으로는 Position을 나타낸다. 2D는 (x, y), 3D는 (x, y, z) 정보를 가진다. 그 외에 색상 정보 등을 가지고 있다. Color는 RGBA(색의 3원 색인 Red, Green, Blue와 명암을 나타내는 Alpha 값)로 나타낸다.


2. Polygon (다각형)

Vertex 두 개를 연결하면 선이다. 세 개를 연결하면 삼각형이 된다. 이렇게 Vertex를 연결해서 만든 면을 Polygon이라고 한다. Polygon은 3D 그래픽에서 물체를 표현할 때 쓰이는 기본 단위인 다각형이다. OpenGL에서 Polygon의 최소 단위는 삼각형(triangle)이다. 사각형은 두 개의 삼각형을 연결해서 표현한다.


3. Shader

Vertex 정보를 바탕으로 GPU가 실제로 화면에 그릴 수 있도록 처리하는 프로그램이다. 대표적으로 두 가지 프로그램이 있다.


Vertex Shader (정점 셰이더)


Vertex 정보를 2D 또는 3D 공간에 배치하는 작업을 한다. 각 정점의 최종 위치를 생성하며 정점마다 한 번씩 실행한다. OpenGL은 정점 집합을 취해 점, 선 및 삼각형으로 만든다.


Fragment Shader (프레그먼트 셰이더)


점, 선 또는 삼각형의 각 프레그먼트 최종 색상을 생성하며 프레그먼트마다 한 번 실행된다. 프레그먼트는 픽셀과 유사한 단일 색상의 작고 직사각형인 영역이다.



Fragment Shader의 주목적은 GPU에게 최종적인 프레그먼트의 색이 무엇이 되어야 하는지 알려주는 것이다. 기본 요소의 모든 프레그먼트에 대해 한 번씩 호출되므로 삼각형이 1000개의 프레그먼트로 매핑되면 Fragment Shader는 1000번 호출된다.


4. Rasterization

Vertex Shader를 통해 변환된 데이터를 Fragment Shader에서 처리 가능한 정보로 바꿔주는 과정을 Rasterization이라고 한다. 이런 처리를 하는 것을 Rasterizer라고 한다.

다음의 Figure 1과 Figure 2가 Vertex Shader 단계이며 Figure 3이 Rasterization 단계이다.


5. Renderer

Vertex > Vertex Shader > Rasterizer > Fragment Shader를 통해 만들어진 데이터를 화면에 보여주는 작업을 Rendering이라고 한다. 이렇게 데이터들을 화면에 출력해주는 역할을 하는 것을 Renderer라고 부른다.


6. Rendering Pipeline

앞서 언급한 데이터가 화면에 그려지는 처리과정을 Rendering Pipeline이라고 한다.

Vertex 등의 데이터를 Vertex Shader로 처리하고 Rasterization을 거친 뒤 Fragment Shader를 통해 픽셀에 색상 값을 주고 화면에 그리는 것(Rendering)까지이다.


7. MVP

(이미지 출처 : https://www.charlezz.com/?p=960)

Model

모델은 위 스크린샷을 기준으로 정육면체를 의미한다. 즉, 화면에 나타내고자 하는 객체이다.

View

Model 객체를 바라보는 대상, 즉 카메라 객체를 의미한다. 어떤 피사체를 찍을 때 바라보는 시점이 된다.

Projection

카메라가 모델을 볼 수 있는 공간이다. 스크린샷에서 흰 선으로 그려진 사각뿔의 Frustum(절두체)가 Projection이다. Projection은 대표적으로 두 가지가 있다.


(이미지 출처 : https://www.charlezz.com/?p=960)

Perspective Projection : 사람의 눈이나 카메라처럼 원근법을 적용하여 객체를 표현한다. 따라서 최종 렌더링 결과물을 보면 익숙하다.

Orthographic Projection : 절두체 안에 있는 모든 오브젝트들을 원근과 왜곡 없이 표현한다.


도형을 그리는 방식

OpenGL은 정점을 이어서 도형을 구성한다. 정점을 잇는 방식은 다음과 같다.

(이미지 출처 : https://www.slideshare.net/darvind/open-gles-31304407)

GL_POINTS : 정점에 해당하는 픽셀의 점을 그린다.

GL_LINES : 정점 배열 순서로 라인을 그리며 선과 선이 이어지지 않는다.

GL_LINE_STRIP : 정점 배열을 순서대로 이어 선을 이어 그린다.

GL_LINE_LOOP : 정점 배열을 순서대로 이어 선을 그리고 첫 번째와 마지막 정점도 이어서 그린다.

GL_TRIANGLES : 정점 3개를 이어서 삼각형을 그린다. 2개의 삼각형을 그리려면 6개의 정점이 필요하다.

GL_TRIANGLE_STRIP : 첫 삼각형을 그릴 때는 정점 3개를 이어서 삼각형을 그리고, 다음 삼각형부터 하나의 추가 정점만 필요하다. 이전 정점 두 개를 이어서 그리기 때문이다. ex) (v0, v1, v2), (v1, v2, v3), (v2, v3, v4), (v3, v4, v5)

GL_TRIANGLE_FAN : 첫 삼각형 시작 정점을 기준으로 이전 삼각형의 마지막 정점과 추가 정점을 이용하여 삼각형을 그린다. 첫 번째 삼각형을 그릴 때는 (v0, v1, v2), 두 번째는 (v0, v2, v3), 세 번째는 (v0, v3, v4)의 순으로 반복하여 그린다. 마지막은 (v0, v4, v5)이다.


안드로이드에서 지원하는 OpenGL ES 버전

안드로이드 1.0 이상 : OpenGL ES 1.0과 1.1

안드로이드 2.0(API Lever 8) 이상 : OpenGL ES 2.0

안드로이드 4.3(API Lever 18) 이상 : OpenGL ES 3.0

안드로이드 5.0(API Lever 21) 이상 : OpenGL ES 3.1


안드로이드에서 OpenGL ES 설정하기

AndroidManifest에 다음과 같이 사용할 버전을 명시한다.


버전 2.0

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

버전 3.0

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

버전 3.1

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



예제 Source

예제는 이 블로그의 코드를 이용한다. 여기에 추가적으로 사각뿔을 어떻게 표현하는지 추가할 것이다.

모든 작업이 완료된 결과물은 아래 코드를 받으면 된다.


그래픽 객체 그리기

안드로이드에서 OpenGL을 사용하여 그래픽 객체(도형 등)를 그리기 위해서는 크게 다음 세 가지가 필요하다.

GLSurfaceView, GLSurfaceView.Renderer, Triangle(그리려는 사용자 정의 그래픽 객체)


GLSurfaceView 클래스는 그래픽 객체가 그려지게 되는 View이다. GLSurfaceView이 인스턴스를 생성하고 별도의 클래스로 구현한 GLSurfaceView.Renderer의 객체를 setRenderer 메서드를 통해 GLSurfaceView의 인스턴스에 할당한다.


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


삼각형 그리기

삼각형을 그리기 위해서는 Triangle 클래스에 좌표를 정의해야 한다.

OpenGL ES는 3차원 좌표(x, y, z)를 사용하며 기본적으로 원점(0, 0, 0)을 GLSurfaceView의 중앙으로 한다.

따라서 좌표 (1, 1, 0)은 GLSurfaceView은 우측 상단 코너이고 좌표 (-1, -1, 0)은 좌측 하단 코너이다.

주의할 점은 도형의 좌표를 정의할 때 반시계 방향으로 배치해야 한다는 것이다.

(이미지 출처 : https://developer.android.com/guide/topics/graphics/opengl)


OpenGL과 안드로이드 좌표계 차이점

OpenGL은 정사각형 형태의 균일 좌표계를 가정한다. 이러한 좌표를 사각형이 아닌 화면에 정사각형인 것처럼 그린다. 하기 이미지 좌측은 OpenGL의 균일 좌표계이고 우측은 실제 안드로이드 가로 화면에 매핑되는 모습을 나타낸다.


(이미지 출처 : https://developer.android.com/guide/topics/graphics/opengl)


실제 동작시에 하기와 같이 세로와 가로 화면에서 보이는 도형의 비율이 균일하지 않다.



이 문제를 해결하기 위해서는 Projection의 좌우를 비율로 설정하고 적절한 View를 설정해야 한다. 이를 위해서 다음의 단계를 따른다.


- 삼각형은 원점에 존재한다. 따라서 Model 행렬은 별도의 연산 없이 단위행렬로 지정한다.

- Perspective Projection Matrix를 만든다.

- 카메라(View)는 원점에 있으면 삼각형을 볼 수 없으므로 z축으로 일정 거리를 둔다.


현재 삼각형 좌표는 다음과 같다.


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

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

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

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

    };



삼각형 좌표를 다음과 같이 단순하게 설정하도록 한다.



Projection Matrix는 아래와 같이 FrustumM() 함수를 이용하여 만든다.


 //3차원 공간의 점을 2차원 화면에 보여주기 위해 사용되는 projection matrix를 정의

 Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);


frustumM() 더보기


View Matrix는 setLookAt() 함수를 이용하여 만든다.


//카메라 위치를 나타내는 Camera view matirx를 정의

Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);


인자1 : View Matrix

인자2 : Matrix offset

인자3~5 : eye (카메라의 위치)

인자6~8 : center (카메라가 바라보는 방향)

인자9~11 : up vector (카메라의 위쪽 방향)


다음 이미지를 참고하면 이해하기 쉽다.

(이미지 출처 : https://www.charlezz.com/?p=960)


Projection과 View 설정을 마치면 다음과 같이 안드로이드 가로 화면에서도 도형의 비율이 유지되는 것을 확인할 수 있다.



up vector가 (0,1,0)이면 y축 방향으로 카메라의 위쪽이 위를 향하여 위와 같은 모습이 된다.

참고로 up vector 설정을 통해 카메라의 위쪽 방향을 아래로 변경한 모습은 다음과 같다. 즉, up vector가 (0,-1,0)이다.



사각뿔(3D 도형) 그리기

삼각형을 그리는 방법을 응용하여 입체감이 있는 3D 도형인 사각뿔을 그릴 수 있다.

사각뿔을 그리기 위한 좌표를 다음과 같다.


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

            //front

            0.0f, 0.5f, 0.0f, // 상단 vertex

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

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

            //right

            0.0f, 0.5f, 0.0f, // 상단 vertex

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

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

            //back

            0.0f, 0.5f, 0.0f, // 상단 vertex

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

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

            //left

            0.0f, 0.5f, 0.0f, // 상단 vertex

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

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

    };


삼각형을 그릴 때 각 점을 넣는 순서가 반시계 방향인 것은 동일하다. 주의할 점은 외부(바깥쪽)에서 바라보는 시점으로 반시계 방향이다. 정면이 아니다.


앞선 좌표 정보를 그림으로 나타내면 다음과 같다.


최초 좌표는 전면 삼각형이다. 다음 삼각형의 좌표는 우측 삼각형의 좌표이다. 다시 한번 말하지만 이때 반시계 기준은 View가 Model을 외부에서 바라볼 때이다. 그다음은 뒷면 삼각형이고 이때도 외부에서 Model을 바라보는 기준으로 좌표 순서를 정해야 한다. 마지막은 좌측 삼각형이 된다.



여기까지 진행한 후 실행하면 위와 같은 화면이 나타난다. 사각뿔이 아닌데?


3D의 형태를 눈으로 확인하기 위해서는 View Matrix도 아래와 같이 변경해야 한다.

(x, z 축으로 3씩 이동하여 우측 상단에서 바라보는 형태)



 //카메라 위치를 나타내는 Camera view matirx를 정의

 Matrix.setLookAtM(mViewMatrix, 0, 3, 0, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);


그럼 다음과 같이 사각뿔 형태의 모양을 볼 수 있다.


ref.)

찰스의 안드로이드 : https://www.charlezz.com/?p=960

Google Developer : https://developer.android.com/guide/topics/graphics/opengl

SlideShare : https://www.slideshare.net/darvind/open-gles-3130440

Nova Woo님 미디엄 : https://medium.com/@NovaWoo/opengl%EC%9D%84-%EC%9C%84%ED%95%9C-3d-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-1-1f305105b478

ntu : https://www.ntu.edu.sg/home/ehchua/programming/opengl/CG_BasicsTheory.html

Josh Beam's Website : https://www.joshbeam.com/articles/triangle_rasterization/

멈춤보다 천천히라도 : https://webnautes.tistory.com/1009

LINSOO님 블로그 : https://linsoo.co.kr/archives/17639

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari