|
- | OpenGL ES |
- | 모바일 센서 API |
- | 게임 루프 |
- | 결론 및 미래를 위한 아이디어 |
- | 자료 |
- | 저자 정보 |
OpenGL ES는 Khronos Group에서 관리하는 표준으로, Khronos Group은 이 밖에 몇 가지 다른 그래픽 및 오디오 표준(OpenGL, OpenAL)도 관리합니다. OpenGL ES는 표준 OpenGL API를 기반으로 합니다. 따라서 OpenGL에 익숙한 사용자라면 아래의 코드는 대부분 새롭지 않을 것입니다. 자바 ME로 구현한 OpenGL ES는 표준 OpenGL 버전 1.5를 기반으로 한 버전 1.1을 지원합니다. OpenGL ES 버전 2.0도 있으나 이 버전의 경우 필요한 하드웨어가 훨씬 많으며 현재로서는 썬의 모바일 전화에 적합하지 않습니다.
3D 프로그래밍에 대해 여기서 자세히 설명하지는 않겠습니다. OpenGL과 3D 프로그래밍의 일반론을 소개하는 여러 가지 자료가 많이 있습니다. 여기서는 자바 ME에서 OpenGL ES를 초기화하는 방법과 사용자 장치에 맞게 적절히 구성하는 방법에 대해 알아보겠습니다.
JSR-239는 기본적으로 OpenGL ES API를 C 프로그래밍 언어용으로 직접 구현한 것입니다. 자바 SE NIO API의 부분적인 자바 ME 구현이 포함된 java.nio
라는 패키지도 있습니다. 이 구현은 OpenGL ES가 꼭지점, 수직선, 텍스처 등에 사용하는 기본 버퍼를 만드는 데 사용됩니다.
JSR-239로 3D 드로잉을 하기 위해 GameCanvas
를 사용하고 그 위에 3D 장면을 그리겠습니다. 우선 그래픽을 초기화하고 색상 심도 및 기타 OpenGL 관련 등록 정보를 구성해야 합니다. 다음은 빨간색 5비트, 초록색 6비트, 파란색 5비트(색상 심도 총 16비트)와 16비트의 심도 버퍼를 사용하여 그래픽을 초기화하는 코드입니다. 초록색이 빨간색 및 파란색보다 1비트 큰 이유는 인간의 눈으로 판별할 수 있는 초록색 색조의 수가 더 많기 때문입니다.
private boolean initGraphics() { egl = (EGL11) EGLContext.getEGL(); if (egl == null) { System.out.println("Error: could not initialize OpenGL ES"); return false; } eglDisplay = egl.eglGetDisplay(EGL11.EGL_DEFAULT_DISPLAY); if (eglDisplay == null) { System.out.println("Error: could not open connection to display"); return false; } int[] majorMinor = new int[2]; if (!egl.eglInitialize(eglDisplay, majorMinor)) { System.out.println("Error: could not initialize OpenGL ES display"); return false; } System.out.println("EGL version: " + majorMinor[0] + "." + majorMinor[1]); int[] numConfigs = new int[1]; egl.eglGetConfigs(eglDisplay, null, 0, numConfigs); if (numConfigs[0] < 1) { System.out.println("Error: no configurations found"); return false; } int configAttributes[] = { EGL11.EGL_RED_SIZE, 5, EGL11.EGL_GREEN_SIZE, 6, EGL11.EGL_BLUE_SIZE, 5, EGL11.EGL_ALPHA_SIZE, 0, EGL11.EGL_DEPTH_SIZE, 16, // EGL11.EGL_STENCIL_SIZE, EGL11.EGL_DONT_CARE, // don't care about stencils EGL11.EGL_SURFACE_TYPE, EGL11.EGL_WINDOW_BIT, EGL11.EGL_NONE }; EGLConfig eglConfigs[] = new EGLConfig[numConfigs[0]]; if (!egl.eglChooseConfig(eglDisplay, configAttributes, eglConfigs, eglConfigs.length, numConfigs)) { System.out.println("Error: could not find a suitable configuration"); return false; } EGLConfig eglConfig = eglConfigs[0]; eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL11.EGL_NO_CONTEXT, null); if (eglContext == null) { System.out.println("Error: could not create a rendering state"); return false; } g2d = getGraphics(); gl = (GL11) eglContext.getGL(); if (gl == null) { System.out.println("Error: could not create a 3D graphics context"); return false; } eglWinSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, g2d, null); if (eglWinSurface == null) { System.out.println("Error: could not create a drawing surface window"); return false; } if (!egl.eglMakeCurrent(eglDisplay, eglWinSurface, eglWinSurface, eglContext)) { System.out.println("Error: could not make the context current"); return false; } return true; } |
3D 장면을 그리거나 JSR-239를 사용해야 하는 텍스처 또는 기타 리소스를 초기화하려면 먼저 이 메소드를 호출해야 합니다. 3D 장면을 그리려면 이터레이션 사이에 지연이 전혀 없고 최대한 빨리 실행되는 루프로 새 스레드를 만들어야 합니다. 이렇게 해야 초당 프레임 수를 최대한으로 늘릴 수 있습니다. 이 게임 루프가 실제의 게임 로직을 관리합니다(아래 참조). 일반적으로, OpenGL을 사용하여 3D 장면을 디자인할 때는 모든 3D 개체를 장면 그래프로 구조화합니다. 이로써 몇 개의 작은 개체를 모은 복합 개체를 만들고 이것을 한꺼번에 이동할 수 있습니다. 아래 코드는 GameCanvas
를 캡처하고 장면 그리기를 시작하도록 JSR-239에 지시하는 방법을 보여 줍니다. 이 예제에서는 자바를 사용하여 JSR-239를 렌더링하는 것과 관련이 있는 중요 부분만 남기고 코드의 일부분을 삭제했습니다.
private void renderScene() { egl.eglWaitNative(EGL11.EGL_CORE_NATIVE_ENGINE, g2d); // clear the scene, reset the transformations and set the lighting ... // Start rendering the scene graph root.renderNode(gl); // Tell the device we have finished drawing 3D for this frame and release the device gl.glFinish(); egl.eglWaitGL(); // Draw ordinary 2D graphics using the Graphics2D object for the GameCanvas g2d.setColor(0x00aa00); ... // Flush the graphics of the GameCanvas to display the new frame flushGraphics(); } |
장치 그래픽을 캡처하기 위해 각 렌더링을 전달하기 전에 eglWaitNative
메소드를 호출해야 합니다. 또한 이 과정을 마친 뒤 장치를 해제하고 마지막으로 flushGraphics
를 호출하여 새 프레임을 표시해야 합니다. eglWaitNative
및 eglWaitGL
을 사용하면 보통의 자바 ME 2D 그래픽과 JSR-239를 결합할 수 있습니다. JSR-239를 모바일 3D API(JSR-184) 또는 확장형 벡터 그래픽(JSR-226) 등 다른 그래픽 API와 결합하는 것도 가능합니다.
모바일 센서 API(JSR-256)는 자바 ME 장치에 통합되어 있는 여러 가지 센서와의 통신을 주로 다루는 일반 사양입니다. 온도계, 광 센서 또는 이 예제의 가속도계 등 여러 종류의 센서가 있을 수 있습니다. 가속도계는 가속도를 측정하며, 이 게임에서 중요한 가속도는 다른 무엇보다도 중력 가속도입니다. 중력은 일정하게 유지되기 때문에 전화기의 경사도를 판별하는 데 사용됩니다.
소니 에릭슨 w910i에는 3축 가속도계가 내장되어 있습니다. 즉, 세 차원 모두에서 가속도를 측정할 수 있습니다. 전화기를 평평하게 놓으면 가속도계는 Z축(상하) 방향의 가속도가 약 1000이라고 알려 줍니다(이 값은 1G에 해당). 전화기는 정지 상태이고 중력은 아래 방향으로만 작용하므로 X축 및 Y축 방향의 가속도(측면 가속도)는 0에 가까울 것입니다. 전화기를 뒤집어 화면이 아래쪽을 향하도록 하면 가속도계는 Z축 값 -1000을 가리킵니다. 옆으로 세워 놓을 경우, 세운 방향에 따라 X축 또는 Y축 값이 1000 또는 -1000이 됩니다.
전화기를 X축을 따라 45도 각도로 놓으면 Z축 값은 ñ707, Y축 값은 ±707이 됩니다. 두 축 모두에 최대의 중력이 발휘될 수는 없기 때문입니다(특정 각도에 대한 각 축의 값은 사인 및 코사인 함수를 사용하여 손쉽게 계산 가능). 언제든지 가속도계에서 판독한 X축 및 Y축 값으로 전화기의 자세를 판별하고, 이 값을 토대로 우주선을 움직여 다가오는 소행성을 피할 수 있습니다.
JSR-256용 API는 매우 간단합니다. 가속도계에 액세스하려면 일반 연결 프레임워크를 사용하여 주어진 장치 URL에 대한 연결을 개시합니다. 또한 SensorManager
를 사용하여 연결을 위한 URL 등 해당 장치에서 사용 가능한 모든 센서에 관한 정보를 검색할 수도 있습니다. 아래 예제에서는 URL에 하드 코드 문자열을 사용했습니다. 그러나 보다 일반적인 방법으로 구현하고자 하는 경우 SensorManager
는 사용 가능한 가속도계를 찾아 그것을 사용하거나, 사용자에게 경고를 표시합니다.
SensorConnection
으로 구현된 센서를 참조해야 하는 경우, 장치 폴링을 통해 센서 데이터를 검색하거나 DataListener
를 등록하여 자동으로 업데이트를 수신하는 두 가지 방법 중에서 선택할 수 있습니다. 제 생각에는 이벤트 위주로 접근할 때 보다 우수한 설계가 가능하며, 이 게임에서도 그 메소드를 사용했습니다. DataListener
를 SensorConnection
에 등록할 때는 버퍼 크기도 지정해야 합니다. 이 버퍼는 DataListener
로 이벤트를 보내려면 얼마나 많은 데이터 값을 수집해야 하는지를 나타냅니다. 버퍼 크기를 적게 설정하면 업데이트 속도가 빨라집니다. 반면에 버퍼 크기가 크면 가속도계에서 평균을 계산하여 일탈 값을 보다 빨리 제거할 수 있습니다.
아래 코드는 게임용으로 알맞은 어댑터를 구현한 것입니다. 이 어댑터는 가속도계에 DataListener
로 자동 등록되며 값을 세 가지 다른 정수로 저장합니다. 또한 URL도 지속적으로 센서에 저장합니다. 이 URL은 소니 에릭슨 전화기의 가속도계에만 해당되는 URL입니다. 보다 포괄적으로 만들려면 일단 모든 센서에 쿼리한 다음 가속도계 센서에 연결하도록 해야 합니다(또는 사용자에게 오류 메시지 표시). 수집된 값의 평균을 계산하여 일탈 값을 제거하려면 getAverage
메소드를 사용합니다.
public class SensorAdapter implements DataListener { private int x, y, z; private static final String sensorURL = "sensor:acceleration;contextType=user;model=ST_LIS302DL;location=inside"; public SensorAdapter() { x = 0; y = 0; z = 0; try { SensorConnection sensor = (SensorConnection) Connector.open(sensorURL); sensor.setDataListener(this, 10); } catch (IOException e) { e.printStackTrace(); } } public int[] getXYZ() { synchronized(this) { return new int[] {x, y, z}; } } public void dataReceived(SensorConnection sensor, Data[] data, boolean isDataLost) { synchronized(this) { for(int i=0; i |
이 게임은 물론 다른 많은 게임에서도 핵심이 되는 것은 바로 3D 장면을 업데이트하고, 충돌을 검사하고, 게임 내 모든 개체의 상태를 추적하는 게임 루프입니다. 게임은 이 루프에 따라 진행됩니다. 여기서 사용하는 게임 루프는 사용자가 게임을 종료할 때까지 실행을 계속하는 스레드로 구성되어 있습니다. 이 게임은 매우 단순하며 기본적으로 다음과 같은 순서로 진행됩니다.
게임 루프가 위 단계를 모두 마친 뒤에는 Thread.sleep
을 호출하여 다시 시작하기 전에 잠시 지연을 둡니다. 이 지연 시간에 따라 게임의 실행 속도가 달라집니다. 지연 값을 높게 설정하면 게임은 느리게 흘러가고, 값을 낮게 하면 플레이가 불가능할 만큼 빨라집니다.
우주선을 앞으로 움직일 때 실제로는, 즉 3D 용어로 표현하자면, 모든 소행성을 움직이는 것이고 우주선은 제자리에 머무릅니다. 이 3D 장면에서는 축마다 하나씩 세 개의 부동 값을 단순 환산하여 모든 개체의 위치를 결정하므로, 이 값이 너무 커지면 정밀도가 저하될 우려가 있음을 명심해야 합니다. 그러면 3D 개체는 왜곡되고 화면이 아주 이상하게 보입니다. 이를 방지하기 위해서는 "카메라"와 우주선이 원점 근처에 위치하도록 하고, 앞으로 움직일 때는 이 3D 장면의 다른 모든 개체에 음수 환산 값을 적용해야 합니다. 모바일 장치의 간단한 3D 게임이든 PC에서 실행하는 대규모 MMO 게임이든 간에 대부분의 3D 게임은 실제로 이런 방식으로 구현됩니다.
얼마나 우수하고 정밀한 감지가 요구되는가에 따라 충돌 감지는 매우 어려워질 수 있습니다. 이 게임에서는 가장 간단한 형태의 충돌 감지 기능 중 하나인 축 정렬 바운딩 상자를 사용했습니다. 즉, 각 개체를 둘러싸는 가상의 상자를 만들고 이 상자들이 교차하는지 확인하여 두 개체 간의 충돌 여부를 테스트하는 것입니다.
지금까지 내장된 가속도계로 컨트롤하는 간단한 자바 ME용 3D 게임을 만들어 보았습니다. 게임의 소스 코드를 다운로드하여 원하는 대로 마음껏 변경해 보십시오. 그리고 JSR-239 또는 JSR-256을 사용하여 게임이나 애플리케이션을 직접 제작하기 위한 아이디어를 얻으시기 바랍니다.
이 게임에서는 아주 기본적인 방식으로 가속도계를 사용했습니다. Z축 값에는 전혀 신경쓰지 않았습니다. 제가 아직 상상해 본 적이 없는 모바일 센서 API의 다른 용도가 분명히 더 있을 것이고, 그 방법은 이 기사에서 설명한 것보다 훨씬 흥미로울 것입니다. 최근 저는 블루투스를 통해 가속도계의 데이터를 컴퓨터로 보내면 전화기를 PC의 게임 컨트롤러로 사용할 수 있지 않을까 하는 생각을 하고 있습니다. 테니스 게임을 구현할 때 유용하겠지요. 이것은 또 제가 JOGL을 사용하여 보다 고급 3D 작업을 해봐야 하는 이유이기도 합니다. 이 모바일 센서 API에는 게임 외에도 엄청난 가능성이 더 숨겨져 있다고 생각합니다. 프레젠테이션용 무선 마우스를 만들면 어떨까 하는 생각이 갑자기 떠오르는군요.
여러분이 이 기사에서 멋진 아이디어를 얻으셨으면 좋겠습니다. 그리고 OpenGL ES나 모바일 센서 API를 사용하여 여러분만의 근사한 게임 또는 애플리케이션을 만드시길 바랍니다. 저에게 질문이 있거나 아이디어를 나누고 싶을 때는 언제든지 연락 주십시오.
Erik Hellman은 10년이 조금 넘게 자바를 다루어 왔습니다. 에릭슨의 대규모 텔레콤 시스템에서 자바 EE로 작업한 뒤 자바 컨설턴트가 되었으며, 수많은 회의에서 광범위한 여러 주제에 관해 프레젠테이션을 한 바 있습니다. 현재 스웨덴 룬드에서 소니 에릭슨의 자바 SDK 개발 팀을 이끌고 있습니다.
이 글의 영문 원본은
New Gaming Experiences with OpenGL ES and the Mobile Sensor API
에서 보실 수 있습니다.