스마트기기개발관련/OpenGL (그래픽, 게임)

안드로이드 오픈지엘 OpenGL ES 2.0 매핑 투영 카메라 관련

AlrepondTech 2014. 6. 11. 11:35
반응형

 

 

=================================

=================================

=================================

 

 

 

출처: http://blog.naver.com/PostView.nhn?blogId=winhee81&logNo=170597011

 

 

 

안드로이드는 OpenGL기반 고성능 2D, 3D 그래픽을 지원한다.

OpenGL은 cross-platform API로써 3D 그래픽 처리 장치와의 인터페이스를 지원한다.

OpenGL ES는 OpenGL군 중에서 임베디드 장치를 목적으로 특화 된 스펙을 가지고 있다.

OpenGL ES 1.0과 1.1 API 스펙은 안드로이드 1.0을 시작으로 지원되었다.

안드로이드 2.2(API 레벨 8)부터는 프레임워크에서 OpenGL ES 2.0 API 스펙을 지원하게 되었다.

(노트: 안드로이드 프레임워크가 제공하는 특정 API는 J2ME JSR239 OpenGL ES API와 유사하지만 일치하지는 않는다.)

 

The Basic

안드로이드는 프레임워크 API와 NDK 두 곳에서 OpenGL을 지원한다.

본 자료에서는 프레임워크의 API를 다룬다.

 

안드로이드 프레임워크에는 OpenGL ES API를 사용하도록 두개의 기본 클래스를 제공하고 있다.

이 두 클래스는 GLSurfaceView, GLSurfaceView.Renderer이다.

안드로이드 어플리케이션 내에서 OpenGL을 사용하는 것이 목적이라면 첫번 째 목표를

Acitivty 내에서 이 두 클래스들이 어떻게 동작하는지를 이해하는 것으로 하는것이 좋다.

 

GLSurfaceView

이 클래스는 OpenGL API call을 사용해 오브젝트들을 draw 할 수 있는 View클래스이며, SurfaceView와 기능적으로 비슷하다.

GLSurfaceVIew의 인스턴스를 생성하고, 렌더러를 추가하는 것으로 이 클래스를 사용할 수 있다.

터치 스크린등의 이벤트를 사용하려고 하는 경우에는 GLSurfaceView를 상속받아 사용하면 된다.

 

GLSurfaceView.Renderer

이 인터페이스에는 OpenGL GLSurfaceView에서 그래픽 draw를 하기 위해 필요한 메소드들이 정의 되어 있다.

GLSurfaceView.setRenderer()를 통해 GLSurfaceView에 독립된 클래스로 구현 된 랜더러의 인스턴스를 제공해야 한다.

GLSurfaceView.Renderer 인더페이스에세는 아래 메소드들이 구현되어야 한다.

onSurfaceCreated(): GLSurfaceView가 생성 될 때 시스템에 의해 한번 호출된다.

                              OpenGL 환경을 세팅하거나 OpenGL의 그래픽 요소들을 초기화 하는데 사용하면 된다.

onDrawFrame(): GLSurfaceView를 다시 draw 할 때 마다 호출된다. drawing을 하는 주요 메소드로 사용하면 된다.

onSurfaceChanged(): GLSurfaceView의 크기, 장치의 orientation등의 지오메트리가 변경되면 호출된다.

                                GLSurfaceView 컨테이너 내에서의 변화에 대한 대응을 위해 이 메소드를 사용하면 된다.

 

OpenGL 패키지

OpenGL을 사용하기 위해 GLSurfaceView를 컨테이너 view 및 GLSurfaceView.Renderer를 생성했다면,

이제 OpenGL API를 호출할 준비가 된 것이다.

 

OpenGL ES 1.0/1.1 API 패키지

    * android.opengl - 이 패키지는 OpenGL ES 1.0/1.0 클래스의 static메소드들을 제공하며, javax.microedition.khronos 패키지보다

                               더 나은 성능을 제공한다.

    * GLES10

    * GLES10Ext

    * GLES11

    * GLES10Ext

    * javax.microedition.khronos.opengles - 이 패키지는 OpenGL ES 1.0/1.1 표준 API를 제공한다.

    * GL10

    * GL10Ext

    * GL11

    * GL11Ext

    * GL11ExtensionPack

 

OpenGL ES 2.0 API 클래스

    * android.opengl.GLES20 - 안드로이드 2.2(API 레벨 8)부터 제공되며, OpenGL ES 2.0 인터페이스를 제공한다.

 

OpenGL 요구사항 선언

어플리케이션이 몇몇 기기들에서는 지원하지 않는 OpenGL 기능을 사용한다면, 이러한 요구사항을 AndroidManifest.xml에

선언해야 한다.

*  OpenGL ES 버전 요구사항 - 어플리케이션이 OpenGL ES 2.0만 지원한다면 아래와 같이 선언하면 된다.

     <!-- Tell the system this app requires OpenGL ES 2.0 -->
     <uses-feature android:glEsVersion="0x00020000" android:required="true"/>

이렇게 선언된 어플리케이션이 구글 플레이에 올려지면 OpenGL ES 2.0을 지원하지 않는 기기에서는 인스톨에 실패하게 된다.

 

* 텍스쳐 압축 요구사항 - 어플리케이션이 텍스쳐 압축 포맷을 사용한다면, AndroidManifest.xml의 <supports-gl-texture>를 통해 어플리케이션이 지원하는 포맷을 명시해야 한다.

텍스쳐 압축 요구사항을 명시하게 되면 설치된 어플리케이션의 텍스쳐 포맷들 중 단 하나라도 지원하지 않는 장치의 사용자는 이 어플리케이션을 실행할 수 없다.

 

그려질 물체의 좌표 매핑

안드로이드 기기에 그래픽을 보여주는 작업에는 몇가지 고려사항이 있는 데 그중 하나가 스크린 크기와 형태가 다양하다는 것이다.

OpenGL은 기본적으로, 스크린이 사각형이고 단일 좌표 체계를 사용한다고 추측하고 drawing작업을 하게 된다.

 

 

 

 

 

Figure1. 기본 OepnGL 좌표에서의 물체(왼쪽)와 일반적인 안드로이드 스크린에 매핑된 물체(오른쪽)

 

이런 문제를 해결하기 위해서 OpenGL 투영 모드와 카메라 뷰를 적용해 좌표를 변환해 어느 디스플레이에서도 물체가 동일하게 보이도록 할 수 있다.

투영 matrix와 카메라 뷰 matrix를 생성하고 이들을 OpenGL 랜더링 파이프라인에 적용시키면 된다. 투영 matrix는 그래픽 물체들의 좌표를 재계산하여 안드로이드 기기 스크린에 맞도록 매핑을 수행한다. 카메라 뷰 matrix는 바라보는 시점으로부터 물체가 어떻게 보이는지를 계산 및 변환해준다.

 

OepnGL ES 1.0에서의 투영 및 카메라 뷰

ES 1.0 API에서는 투영 matrix및 카메라 뷰 matrix를 만들고 그들을 OpenGL 환경에 적용시켜야 한다.

1. 투영 matrix - 기기 스크린의 지오메트리에 따라 투영 matrix를 만들어 물체 좌표를 재계산해 알맞은 비율로 그려지도록 한다.

                        아래 예제는 스크린 비율에 따라 투영 matrix를 만드는 방법을 보여준다.

   public void onSurfaceChanged(GL10 gl, int width, int height) {     
         gl.glViewport(0, 0, width, height);       
         // make adjustments for screen ratio       
        float ratio = (float) width / height;       
        gl.glMatrixMode(GL10.GL_PROJECTION); // set matrix to projection mode       
        gl.glLoadIdentity(); // reset the matrix to its default state       
        gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7); // apply the projection matrix   
   } 

 

2. 카메라 변환 matrix - 투영 matrix를 통해 좌표를 변환하고 나면, 카메라 view도 적용을 해야 한다. 아래 예제 코드는 모델뷰를

                                   적용하고, Glu.gluLookAt() 유틸리티를 사용해 카메라 위치를 설정해 준다.

   public void onDrawFrame(GL10 gl) { ... // Set GL_MODELVIEW transformation mode       
          gl.glMatrixMode(GL10.GL_MODELVIEW);         
          gl.glLoadIdentity(); // reset the matrix to its default state         
          // When using GL_MODELVIEW, you must set the camera view         
          GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);         
          ...     
  } 

 

OpenGL ES 2.0에서의 투영과 카메라 뷰

ES 2.0 API에서는 그래픽 물체들을 위한 버텍스 쉐이더에 matrix member를 추가하는 것으로 투영과 카메라 뷰 전환을 적용할 수 있다.

 

1. 버텍스 쉐이더에 matrix 추가 - 뷰 투영 matrix를 위한 변수를 만들고 이를 쉐이더 정점들에 행렬곱으로 적용되도록 한다.

   아래 예제는 버텍스 쉐이더 코드이며, 포함된 uMVPMatrix member가 투영과 카메라 뷰 matrix의 역할을 수행한다.

     private final String vertexShaderCode =       
     // This matrix member variable provides a hook to manipulate         
     // the coordinates of objects that use this vertex shader         
     "uniform mat4 uMVPMatrix; \n" +         
     "attribute vec4 vPosition; \n" +         
     "void main(){ \n" +         
     // the matrix must be included as part of gl_Position         
     "gl_Position = uMVPMatrix * vPosition; \n" +         
     "} \n"; 

노트: 위 예제에는 하나의 matrix member가 버텍스 쉐이더내에 선언되어 있는 데, 이 matrix에는 투영 matrix와 카메라 뷰 matrix가 행렬곱

        된다. 만일 이 두 matrix를 분리 된 상태로 두고 싶다면, 두 개의 matrix를 버텍스 쉐이더내에 선언하면 된다.

 

2. 쉐이더 matrix에 데이터 쓰기 - 버텍스 쉐이더에 대한 핸들을 얻게 되면 이제 투영과 가메라 뷰 matrix 적용을 위해 이 uMVPMatrix에

   접근해야 한다. 아래 코드는 해당 버텍스 쉐이더의 matrix 변수에 접근하기 위한 핸들을 어떻게 얻는지 보여준다.

   public void onSurfaceCreated(GL10 unused, EGLConfig config) {        
         ...         
         muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 
         ...     
  } 

 

3. 투영 및 카메라 뷰 matrix 생성 - 투영 및 카메라 뷰 matrix를 생성하여 그래픽 물체에 적용시킨다.

   아래 예제 코드는 어떻게 투영 및 카메라 뷰 matrix를 생성하는 지 보여준다.

   public void onSurfaceCreated(GL10 unused, EGLConfig config) {       
         ...         
         // Create a camera view matrix         
         Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);     
  }     


  public void onSurfaceChanged(GL10 unused, int width, int height) {         
         GLES20.glViewport(0, 0, width, height);         
         float ratio = (float) width / height;         
         // create a projection matrix from device screen geometry         
         Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);     
  } 

 

4. 투영 및 카메라 뷰 matrix 적용 - 투영 및 카메라 뷰 matrix를 적용하기 위해 이 두 matrix를 행렬곱 한 뒤 버텍스 쉐이더에 적용시킨다

                                                    아래 예제 코드는 어떻게 이 두 matrix를 조합하고, 버텍스 쉐이더에 적용시키는 지 보여준다.

  public void onDrawFrame(GL10 unused) {         
          ...         
          // Combine the projection and camera view matrices         
          Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);         
 
          // Apply the combined projection and camera view transformations         
          GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);         
        
          // Draw objects         
          ...     
  } 

 

표면 형태 및 Winding

OpenGL에서 물체 표면은 3축 좌표계(x,y,z)에서의 3개 또는 그 이상의 점들로 구성된다. 이 점들을 버텍스(vertex)라고 부르며 앞면, 뒷면을 구성한다. 어떤 면이 앞이고 뒤인지는 어떻게 구분하는가? 답은 점들을 이어 붙이는(WInding) 방향에 있다.

 

 

 

figure2. 반시계 방향 순서로 정의 된 버텍스들

 

이 예제에서 삼각형의 점들은 반시계 방향으로 그려지도록 정의 되었다. 기본적으로 OpenGL에서는 이렇게 반시계 방향으로 그려진 표면이 앞면이된다. 그러므로 figure2에 보여진 삼각형의 표면은 앞면이다.

 

어떤 표면이 앞면인지 알아내는게 왜 중요한가? 답은 OpenGL의 기능중 face culling이라는 기능을 사용하기 위해서다. face culling은 OpenGL 환경 옵션이며, 랜더링을 구성하는 파이프라인 내에서 물체의 뒷면은 계산 및 그리기 작업에서 제외 시켜 시간과 메모리 공간을

더 적게 쓰게 한다.

 // enable face culling feature
  gl.glEnable(GL10.GL_CULL_FACE); 
  // specify which faces to not draw 
  gl.glCullFace(GL10.GL_BACK); 

이 face culling 기능을 어느 표면이 물체의 앞면인지 뒷면인지 구성하지 않은 채로 쓰게 된다면, 이 물체는 좀 얇아 보이거나 심지어 전혀 보이지 않을 수도 있다. 그러므로 항시 표면을 제대로 정의 해 주는 건 중요하다.

 

노트: OpenGL 환경을 설정해 시계방향이 물체의 앞면이 되도록 할 수도 있다. 하지만 이렇게 하려면 더 많은 코드를 작성해야 하고

        OpenGL을 이해하는 개발자들에 있어 혼란을 가져올 수 있다. 그러므로 추천하지는 않는다.

 

OpenGL 버전 및 기기 호환성

텍스쳐 압축 지원 

텍스쳐 압축기법은 요구 되는 메모리를 줄이므로 OpenGL 머플리케이션에 더 나은 성능을 제공한다. 안드로이드 프레임워크는 ETC1 압축 포맷을 표준 기능으로써 제공하고 있다. 또한 ETC1Util 유틸리티 클래스와 etc1tool 압축 툴(안드로이드 SDK의 tools 폴더에 있음)도 함께 제공한다. 텍스쳐 압축을 사용하는 안드로이드 샘플 코드는 SDK의 samples 폴더에 존재한다.

ETC 포맷은 대부분의 안드로이드 기기에서 지원 되지만 항시 그렇다고 보장하지는 않는다. 어플리케이션이 알파채널을 가지는 텍스쳐를 사용한다면 이를 위한 텍스쳐 압축 포맷이 기기에 존재하는지 확인해 봐야한다.

 

ETC1 포맷 이후로 안드로이드 기기는 GPU 칩셋과 OpenGL을 기반으로 하는 다양한 텍스쳐 압축을 지원한다.

그러므로 타겟팅하는 단말이 제공하는 텍스쳐 압축 타입이 무엇인지 확인하고 결정할 필요가 있다.

제공 된 단말의 지원 텍스쳐 포맷을 결정하기 위해서는 반드시 OpenGL 쿼리를 통해 해당 기기가 어떤 텍스쳐 압축 포맷을 지원하는 지 알아 봐야 한다. 일반적인 텍스쳐 압축 포맷들은 아래에 나열 되어 있다.

    * ATITC(ATC) - ATI 텍스쳐 압축 포맷은 알파채널을 포함 또는 포함하지 않는 RGB텍스쳐를 위한 고정 비율 압축 기법을 사용한다.

                           이 포맷은 OpenGL extension에서 몇몇 이름들로 정의되어 있다.

                      GL_AMD_compressed_ATC_texture

                      GL_ATI_texture_compression_atitc

    * PVRTC - PowerVR 텍스쳐 압축 포맷은 알파채널을 포함 또는 포함하지 않는 2비트 또는 4비트로 구성 된 픽셀을 가지는 텍스쳐

                     압축 포맷이다.

                     GL_IMG_texture_compression_pvrtc

    * S3TC(DXTn/DXTC) - S3 텍스쳐 압축 포맷은 몇가지 포맷(DXT1 ~ DXT5)으로 구분되고 위의 두 포맷들 보다는 범용적이지 않다.

                                     포맷은 4비트 또는 8비트의 알파 채널을 가지는 RGB 텍스쳐를 지원한다.

                              GL_OES_texture_compression_S3TC

                              GL_EXT_texture_compression_s3tc

                              GL_EXT_texture_compression_dxt1

                              GL_EXT_texture_compression_dxt3

                              GL_EXT_texture_compression_dxt5

    * 3DC - 3DC 텍스쳐 압축 포맷 역시 범용적이지 못하다. 알파채널을 가지는 RGB 텍스쳐를 지원한다.

                 GL_AMP_compressed_3DC_texture 

 

주의: 위 텍스쳐 압축 포맷이 모든 기기에서 지원 되지는 않는다.

노트: 어플리케이션에서 사용할 텍스쳐 압축 포맷을 결정했다면, AndroidManifest파일 내 <supports-gl-texture>에 정의하여야 한다.

        이렇게 정의하면 구글 플레이 같은 외부서비스에서 필터링 되어 요구 사항에 충족 되는 기기에만 인스톨 된다.

 

OpenGL extension 결정

안드로이드 기기에 따라 OpenGL의 extension은 다양하다.

특정 기기가 지원하는 텍스쳐 압축 포맷 및 OpenGL extension들 중 원하는 옵션을 적용하기 위해서는 아래와 같이 하면 된다.

1. 타겟으로 하는 기기에서 지원하는 텍스쳐 압축 포맷을 확인하기 위해 아래 코드를 실행한다.

  String extensions = javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);

주의: 이 메소드의 결과는 기기에 따라 다양하게 나타난다. 여러 기기들에서 위 코드를 수행해 보고 공통적으로 지원하는 텍스쳐 압축 포맷을 찾자

2. 또한 해당 기기에서 지원하는 OpenGL extension을 확인하여 사용하려는 extension을 결정한다.

 

OpenGL API 선택

OpenGL ES API 버전 1.0(1.1 extension포함)과 2.0 모두 3D게임, UI drawing에 있어 고성능 그래픽 인터페이스를 제공한다.

하지만 두 API는 프로그래밍을 하는 기법이 많이 다르다. 그러므로 개발자는 두 API를 가지고 개발을 시작하기 전에 아래 요소들을 고려해 볼 필요가 있다.

성능 - 일반적으로 OpenGL ES 2.0은 ES 1.0/1.1 API 보다 더 빠른 성능을 제공한다. 하지만, 안드로이드 기기에 따라 OpenGL 그래픽

          파이프라인을 수행하는 방법이 다른 이유로 성능차는 다양하게 나타난다.

기기 호환성 - 개발자는 기기의 타입, 안드로이드 버전과 사용가능한 OpenGL ES 버전등을 고려할 필요가 있다.

코딩 편리성 - OpenGL ES 1.0/1.1 API는 고정 기능 파이프라인을 사용하고 ES 2.0 API에서는 제공되지 않는 편리한 기능들을 제공한다.

                   OpenGL에 입문한 개발자들은 OpenGL ES 1.0/1.1 코딩이 더 간결하고 편함을 느끼게 될 것이다.

그래픽 제어 - OpenGL ES 2.0 API는 더 많은 제어권을 개발자에게 부여한다. 쉐이더의 동작을 프로그래밍을 통해 제어할 수 있고,

                   더 직접적으로 그래픽 파이프 라인에 관여할 수 있게 한다. 또한, 1.0/1.1 API에서는 하기 힘든 효과들을 더 쉽게 만들 수

                   있게한다.

 

 

 

=================================

=================================

=================================

 

 

반응형