[2011.03.11] OpenGL을 이용한 3D 그래픽 프로그래밍 - Triangle, AnimationTriangle, Retangle
안드로이드 2 마스터 북 Pro Android 2 교재를 보면서 따라해보았다.
고스톱 게임 소스를 보는데...GLSurfaceView가 나왔고...인터넷을 통해 찾아보다가 OpenGL임을 알게 되었고....교재를 보았고...
먼말인지 이해를 못하겠고...무작정 따라했다^^
main.xml 소스보기 - 화면의 첫 페이지를 구성한다. "간단한 메인 액티비티"라는 문구 출력을 한다.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="간단한 메인 액티비티" /> </LinearLayout> |
menu.xml 소스보기 - Main.java에서 사용하게될 메뉴의 모양을 정의하는 XML파일이다.
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:id="@+id/menuGroup_Main"> <item android:id="@+id/mid_OpenGL_SimpleTriangle" android:title="SimpleTriangle" /> <item android:id="@+id/mid_OpenGL_AnimatedTriangle" android:title="AnimatedTriangle" /> <item android:id="@+id/mid_rectangle" android:title="Rectangle" /> <item android:id="@+id/mid_square_polygon" android:title="Square Polygon" /> <item android:id="@+id/mid_polygon" android:title="Polygon" /> <item android:id="@+id/mid_textured_square" android:title="Textured Square" /> <item android:id="@+id/mid_textured_polygon" android:title="Textured Polygon" /> <item android:id="@+id/mid_OpenGL_Current" android:title="Current" /> <item android:id="@+id/menu_clear" android:title="Menu Clear" /> </group> </menu>
|
Main.java 소스보기 - main.xml과 연결된 java 소스로 메뉴버튼 동작 과정을 코딩하였다. 메뉴 버튼을 눌러서 어떤 도형을 화면에 표시할지 선택 할 수 있다.
package lee.hyeontae.OpenGLTestHarnessActivity;
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.Button;
public class Main extends Activity { Button btn;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); }
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.layout.menu, menu);
return true; }
@Override public boolean onOptionsItemSelected(MenuItem item) { this.invokeSimpleTriangle(item.getItemId()); return true; }
private void invokeSimpleTriangle(int mid) { Intent intent = new Intent(this,OpenGLTestHarnessActivity.class); intent.putExtra("com.ai.menuid", mid); startActivity(intent); } }
|
OpenGLTestHarnessActivity.java 소스보기 - Main.java로 부터 Intent를 받아 id체크를 하고, 해당 id에 대한 세팅이 구현되어있다. 해당 ID에 대한 Render를 세팅해 주고, 그 Render로 구성된 GLSurfaceView를 화면에 출력한다. 먼저 GLSurfaceView란 http://blog.naver.com/yhcyksyh/40110354480 여기를 참고하자^^ 간단히 Android에서 OpenGL 을 사용할 수 있도록 Provide 해주는 역활이라고나 할까?? 그럼....Render는? Render의 사전적의미는 (어떤상태가 되도록) 만들다.... 그러니까 거시기...지속적으로 무언가를 만드는 작업을 Renderring이라고...하자^^
package lee.hyeontae.OpenGLTestHarnessActivity;
import lee.hyeontae.OpenGLTestHarnessActivity.AnimatedSimpleTriangle.AnimatedSimpleTriangleRenderer; import lee.hyeontae.OpenGLTestHarnessActivity.SimpleRectangle.SimpleRectangleRenderer; import lee.hyeontae.OpenGLTestHarnessActivity.SimpleTriangle.SimpleTriangleRenderer; import android.app.Activity; import android.content.Intent; import android.opengl.GLSurfaceView; import android.os.Bundle;
public class OpenGLTestHarnessActivity extends Activity { private GLSurfaceView mTestHarness; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mTestHarness = new GLSurfaceView(this); // GLSurfaceView Instance화 mTestHarness.setEGLConfigChooser(false); // 특수 EGL 설정 선택 메서드가 필요없음을 전달.
Intent intent = getIntent(); int mid = intent.getIntExtra("com.ai.menuid", R.id.mid_OpenGL_Current); if(mid==R.id.mid_OpenGL_Current) { // mTestHarness.setRenderer(new TexturedPolygonRenderer(this)); /** * 애니메이션의 사용 여부 결정 */
// mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; }
if(mid==R.id.mid_OpenGL_SimpleTriangle) { mTestHarness.setRenderer(new SimpleTriangleRenderer(this));
/** * 애니메이션의 사용 여부 결정 */ mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; }
if(mid==R.id.mid_OpenGL_AnimatedTriangle) { mTestHarness.setRenderer(new AnimatedSimpleTriangleRenderer(this));
/** * 애니메이션의 사용 여부 결정 */ // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; }
if(mid==R.id.mid_rectangle) { mTestHarness.setRenderer(new SimpleRectangleRenderer(this));
/** * 애니메이션의 사용 여부 결정 */ mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; } /* if(mid==R.id.mid_square_polygon) { mTestHarness.setRenderer(new SquareRenderer(this)); mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; }
if(mid==R.id.mid_polygon) { mTestHarness.setRenderer(new PolygonRenderer(this)); // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; }
if(mid==R.id.mid_textured_square) { mTestHarness.setRenderer(new TexturedSquareRenderer(this)); mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출
setContentView(mTestHarness); return; }
mTestHarness.setRenderer(new TexturedPolygonRenderer(this)); // mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 그리기코드는 단 1회 호출 mTestHarness.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // 그리기 코드가 반복 호출 */ setContentView(mTestHarness); return; } @Override protected void onPause() { super.onPause(); mTestHarness.onPause(); } @Override protected void onResume() { super.onResume(); mTestHarness.onResume(); } }
|
AbstractRenderer.java 소스보기 - 이 클래스는 주고 카메라와 관련된 정보를 세팅해주는 역활을 한다. 여기서 카메라는 찰칵찰칵~말고~~~어떤...그...보는 시점. 그니까 카메라라고 말을해도 되지만... 내가 생각하는 그 Device Camera는 아니고 추상적 카메라라고 생각하자. 카메라의 위치, 방향, 방위나 각도를 설정하고 창 높이나 폭의 변화 등 평면이 변경되었을때의 설정을 한다. 물론 Surface Create 될 때의 설정도 이 클래스에서 한다.
package lee.hyeontae.OpenGLTestHarnessActivity;
import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLU; import android.opengl.GLSurfaceView.Renderer;
/** * abstract : 부모 클래스에서 추상메소드 + 일반메소드를 가지고 있을 경우 사용한다. */ public abstract class AbstractRenderer implements Renderer {
/** * 주요 그리기 기능은 onDrawFrame()에 들어 있다. */ @Override public void onDrawFrame(GL10 gl) { gl.glDisable(GL10.GL_DITHER); gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity();
/** * (0,0,5) : 시점 (카메라 위치) eye point * (0,0,5)면 z축 으로 5단위 떨어진 거리에서 기점을 향해 보게 된다. * * (0,0,0) : 관측점 (카메라 방향) look-at point * 카메라를 5단위 만큼 떨어진 거리에 위치 시켰다면 앞,뒤 어느 방향을 찍을지 결정해야한다. * * (0,1,0) : 업 벡터(카메라 방위나 각도) up vector * (0,1,0)이면 y축 으로 1단위 떨어진 수직 아래 위치에서 위를 향하게 된다. * (0,-1,0)이면 y축 으로 1단위 떨어진 수직 위 위치에서 아래를 향하게 된다. * * 아래 처럼 변경하면 삼각형이 거꾸로 출력된다. * GLU.gluLookAt(gl, 0, 0, 5, 0f, 0f, 0f, 0f, 1.0f, 0.0f); */ GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); draw(gl); } protected abstract void draw(GL10 gl);
/** * 창 높이나 폭의 변화 등 평면이 변경되면 onSurfaceChanged() 호출. * 카메라와 조망 입체 설정을 하면 된다. */ @Override public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height); // 화면 크기나 카메라 필름 크기 조절 float ratio = (float) width / height; // 종횡비 계산 gl.glMatrixMode(GL10.GL_PROJECTION); // 원근 투영하도록 지정 gl.glLoadIdentity();
/** * [조망 입체나 줌 조절] * 조망 박스 좌변, 조망 박스 우변, 조망 박스 윗변, 조망 박스 밑변, 카메라와 근점 사이 거리, 카메라와 원점 사이 거리 * * 아래처럼 조절하면 삼각형 크기는 그대로지만 조망박스가 4배로 커지면서 삼각형이 작아진다. * gl.glFrustumf(-ratio*4, ratio*4, -1*4, 1*4, 3, 7); */ gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
}
/** * 새 평면이 생성될 때마다 onSurfaceCreated()가 호출됨. */ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glDisable(GL10.GL_DITHER); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glClearColor(.5f, .5f, .5f, 1); gl.glShadeModel(GL10.GL_SMOOTH); gl.glEnable(GL10.GL_DEPTH_TEST); }
}
|
SimpleTriangleRenderer.java 소스보기 - 캬~이름만 봐도 알아야지^^ Simple Triangle을 그려주는 클래스이다. 이 클래스들은 AbstractRenderer로 부터 상속된다.
package lee.hyeontae.OpenGLTestHarnessActivity.SimpleTriangle;
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
import lee.hyeontae.OpenGLTestHarnessActivity.AbstractRenderer;
import android.content.Context;
public class SimpleTriangleRenderer extends AbstractRenderer {
// 사용할 정점 수 private final static int VERTS = 3;
// 정점 좌표들이 저장될 무형식 전용 버퍼 private FloatBuffer mFVertexBuffer;
// 정점들의 재사용에 사용되는 인덱스가 저장될 무형식 전용 버퍼 private ShortBuffer mIndexBuffer;
public SimpleTriangleRenderer(Context context) { /** * 부동소수점 버퍼 생성하기 * 하나의 삼각형은 3개의 정점이 필요하고 * 각 정점은 3개의 부동소수점으로 구성되며(축이 3개) * 각 부동소수점은 4바이트로 구성됨. */ ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4); vbb.order(ByteOrder.nativeOrder()); mFVertexBuffer = vbb.asFloatBuffer();
/** * Index값 마다 2바이트 할당 * Index Buffer는 새 정점들을 생성하는것이 아니라 그저 glVertexPointer를 통해 지정된 정점 배열 인덱스를 참조한다. */ ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2); ibb.order(ByteOrder.nativeOrder()); mIndexBuffer = ibb.asShortBuffer();
float[] coords = { -0.5f, -0.5f, 0.0f, // (x1, y1, z1) 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; for(int i=0;i<VERTS;i++) { for(int j=0;j<3;j++) { /** * coords[0],coords[1],coords[2], * coords[3],coords[4],coords[5], * coords[6],coords[7],coords[8], */ mFVertexBuffer.put(coords[i*3+j]); } }
/** * 정점들의 배열 방식을 알아내고, 그 배열을 버퍼에 할당 */ short[] myIndecesArray = {0,1,2}; for(int i=0;i<3;i++) { mIndexBuffer.put(myIndecesArray[i]); }
/** * 버퍼 위치를 맨 앞으로 지정 */ mFVertexBuffer.position(0); mIndexBuffer.position(0);
}
//재 정의 메소드 @Override protected void draw(GL10 gl) { /** * Red, Green, Blue, Alpha * 알파 50%의 빨강으로 지정함 */ gl.glColor4f(1.0f, 0, 0, 0.5f);
/** * 정점들을 지정해서 그릴땐 glVertexPointer를 사용한다. 그려질 정점 배열을 지정하는 기능을 담당한다. * 첫번째 인자는 몇 개의 축을 사용할지에 따라 2나 3만 전달해야 한다. * 두번째 인자는 좌표를 부동소수점으로 변환해야 함을 나타낸다. * 세번째 인자는 간격이라부르고 각 정점 사이의 바이트 수를 나타낸다. */ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
/** * glVertexPointer를 통해 정점들을 지정했으면, * glDrawElements를 사용하여 기초 도형들(점, 선, 삼각형)중 하나를 가지고 그 정점들을 그리도록 한다. * * 도형의 종류, 인덱스 수, 각 인텍스의 크기, 세개의 인덱스가 들어 있는 버퍼 */ gl.glDrawElements(GL10.GL_TRIANGLES, VERTS, GL10.GL_UNSIGNED_SHORT, mIndexBuffer); } }
|
SimpleRectangleRenderer.java 소스보기 - Simple Rectangle을 그려주는 클래스이다.
package lee.hyeontae.OpenGLTestHarnessActivity.SimpleRectangle;
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
import lee.hyeontae.OpenGLTestHarnessActivity.AbstractRenderer;
import android.content.Context;
public class SimpleRectangleRenderer extends AbstractRenderer {
// 사용할 정점 수 private final static int VERTS = 4;
// 정점 좌표들이 저장될 무형식 전용 버퍼 private FloatBuffer mFVertexBuffer;
// 정점들의 재사용에 사용되는 인덱스가 저장될 무형식 전용 버퍼 private ShortBuffer mIndexBuffer;
public SimpleRectangleRenderer(Context context) { /** * 부동소수점 버퍼 생성하기 * 하나의 삼각형은 3개의 정점이 필요하고 * 각 정점은 3개의 부동소수점으로 구성되며(축이 3개) * 각 부동소수점은 4바이트로 구성됨. */ ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4); vbb.order(ByteOrder.nativeOrder()); mFVertexBuffer = vbb.asFloatBuffer();
/** * Index값 마다 2바이트 할당 * Index Buffer는 새 정점들을 생성하는것이 아니라 그저 glVertexPointer를 통해 지정된 정점 배열 인덱스를 참조한다. */ ByteBuffer ibb = ByteBuffer.allocateDirect(6 * 2); ibb.order(ByteOrder.nativeOrder()); mIndexBuffer = ibb.asShortBuffer();
float[] coords = { -0.5f, -0.5f, 0, // (x1, y1, z1) 0.5f, -0.5f, 0, 0.5f, 0.5f, 0, -0.5f, 0.5f, 0, }; for(int i=0;i<VERTS;i++) { for(int j=0;j<3;j++) { /** * coords[0],coords[1],coords[2], * coords[3],coords[4],coords[5], * coords[6],coords[7],coords[8], */ mFVertexBuffer.put(coords[i*3+j]); } }
/** * 정점들의 배열 방식을 알아내고, 그 배열을 버퍼에 할당 */ short[] myIndecesArray = {0,1,2,0,2,3}; for(int i=0;i<6;i++) { mIndexBuffer.put(myIndecesArray[i]); } mFVertexBuffer.position(0); mIndexBuffer.position(0);
}
//재 정의 메소드 @Override protected void draw(GL10 gl) { /** * Red, Green, Blue, Alpha * 알파 50%의 빨강으로 지정함 */ gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
/** * 정점들을 지정해서 그릴땐 glVertexPointer를 사용한다. 그려질 정점 배열을 지정하는 기능을 담당한다. * 첫번째 인자는 몇 개의 축을 사용할지에 따라 2나 3만 전달해야 한다. * 두번째 인자는 좌표를 부동소수점으로 변환해야 함을 나타낸다. * 세번째 인자는 간격이라부르고 각 정점 사이의 바이트 수를 나타낸다. */ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
/** * glVertexPointer를 통해 정점들을 지정했으면, * glDrawElements를 사용하여 기초 도형들(점, 선, 삼각형)중 하나를 가지고 그 정점들을 그리도록 한다. * * 도형의 종류, 인덱스 수, 각 인텍스의 크기, 세개의 인덱스가 들어 있는 버퍼 */ gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT, mIndexBuffer); } }
|
AnimatedSimpleTriangleRenderer.java 소스보기 - Triangle에 Animation효과를 줘서 Rotation되도록 구현한 클래스이다.
package lee.hyeontae.OpenGLTestHarnessActivity.AnimatedSimpleTriangle;
import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
import lee.hyeontae.OpenGLTestHarnessActivity.AbstractRenderer; import android.content.Context; import android.os.SystemClock;
public class AnimatedSimpleTriangleRenderer extends AbstractRenderer { // 사용할 정점 수 private final static int VERTS = 3;
// 정점 좌표들이 저장될 무형식 전용 버퍼 private FloatBuffer mFVertexBuffer;
// 정점들의 재사용에 사용되는 인덱스가 저장될 무형식 전용 버퍼 private ShortBuffer mIndexBuffer;
public AnimatedSimpleTriangleRenderer(Context context) { /** * 부동소수점 버퍼 생성하기 * 하나의 삼각형은 3개의 정점이 필요하고 * 각 정점은 3개의 부동소수점으로 구성되며(축이 3개) * 각 부동소수점은 4바이트로 구성됨. */ ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4); vbb.order(ByteOrder.nativeOrder()); mFVertexBuffer = vbb.asFloatBuffer();
/** * Index값 마다 2바이트 할당 * Index Buffer는 새 정점들을 생성하는것이 아니라 그저 glVertexPointer를 통해 지정된 정점 배열 인덱스를 참조한다. */ ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2); ibb.order(ByteOrder.nativeOrder()); mIndexBuffer = ibb.asShortBuffer();
float[] coords = { -0.5f, -0.5f, 0, // (x1, y1, z1) 0.5f, -0.5f, 0, 0,0f, 0.5f, 0 }; for(int i=0;i<VERTS;i++) { for(int j=0;j<3;j++) { /** * coords[0],coords[1],coords[2], * coords[3],coords[4],coords[5], * coords[6],coords[7],coords[8], */ mFVertexBuffer.put(coords[i*3+j]); } }
/** * 정점들의 배열 방식을 알아내고, 그 배열을 버퍼에 할당 */ short[] myIndecesArray = {0,1,2}; for(int i=0;i<3;i++) { mIndexBuffer.put(myIndecesArray[i]); } mFVertexBuffer.position(0); mIndexBuffer.position(0);
}
//재 정의 메소드 @Override protected void draw(GL10 gl) {
/** * 4초마다 새로운 회전각을 지정함. */ long time = SystemClock.uptimeMillis() % 4000L; float angle = 0.090f * ((int)time); gl.glRotatef(angle, 0, 0, 1.0f);
/** * Red, Green, Blue, Alpha * 알파 50%의 빨강으로 지정함 */ gl.glColor4f(1.0f, 0, 0, 0.5f);
/** * 정점들을 지정해서 그릴땐 glVertexPointer를 사용한다. 그려질 정점 배열을 지정하는 기능을 담당한다. * 첫번째 인자는 몇 개의 축을 사용할지에 따라 2나 3만 전달해야 한다. * 두번째 인자는 좌표를 부동소수점으로 변환해야 함을 나타낸다. * 세번째 인자는 간격이라부르고 각 정점 사이의 바이트 수를 나타낸다. */ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
/** * glVertexPointer를 통해 정점들을 지정했으면, * glDrawElements를 사용하여 기초 도형들(점, 선, 삼각형)중 하나를 가지고 그 정점들을 그리도록 한다. * * 도형의 종류, 인덱스 수, 각 인덱스의 크기, 세개의 인덱스가 들어 있는 버퍼 */ gl.glDrawElements(GL10.GL_TRIANGLES, VERTS, GL10.GL_UNSIGNED_SHORT, mIndexBuffer); } }
|