안드로이드 jPCT-AE로 (근사) 직교투영(Orthogonal Projection) 실현하기

2012/07/04 17:23

많은 2D 게임들이 GPU를 사용하기 위해 OpenGL ES를 랩핑한 다양한 2D 렌더링 엔진을 사용합니다. jPCT-AE도 3D 엔진이니 2D를 표현하는데 사용할 수 있겠죠? 실제로 OpenGL API에서는 gluPerspective()와 gluOrtho() 함수를 제공해서 원근투영(Perspective Projection)직교투영(Orthogonal Projection)을 지원합니다. 전자는 3D 객체가 거리에 따라 원근감을 표현하기 위한 투영방식이고 후자는 원근에 상관없이 항상 동일한 비율로 보여주기 위한 투영방식입니다. 두 개는 섞어쓸 수 없으며 항상 한개만 선택해야 하죠. 그래서 2D 표현을 위해서는 직교투영을 선택해야 할 겁니다. 


참고로 이 글을 보시기 전에 제가 전에 작성한 글을 미리 보시면 좀 더 도움됩니다.

안드로이드 jPCT-AE로 OpenGL ES 3D 데모 개발 http://blog.jidolstar.com/824 

안드로이드 jPCT-AE를 활용해 간단한 기하구조를 만들어 Object3D를 다뤄보기 http://blog.jidolstar.com/825


요즘 맹대표님도 관련 글을 적어주시고 계시네요.

http://www.diebuster.com/adr/?cat=1 


jPCT-AE는 안드로이드를 위한 OpenGL ES를 Java로 개발할 수 있도록 최적화된 3D 엔진입니다. 그렇기 때문에 당연히 gluOrtho()와 같은 기능이 있어야 하지만 API를 찾아보면 눈을 씻고봐도 찾을 수 없습니다. 결국 검색하면 무려 이 엔진을 개발한 사람의 답변을 들을 수 있습니다.


http://www.jpct.net/forum2/index.php?topic=631.0 

http://www.jpct.net/forum2/index.php?topic=1453.0


내용은 jPCT는 직교투영을 지원하지 않는다 입니다. 헉..... 

굉장히 간단한 함수를 지원하지 않는다는게 말이나 되는가 싶지만, 어쨌든 포기할 순 없겠죠. 그래서 두가지 방법이 있습니다.


  1. 직교투영이 되게 하기 위해 jPCT-AE 원본소스를 가져다가 기능을 추가
  2. 근사적인 직교투영이 가능하도록 관련된 수치 및 공식을 찾는다.  


첫번째 방법은 이왕 3D 엔진 쓰는건데.... 꼭 저렇게 해야하는 건가? 그냥 OpenGL 쓰고 말지라는 생각이 들어서...(그보다 그걸 팔 의지가 없어서~ ^^) 두번째 방법를 선택했습니다. 


원근투영이지만 직교투영에 가깝게 하려면 먼저 OpenGL의 gluPerspective() 함수의 인자를 이해해야 합니다. 이 함수의 인터페이스는 다음과 같습니다.


gluPerspective( fov, aspect, near, far );


fov는 시야각(라디안), aspect는 가로세로 종횡비, near, far는 3D객체의 카메라로부터의 상대거리로써 클리핑(clipping) 위한 최소, 최대 거리 값을 의미합니다. 여기서 가장 중요한 것은 fov입니다.


fov는 field of view의 약어로 디바이스의 실제 화면에 보여지는 대상의 시야각을 나타냅니다. 직교투영에서는 시야각의 크기와 상관없이 3D 물체의 왜곡이 없습니다. 하지만 원근투영에서는 이 값이 클수록 화면의 구석으로 갈수록 왜곡이 생깁니다. 원근을 나타내주기 위한 필요값이긴 하지만 fov가 적당하지 않으면 오히려 사실적이지 못한 화면을 보여줄 수 밖에 없게 되지요. 물론 큰 값을 가져도 나름 멋진 모습을 연출하기도 합니다. 카메라 사진사들이 어안렌즈로 주변 환경을 찍는 느낌이겠지요. 아무튼 중요한 것은 왜곡이 없어야 직교투영에 가깝다고 할 수 있으므로 fov값은 작아야 합니다. jPCT에서 기본값이 1.25입니다. Camera.getFov()를 통해서 확인할 수 있습니다. 이 값을 0.2로 수정해 작은 값을 표현하려면 Camera.setFov(fov)를 하면 됩니다. 하지만 jPCT는 fov 기본범위가 0.5 ~ 1.5로 되어 있습니다. 다행히 이 범위도 조정할 수 있는데 Camera.setFOVLimits(min, max)로 가능합니다. min값을 우리가 정한 0.2로 설정하면 되겠죠? 


fov를 충분히 작게한다는 것은 알겠는데... 직교투영을 실현한다는 것은 어떤 의미일까요? 

이것은 가령, 400px의 폭을 가진 이미지를 화면에 붙이면 반드시 화면에도 400px의 영역을 차지해야하는 것과 동일한 겁니다. 직교투영은 반드시 카메라와 3D객체간의 거리에 상관없이 왜곡되지도 또 크기도 작아지지 않는 것이니깐요. 그럴려면 필히 여기서 중요하게 찾아야 하는 값은 바로 카메라와 3D객체간에 거리입니다. 이 값을 z값이라고 합시다. fov는 0.2로 하기로 했는데 직교투영을 실현하기 위해 z값은 어떻게 설정하면 될까요? 그것은 간단한 삼각함수로부터 근사적 계산이 가능해 집니다.





tan( fov / 2 ) = w / (2 * z) 


여기서 fov : 시야각, w는 화면의 폭(px), z는 카메라와 3D 객체간의 거리 입니다. fov와 w는 이미 아는 값이므로 z를 계산할 수 있습니다.


z = w / ( 2 * tan( fov / 2 ) )



fov를 0.2, 화면폭이 480px이면 z값은 대략 2400정도가 나옵니다. 그럼 결국 카메라를 3D 객체로부터 2400떨어져 있으면 근사적인 직교투영이 성립된다는 말입니다. 하지만 jPCT는 여기서 잘 알아봐야 하는게 2400이면 꽤 많이 떨어진 것이므로 gluPerspective( fov, aspect, near, far );의 near, far값을 잘 조절애햐 합니다. 실제로 jPCT에는 Config.nearPlane, Config.farPlane값이 있는데 farPlane의 기본값이 1000입니다. 우리가 계산한 2400을 카메라의 z축 위치값으로 설정하면 3D 객체가 보일 수 없다는 소리입니다. 그래서 Config.farPlane를 2400 이상의 값으로 설정해야합니다. 또는 계산하다보면 nearPlane에 설정된 기본값보다 작은 경우도 생길지 모르겠습니다. 그런 경우는 nearPlane도 값도 당연히 조정해줘야 합니다. 


어쨌든 이 모든 결과를 종합해서 호스트 코드의 일부분을 보여준다면 다음과 같아집니다.


//화면크기를 기준으로 평면 4x4로 타일 평면 배치 
float rw = w / 4.0f;
float rh = h / 4.0f;
float[] coordinates = {
          0.0f, 0.0f, 0.0f //TL 0
        , rw, 0.0f, 0.0f //TR 1
        , 0.0f, rh, 0.0f //BL 2s
        , rw, rh, 0.0f //BR 3
};
float[] uvs = {
          0.0f, 0.0f
        , 1.0f, 0.0f 
        , 0.0f, 1.0f 
        , 1.0f, 1.0f
};
int[] indices = {
          0, 2, 1
        , 1, 2, 3
        
};

for( float x = -w / 2.0f; x < w / 2.0f; x += rw ){
    for( float y = -h / 2.0f; y < w / 2.0f; y += rh ){
        int textureId;
        Object3D obj;
        textureId = TextureManager.getInstance().getTextureID("mybaby");
        obj = new Object3D(coordinates, uvs, indices, textureId);
        obj.translate( x, y, 0 );
        objects.add(obj);
        world.addObject(obj);
    }
}

Logger.log( w + ", " + h );

//fov 0.2를 기준으로 오쏘고날 모드에 적합한 카메라 z값을 찾아 적용!
float fov = 0.2f;
float z = w / 2 / (float)Math.tan( (double)(fov / 2.0) );
Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, z);
cam.lookAt(new SimpleVector(0.0f, 0.0f, 0.0f));
cam.setFOVLimits(fov, 2.0f);
cam.setFOV(fov);
Config.farPlane = z + 100.0f;

Log.e("camera z", String.valueOf(z));
Log.e("fov", String.valueOf(cam.getFOV()));
Log.e("max fov", String.valueOf(cam.getMaxFOV()));
Log.e("min fov", String.valueOf(cam.getMinFOV()));
Log.e("y fov ", String.valueOf(cam.getYFOV()));


아래는 결과 화면입니다. 

jPCT-AE 대략적인 Orthogonal 실현



정확히 화면안에 들어갑니다. 이로써 완벽하지 않지만 근사적으로 원근투영 환경에서 직교투영을 구현해 2D 렌더링 할 수 있는 환경을 구성할 수 있게 되었습니다.


참고 : Calculating the gluPerspective matrix and other OpenGL matrix maths 


글쓴이 : 지돌스타(http://blog.jidolstar.com/826)

 

저작자 표시 비영리 동일 조건 변경 허락

Android , , , , , , , , , , ,