카테고리 없음

안드로이드 OpenGL ES 예제, 변환(Transformations)

Soul-Learner 2012. 5. 15. 14:01

변환 행렬을 이용하여 3개의 사각형이 공전과 자전을 반복하는 예

원문참조 http://blog.jayway.com/2010/01/01/opengl-es-tutorial-for-android-%E2%80%93-part-iii-%E2%80%93-transformations/

모델에 적용되는 변환은 행렬을 이용하며 모델에 한번 적용한 변환행렬은 프레임이 경과해도 유지되므로 다시 변환을 적용하면 기존 변환행렬 위에 변환이 추가되는 특징이 있다.

이동 후 회전, 회전 후 이동은 그 결과가 다른데 그 이유는 모델에 적용되는 변환은 해당 모델의 좌표계(로컬 좌표계)에 적용되며 월드 좌표계에 적용되는 것이 아니기 때문이다.

이동 및 회전변환

좌표계 내에서의 이동과 회전에 대한 변환이므로 좌표계의 구조와 회전방향 등에 대해서 알아야 한다.

OpenGL ES의 좌표계

왼손의 엄지, 검지 중지를 각각 90도가 되도록 편 다음, 중지로 X축방향과 일치시키고, 검지로 Y축방향과 일치시킬 때 엄지 손가락은 Z축을 가리킨다. 각 손가락이 가리키는 방향으로 각 축의 거리를 나타내는 수가 증가한다.

OpenGL ES의 회전방향

X, Y, Z 각 축의 선을 오른 손으로 잡고 엄지를 축방향과 일치시키면 나머지 손가락의 방향은 각축에 대한 회전방향을 가리킨다.

대부분의 그래픽 라이브러리에서는 회전각의 단위가 Radians(호도법)이지만 OpenGL ES는 Degrees(일반각)를 이용한다

이동변환 함수

public abstract void glTranslatef (float x, float y, float z) //OpenGL docs.

각 축의 이동량은 현재 모델의 좌표에 단순하게 덧셈이 되어 새로운 위치의 좌표로 변환된다.

이동변환의 예

// Translates 4 units into the screen.

gl.glTranslatef(0, 0, -4); OpenGL docs.


회전변환 함수

public abstract void glRotatef(float angle, float x, float y, float z)//OpenGL docs.

첫번째 파라미터 angle은 회전각도를 의미하고, 파라미터 x, y, z는 회전축을 나타내는 벡터이다.


회전변환의 예

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

위의 회전변환은 X축을 기준으로 90도 회전변환하는 예이다. 다시 원래의 상태로 회복하려면 다음과 같은 방법을 사용한다.

gl.glRotatef(90f, -1.0f, 0.0f, 0.0f); 혹은 gl.glRotatef(-90f, 1.0f, 0.0f, 0.0f);

다음과 같이 여러번 회전한 경우에는 위의 방법을 사용해서는 원래의 상태로 회복되지 않는다.

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.

즉, 위와 같이 여러번 회전한 후에는 다음과 같은 방법으로는 회전하기 전의 원래의 상태를 회복할 수 없다.

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.

여러번 회전한 후에 원래의 상태를 회복하려면 회전변환을 적용했던 축의 순서를 반대로 적용하면 된다. 즉, 회전축 벡터를 반대로 적용할 뿐만 아니라, 회전 변환했던 축의 순서도 반대로 적용해야 한다. 아래와 같이 변환의 순서도 중요하다는 것을 알 수 있다

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

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

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


이동과 회전을 동시에 한개의 모델에 적용하는 경우

이동변환과 회전변환은 모두 모델의 좌표계(로컬 좌표계)에 적용되는 변환이므로 변환을 적용할 때의 순서에 따라 다른 결과가 나타난다.

즉, 회전변환을 적용한 모델의 좌표계는 각 축의 방향이 변경된 상태이므로 이 상태 위에 이동변환을 적용하면 변환된 축의 방향을 따라 이동변환이 적용된다. 그러므로 이동후 회전의 결과와, 회전후 이동의 결과는 다르게  된다


이동, 회전, 크기변환을 적용하기 전의 조건

프레임을 화면에 출력하기 전에 변환을 적용하고 화면을 그리면 해당 변환 내용이 출력되는 그림에 적용이되어 나타난다. 매 프레임을 그리기 전에 변환을 적용하면 앞서 적용된 변환 행렬 위에 현재 적용하는 변환이 추가로 적용된다 

기존 변환을 기반으로 하지  않고 새로운 변환을 적용하려면 glLoadIdentity() 를 사용하여 기존 변환행렬 대신 단위행렬(Identity Matrix)로 대치하고 변환을 적용해야 한다

public abstract void glLoadIdentity() // OpenGL docs.

단위행렬(Identity Matrix)은 다음과 같은 구성으로 되어 있다.

1 0 0 0

0 1 0 0

0 0 1 0

0 0 0 1


glPushMatrix(), glPopMatrix()

위의 상황과는 반대로 특정 시기의 변환행렬을 차후에 다시 사용하려고 하는 경우에는 그 행렬을 스택에 잠시 저장해 두고 행렬에 변환을 적용하면 스택에 저장해둔 행렬에는 변환이 적용되지 않기 때문에 다시 스택에서 꺼내어 사용할 수 있다.

public abstract void glPushMatrix() // OpenGL docs.

public abstract void glPopMatrix() // OpenGL docs.


크기변환 함수

public abstract void glScalef (float x, float y, float z) // OpenGL docs.

크기변환은 각 축별로 구분하여 적용할 수 있다. 각 축의 좌표에 해당 파라미터를 곱해서 새로운 좌표를 산출한다.

크기변환은 지정한 규모만큼 모델의 좌표를 원점으로 가까이 이동하는 결과를 가져온다. 그러므로 원점에 있는 모델의 크기를 변환하면 모델의 좌표가 원점을 유지하고 있지만 원점 이외의 위치에 모델이 있을 경우에는 축소(축대)변환하면 모델의 크기가 변경될 뿐만 아니라 모델의 좌표도 원점을 향하여 이동한 효과로 나타난다. 그러므로 이동 후 크기 변환과 크기 변환 후 이동변환을 잘 고려하여 설정할 필요가 있다.


변환적용 순서

다른 변환을 여러번 설정할 필요도 있는데, 예를 들어, 이동한 후에 회전하거나 회전한 후에 이동하려는 경우이다. 변환은 내부에서 행렬의 곱셈으로 실행되고 행렬의 곱셈은 교환법칙이 성립되지 않으므로 이동 후 회전과 회전 후 이동은 결과가 다르게 된다.

이동 후 축소(축대)와 축소(축대) 후 이동변환도 마찬가지로 서로 다른 결과로 나타난다. 그 이유는 위에서 언급하였다.


Activity

package gl.test3;


import android.app.Activity;

import android.opengl.GLSurfaceView;

import android.os.Bundle;

import android.view.Window;

import android.view.WindowManager;


public class TutorialPartIII extends Activity {

    @Override

    public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

   

    this.requestWindowFeature(Window.FEATURE_NO_TITLE); 

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

            WindowManager.LayoutParams.FLAG_FULLSCREEN);

        

  GLSurfaceView view = new GLSurfaceView(this);

    view.setRenderer(new OpenGLRenderer());

    setContentView(view);

    }

}


Renderer ( 하나의 사각형이 시계 방향으로 회전(화면 중심에서 자전)하는 예)

package gl.test3;


import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;


import android.opengl.GLU;

import android.opengl.GLSurfaceView.Renderer;


public class OpenGLRenderer implements Renderer {

private Square square;

private float angle = 0;

public OpenGLRenderer() {

square = new Square();

}


public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// Set the background color to black ( rgba ).

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

// Enable Smooth Shading, default not really needed.

gl.glShadeModel(GL10.GL_SMOOTH);

// Depth buffer setup.

gl.glClearDepthf(1.0f);

// Enables depth testing.

gl.glEnable(GL10.GL_DEPTH_TEST);

// The type of depth testing to do.

gl.glDepthFunc(GL10.GL_LEQUAL);

// Really nice perspective calculations.

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

}


public void onDrawFrame(GL10 gl) {

// Clears the screen and depth buffer.

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

// Replace the current matrix with the identity matrix

gl.glLoadIdentity();

// Translates 10 units into the screen.

gl.glTranslatef(0, 0, -10);             // 앞으로(z축)10만큼 이동변환

// Rotate square clockwise.

gl.glRotatef(-angle, 0, 0, 1);           // 이동(z축 -10), z축 시계방향 회전

// Draw square A.

square.draw(gl);                        // 이동(z축 -10), z축 회전변환된 큰 사각형

// Increse the angle.

angle++;

}


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

// Sets the current view port to the new size.

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

// Select the projection matrix

gl.glMatrixMode(GL10.GL_PROJECTION);

// Reset the projection matrix

gl.glLoadIdentity();

// Calculate the aspect ratio of the window

GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f,

100.0f);

// Select the modelview matrix

gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reset the modelview matrix

gl.glLoadIdentity();

}

}


Renderer ( 하나의 사각형이 화면 중심을 기준으로 공전하는 예, 공전주기와 자전주기가 같은 경우)

package gl.test3;


import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;


import android.opengl.GLU;

import android.opengl.GLSurfaceView.Renderer;


public class OpenGLRenderer implements Renderer {

private Square square;

private float angle = 0;

public OpenGLRenderer() {

square = new Square();

}


public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// Set the background color to black ( rgba ).

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

// Enable Smooth Shading, default not really needed.

gl.glShadeModel(GL10.GL_SMOOTH);

// Depth buffer setup.

gl.glClearDepthf(1.0f);

// Enables depth testing.

gl.glEnable(GL10.GL_DEPTH_TEST);

// The type of depth testing to do.

gl.glDepthFunc(GL10.GL_LEQUAL);

// Really nice perspective calculations.

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

}


public void onDrawFrame(GL10 gl) {

// Clears the screen and depth buffer.

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

// Replace the current matrix with the identity matrix

gl.glLoadIdentity();

// Translates 10 units into the screen.

gl.glTranslatef(0, 0, -30);             // 앞으로(z축)30만큼 이동변환

// Rotate square A counter-clockwise.

gl.glRotatef(angle, 0, 0, 1);           // 이동(z축 -10), z축회전

gl.glTranslatef(5f, 0f, 0f);              // 회전 후 변환된 X축을 따라 5만큼 이동변환

// Draw square A.

square.draw(gl);                        // 이동(z축 -10), z축 회전변환된 큰 사각형

// Increse the angle.

angle+=5;

}


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

// Sets the current view port to the new size.

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

// Select the projection matrix

gl.glMatrixMode(GL10.GL_PROJECTION);

// Reset the projection matrix

gl.glLoadIdentity();

// Calculate the aspect ratio of the window

GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f);

// Select the modelview matrix

gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reset the modelview matrix

gl.glLoadIdentity();

}

}


Renderer(화면을 중심으로 공전을 하면서 자전하는 사각형, 공전 속도보다 자전속도가 큰 경우)

package gl.test3;


import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;


import android.opengl.GLU;

import android.opengl.GLSurfaceView.Renderer;


public class OpenGLRenderer implements Renderer {

private Square square;

private float angle = 0;

public OpenGLRenderer() {

square = new Square();

}


public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// Set the background color to black ( rgba ).

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

// Enable Smooth Shading, default not really needed.

gl.glShadeModel(GL10.GL_SMOOTH);

// Depth buffer setup.

gl.glClearDepthf(1.0f);

// Enables depth testing.

gl.glEnable(GL10.GL_DEPTH_TEST);

// The type of depth testing to do.

gl.glDepthFunc(GL10.GL_LEQUAL);

// Really nice perspective calculations.

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

}


public void onDrawFrame(GL10 gl) {

// Clears the screen and depth buffer.

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

// Replace the current matrix with the identity matrix

gl.glLoadIdentity();

// Translates 10 units into the screen.

gl.glTranslatef(0, 0, -30);             // 앞으로(z축)30만큼 이동변환

// Rotate square A counter-clockwise.

gl.glRotatef(angle, 0, 0, 1);           // 이동(z축 -10), z축회전, 화면을 중심으로 공전하는 효과

gl.glTranslatef(5f, 0f, 0f);              // 회전 후 변환된 X축을 따라 5만큼 이동변환

                gl.glRotatef(angle*3, 0, 0, 1);        // 고속으로 자전하는 효과

// Draw square A.

square.draw(gl);                        // 이동(z축 -10), z축 회전변환된 큰 사각형

// Increse the angle.

angle+=5;

}


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

// Sets the current view port to the new size.

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

// Select the projection matrix

gl.glMatrixMode(GL10.GL_PROJECTION);

// Reset the projection matrix

gl.glLoadIdentity();

// Calculate the aspect ratio of the window

GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f);

// Select the modelview matrix

gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reset the modelview matrix

gl.glLoadIdentity();

}

}


Renderer (공전 중인 다른 사각형을 작은 사각형이 공전하는 예 )

package gl.test3;


import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;


import android.opengl.GLU;

import android.opengl.GLSurfaceView.Renderer;


public class OpenGLRenderer implements Renderer {

private Square square;

private float angle = 0;

public OpenGLRenderer() {

square = new Square();

}


public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// Set the background color to black ( rgba ).

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

// Enable Smooth Shading, default not really needed.

gl.glShadeModel(GL10.GL_SMOOTH);

// Depth buffer setup.

gl.glClearDepthf(1.0f);

// Enables depth testing.

gl.glEnable(GL10.GL_DEPTH_TEST);

// The type of depth testing to do.

gl.glDepthFunc(GL10.GL_LEQUAL);

// Really nice perspective calculations.

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

}


public void onDrawFrame(GL10 gl) {

// Clears the screen and depth buffer.

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

// Replace the current matrix with the identity matrix

gl.glLoadIdentity();

// Translates 10 units into the screen.

gl.glTranslatef(0, 0, -30);             // 앞으로(z축)30만큼 이동변환

// Rotate square A counter-clockwise.

gl.glRotatef(angle, 0, 0, 1);           // 이동(z축 -10), z축회전, 화면을 중심으로 공전하는 효과

gl.glTranslatef(5f, 0f, 0f);              // 회전 후 변환된 X축을 따라 5만큼 이동변환

                gl.glPushMatrix();

                gl.glRotatef(angle*3, 0, 0, 1);        // 고속으로 자전하는 효과

// Draw square A.

square.draw(gl);                        // 이동(z축 -10), z축 회전변환된 큰 사각형

               // 앞서 그린 큰 사각형의 공전괘도를 따라서 큰 사각형을 공전하는 작은 사각형을 위한 변환 설정

                gl.glPopMatrix();

gl.glRotatef(-angle*3, 0, 0, 1);

gl.glTranslatef(2f, 0f, 0f);

gl.glScalef(.5f, .5f, .5f);

square.draw(gl);

// Increse the angle.

angle+=5;

}


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

// Sets the current view port to the new size.

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

// Select the projection matrix

gl.glMatrixMode(GL10.GL_PROJECTION);

// Reset the projection matrix

gl.glLoadIdentity();

// Calculate the aspect ratio of the window

GLU.gluPerspective(gl, 45.0f, (float) width / (float) height, 0.1f, 100.0f);

// Select the modelview matrix

gl.glMatrixMode(GL10.GL_MODELVIEW);

// Reset the modelview matrix

gl.glLoadIdentity();

}

}

Square.java

package gl.test3;


import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.nio.FloatBuffer;

import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

public class Square {

private float vertices[] = {

     -1.0f,  1.0f, 0.0f,  // 0, Top Left

     -1.0f, -1.0f, 0.0f,  // 1, Bottom Left

      1.0f, -1.0f, 0.0f,  // 2, Bottom Right

      1.0f,  1.0f, 0.0f,  // 3, Top Right

};

// The order we like to connect them.

private short[] indices = { 0, 1, 2, 0, 2, 3 };

// Our vertex buffer.

private FloatBuffer vertexBuffer;


// Our index buffer.

private ShortBuffer indexBuffer;

public Square() {

// a float is 4 bytes, therefore we multiply the number if 

// vertices with 4.

ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);

vbb.order(ByteOrder.nativeOrder());

vertexBuffer = vbb.asFloatBuffer();

vertexBuffer.put(vertices);

vertexBuffer.position(0);

// short is 2 bytes, therefore we multiply the number if 

// vertices with 2.

ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);

ibb.order(ByteOrder.nativeOrder());

indexBuffer = ibb.asShortBuffer();

indexBuffer.put(indices);

indexBuffer.position(0);

}


public void draw(GL10 gl) {

// Counter-clockwise winding.

gl.glFrontFace(GL10.GL_CCW);

// Enable face culling.

gl.glEnable(GL10.GL_CULL_FACE);

// What faces to remove with the face culling.

gl.glCullFace(GL10.GL_BACK);

// Enabled the vertices buffer for writing and to be used during 

// rendering.

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

// Specifies the location and data format of an array of vertex

// coordinates to use when rendering.

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

gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, 

GL10.GL_UNSIGNED_SHORT, indexBuffer);

// Disable the vertices buffer.

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

// Disable face culling.

gl.glDisable(GL10.GL_CULL_FACE);

}

}