Sunday, December 12, 2010

Chỉ dẫn lập trình game 3D trên Android (P5)

Đang chờ deploy cái web viết tiếp loạt bài về OpenGL vậy.
Bài hôm nay tôi sẽ giới thiệu cho các bạn cách vẽ một hình đa giác cách xoay chúng như thế nào. Bài trước các bạn đã được học về GLSurfaceView và hãy chắc chắn rằng bạn đã đọc nó vì nó rất quan trọng cho bài hôm nay.
Loạt bài trước tôi cũng đã giới thiệu với các bạn một số khái niệm về đỉnh, tam giác, đa giác. Hôm nay trước khi vào vẽ đa giác tôi sẽ giới thiệu cho các bạn thêm một số khái niệm khác.
1, Cạnh:
Cạnh là một đường thẳng nối 2 đỉnh với nhau. Chúng là đường viền của mặt và đa giác. Trong không gian 3D một cạnh có thể là một đường giao nhau của 2 mặt, do đó khi cạnh này chuyển đổi sẽ dẫn đến sự thay đổi theo của các đỉnh, các mặt kết nối với chúng.
Trong OpenGL ES bạn không định nghĩa các cạnh, thay vào đó bạn định nghĩa các mặt bởi các đỉnh khi đó ta sẽ có các cạnh.
Nếu bạn muốn sửa, thay đổi mộ cạnh bạn có thể thay đổi 2 đỉnh tạo nên cạnh đó.
2, Mặt:
Một cách tổng quát, mặt trong toán học là đa tạp n-1 chiều trong không gian n chiều. Trong tô pô, một mặt hai chiều được gọi là mặt đơn giản nếu nó đồng phôi với một hình vuông trên mặt phẳng hai chiều.
Ví dụ: mặt bán cầu là một mặt đơn giản, còn toàn bộ mặt cầu lại không phải là một mặt đơn giản.
Trong không gian ba chiều, mặt được mặc định là đa tạp hai chiều, là tập hợp những điểm trong hệ tọa độ Đề các ba chiều Oxyz thỏa phương trình: z = f(x,y).
Trong OpenGL ES ta hiểu rằng có thể chia mặt tới mức nhỏ nhất khi đó một mặt chỉ là một tam giác, chúng được tạo lên bởi 3 đỉnh, một chuyển đổi của mặt sẽ ảnh hưởng tới các đỉnh, cạnh liên kết với nó.
Khi uốn khúc trên bề mặt của bạn điều này rất quan trọng để thực hiện các điều khiển đúng hướng của nó. Khi đó bạn phải điều khiển những gì sẽ sảy ra với mặt phía trước và cả mặt phía sau nữa. Tại sao điều này lại rất quan trọng bởi vì hiệu năng của ứng dụng sẽ tăng lên khi bạn không phải vẽ cả 2 mặt mà chỉ thực hiện vẽ mặt phía trước. Đó là ý tưởng tốt sử dụng sự uốn khúc cho tất cả các dự án của bạn. Bạn có thể định nghĩa phía trên của bề mặt bằng cách.
gl.glFrontFace(GL10.GL_CCW); // OpenGL docs
gl.glEnable(GL10.GL_CULL_FACE); // OpenGL docs
gl.glCullFace(GL10.GL_BACK); // OpenGL docs

3, Render:
Thời gian để bạn đưa một cái j đó ra ngoài màn hình, có 2 phương thức để bạn làm điều này, 2 phương thức đó là:
public abstract void glDrawArrays(int mode, int first, int count); // OpenGL docs

public abstract void glDrawElements(int mode, int count, int type, Buffer indices); // OpenGL docs

Một số render cơ bản nhất:
GL_POINTS: Render một điểm ra màn hình.
GL_LINE_STRIP: Render một dãy các phân đoạn đường thẳng nối liền nhau, nhưng không nối điểm đầu và điểm cuối.
GL_LINE_LOOP: Giống như GL_LINE_STRIP nhưng không nối điểm đầu và điểm cuối.
GL_LINES: Render một đường thẳng.
GL_TRIANGLES: Render một tam giác.
GL_TRIANGLE_STRIP: Render một loạt các tam giác theo thứ tự VD: Ta có các đỉnh v0, v1, v2, v3, v4, v5 khi đó các tam giác được render là (v0, v1, v2) sau đó là (v2, v1, v3) (lưu ý thứ tự), tiếp theo là (v2, v3, v4) ...
Gl_triangle_strip
GL_TRIANGLE_FAN: Render một loạt các tam giác giống như GL_TRIANGLE_STRIP nhưng các tam giác được vẽ giống như một hình quạt dựa vào một gốc.
Gl_triangle_strip

Giờ chúng ta sẽ vẽ thử một tam giác:
Bạn vẫn còn project hôm trước chứ, ta sẽ thay đổi một chút để có thể render ra được một tam giác.
Ta chỉ cần sửa lại lớp VortexRenderer lại như sau:

package vn.vndev.android.opengl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

/**
 * 
 * @author TanNM
 * 
 */
public class VortexRenderer implements GLSurfaceView.Renderer {
 private static final String LOG_TAG = VortexRenderer.class.getSimpleName();

 private float red = 0f;
 private float green = 0f;
 private float blue = 0f;

 // a raw buffer to hold indices allowing a reuse of points.
 private ShortBuffer indexBuffer;

 // a raw buffer to hold the vertices
 private FloatBuffer vertexBuffer;

 private short[] indicesArray = { 0, 1, 2 };
 private int nrOfVertices = 3;

 private float angle;

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  // preparation
  gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  initTriangle();
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int w, int h) {
  gl.glViewport(0, 0, w, h);
 }

 public void setAngle(float angle) {
  this.angle = angle;
 }

 @Override
 public void onDrawFrame(GL10 gl) {
  // define the color we want to be displayed as the "clipping wall"
  gl.glClearColor(red, green, blue, 1.0f);

  // clear the color buffer to show the ClearColor we called above...
  gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

  // set rotation
  gl.glRotatef(angle, 0f, 1f, 0f);

  gl.glColor4f(0.2f, 0.5f, 0.2f, 0.5f);
  gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
  gl.glDrawElements(GL10.GL_TRIANGLES, nrOfVertices,
    GL10.GL_UNSIGNED_SHORT, indexBuffer);
 }

 private void initTriangle() {
  // float has 4 bytes
  ByteBuffer vbb = ByteBuffer.allocateDirect(nrOfVertices * 3 * 4);
  vbb.order(ByteOrder.nativeOrder());
  vertexBuffer = vbb.asFloatBuffer();

  // short has 4 bytes
  ByteBuffer ibb = ByteBuffer.allocateDirect(nrOfVertices * 2);
  ibb.order(ByteOrder.nativeOrder());
  indexBuffer = ibb.asShortBuffer();

  float[] coords = { -0.5f, -0.5f, 0f, // (x1, y1, z1)
    0.5f, -0.5f, 0f, // (x2, y2, z2)
    0f, 0.5f, 0f // (x3, y3, z3)
  };
  vertexBuffer.put(coords);
  indexBuffer.put(indicesArray);
  vertexBuffer.position(0);
  indexBuffer.position(0);
 }

 public void setColor(float r, float g, float b) {
  red = r;
  green = g;
  blue = b;
 }
}

Để hình tam giác của ta quay với tốc độ tùy vào vị chí mà ta click ta set thêm góc giống như
renderer.setAngle((event.getX() + event.getY()) / 20);
trong phương thức
public boolean onTouchEvent(final MotionEvent event)
của lớp VortexView

Bạn có thể download mã nguồn của ứng dụng về chạy thử.

Friday, December 3, 2010

Chỉ dẫn lập trình game 3D trên Android (P4)

Chúc các bạn một ngày tốt lành, hôm qua đi đá bóng về đau chân quá ko làm việc được nên ngồi viết bài tiếp vậy!
Hôm trước ứng bài demo chạy tốt chứ? Hôm nay tôi sẽ giải thích sơ qua một chút về ứng dụng đó.
1, Trước hết là lớp GLSurfaceView:
Bắt đầu được hỗ trợ trên Android từ phiên bản 1.5.
Lớp này hỗ trợ bạn kết nối vào OpenGL từ view bằng cách quản lý một bề mặt và ghép chúng vào hệ thống view của Android. Nó quản lý một EGL display, cho phép OpenGL thực hiện render vào một bề mặt.
Tạo khả năng giúp OpenGL ES làm việc với vòng đời của Activity.
Tạo khả năng lựa chọn định dạng frame buffer pixel một cách thích hợp.
Quản lý các thread rendering riêng biệt cho phép tạo các chuyển động mượt mà :D.
Cung cấp tools debugging một cách đơn giản cho tracing các lời gọi API OpenGL ES và kiểm tra lỗi.

Bạn muốn tiến xa hơn trong OpenGL thì đây là nơi mà bạn cần phải bắt đầu.
Để sử dụng GLSurfaceView bạn chỉ cần gọi phương thức:
 public void  setRenderer(GLSurfaceView.Renderer renderer);
 
có trong GLSurfaceView. Để tùy biến các thuộc tính của GLSurfaceView thì trước khi gọi phương thức setRenderer bạn có thể gọi một số các phương thức khác như:
 setDebugFlags(int);
 setEGLConfigChooser(boolean);
 setEGLConfigChooser(EGLConfigChooser);
 setEGLConfigChooser(int, int, int, int, int, int);
 setGLWrapper(GLWrapper);
 
Mặc định GLSurfaceView sẽ tạo một bề mặt với định dạng mặc định là PixelFormat.RGB_565. Nếu bạn cần một bề mặt trong suốt bạn có thể gọi phương thức getHolder().setFormat(PixelFormat.TRANSLUCENT).
Một GLSurfaceView phải được thông báo khi Activity là paused và resumed. GLSurfaceView clients cần phải gọi phương thức onPause() khi activity pauses và gọi onResume() khi activity resumes. Lời gọi cho phép GLSurfaceView thực hiện pause hay resume các thread rendering, và nó cũng cho phép GLSurfaceView release hay khởi tạo lại các đối tượng OpenGL display.

2, GLSurfaceView.Renderer:
Là một render interface.
Render sẽ được gọi trên một thread riêng biệt vì thế hiệu năng rendering sẽ tách rời UI thread. Nhưng các render cần giao tiếp với các UI thread bởi vì UI thread là nơi nhận được các sự kiện input. Các render có thể giao tiếp bằng bất kỳ kỹ thuật java chuẩn nào cho giao tiếp cross-thread, hoặc đơn giản hơn là sử dụng phương thức queueEvent(Runnable).
Khi bạn implementation render này bạn cần khai báo các phương thức sau:
 // Gọi khi một bề mặt được khởi tạo hay khởi tạo lại
 public void onSurfaceCreated(GL10 gl, EGLConfig config) 

 // Gọi để vẽ frame hiện tại.
 // Phương thức này sẽ chịu trách nhiệm vẽ frame hiện tại.
 public void onDrawFrame(GL10 gl)

 // Gọi khi bề mặt thay đổi kích thước
 public void onSurfaceChanged(GL10 gl, int width, int height)
 

onSurfaceCreated: Đây là nơi tốt để bạn cài đặt một điều gì đó mà bạn không thường xuyên thay đổi trong vòng đời của rendering.
onDrawFrame: Đây là nơi sự kiện vẽ thực sự diễn ra.
onSurfaceChanged: Nếu thiết bị của bạn cho phép chuyển giữa màn hình nằm ngang hay nằm dọc, bạn sẽ có lời gọi tới phương thức này khi sự chuyển đổi đó diễn ra. Bạn có thể thiết lập lại các tỷ lệ mới ở đây

Bạn có thể download mã nguồn của bài trước và bài này tại đây

Thursday, December 2, 2010

Chỉ dẫn lập trình game 3D trên Android (P3)

Hôm nay tôi sẽ chỉ các bạn lập trình OpenGL trong Android, cách sử dụng OpenGL trong cả 2 ngôn ngữ Java và C. Điều này rất quan trọng trong việc tái sử dụng mã nguồn trong Java và C cũng như sử dụng được các tính năng tốt nhất mà mỗi ngôn ngữ cung cấp.
Bất kỳ một nhà phát triển game nào cũng cần phải biết OpenGL là kim chỉ nam cho nhưng nhà phát triển game chuyên nghiệp. Bạn khó có thể tìm thấy một game mạnh mẽ nào mà không sử dụng API của OpenGl, bởi vì nó có khả năng tận dụng và tăng tốc phần cứng hơn hầu hết tất cả các phần mềm renderer khác.
OpenGL có thể là một môn học đáng sợ cho người mới bắt đầu (that's true for me :D). Nhưng bạn cũng không thể thành thạo OpenGL tới mức biết được làm thế nào để nó có thể vẽ các phần tử đó với OpenGL API. Bạn chỉ cần sự ham học hỏi, mong muốn tìm hiểu một công cụ mạnh mẽ để xây dựng các ứng dụng đồ họa như game.
Trong giới hạn của bài này tôi không thể chỉ bạn tất cả về OpenGL mà tôi chỉ đưa ra ví dụ sử dụng chúng trong ứng dụng Android, dựa vào đó và các API của OpenGL bạn có thể tùy biến theo ý của bạn.
Tham khảo thêm về OpenGL tại:
http://www.opengl.org/sdk/docs/man/
http://www.falloutsoftware.com/tutorials/gl/gl0.htm
http://mathworld.wolfram.com/OrthographicProjection.html
http://nehe.gamedev.net/

Về thiết bị thì khỏi bàn, hiện nay Android ngày càng trở nên mạnh mẽ cho việc phát triển các ứng dụng đồ họa.
Để tận dụng tối đa hiệu năng của CPU, Google đã đưa OpenGL Embedded System (OpenGL-ES) vào trong hệ điều hành Android.
OpenGL-ES cung cấp một loạt các API làm cho game có hiệu năng sử lý cao, tăng tốc phần cứng. Đây là các Java API, để xây dựng lại chúng từ C là một công việc rất phức tạp. Tôi sẽ chỉ các bạn cách thức làm việc trên cả Java và C.
Bắt đầu bằng một số khái niệm cơ bản về OpenGL

1, Đỉnh: là một điểm trong không gian 3D trong OpenGl có thể chỉ là 2 tọa độ (x, y) hoặc nhiều tọa độ như (x, y, z, w) ở đó w là tùy chọn và mặc định nó có giá trị là 1.0, tương tự trục z cũng là tùy chọn và giá trị mặc định của nó là 0. Tất các các đối tượng được vẽ bởi các đỉnh hay chính là các điểm, do đó kho nói tới điểm cũng chính là 1 đỉnh.
2, Tam giác: được tạo nên bởi 3 điểm, trong OpenGL chúng ta có thể dùng 3 đỉnh để tạo nên 1 tam giác.
3, Đa giác: được tạo nên bởi nhiều hơn 2 điểm, tam giác cũng là 1 đa giác.

Nào trước hết chúng ta bắt đầu chúng với Java:
Tạo một Android project mới, với activity là Vortex và một số các lớp sau:
package vn.vndev.android.opengl;

import android.app.Activity;
import android.os.Bundle;

/**
 * 
 * @author TanNM
 *
 */
public class Vortex extends Activity {
 private static final String LOG_TAG = Vortex.class.getSimpleName();
 private VortexView vortexView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  // Hien thi toan man hinh
  this.requestWindowFeature(Window.FEATURE_NO_TITLE); 
  getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN);
  vortexView = new VortexView(this);
  setContentView(vortexView);
 }
}
package vn.vndev.android.opengl;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;

/**
 * 
 * @author TanNM
 *
 */
public class VortexView extends GLSurfaceView {
 private static final String LOG_TAG = VortexView.class.getSimpleName();
 private VortexRenderer renderer;

 /**
  * 
  * @param context
  */
 public VortexView(Context context) {
  super(context);
  renderer = new VortexRenderer();
  setRenderer(renderer);
 }

 public boolean onTouchEvent(final MotionEvent event) {
  queueEvent(new Runnable() {
   public void run() {
    renderer.setColor(event.getX() / getWidth(), event.getY() / getHeight(), 1.0f);
   }
  });
  return true;
 }
}
package vn.vndev.android.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

/**
 * 
 * @author TanNM
 *
 */
public class VortexRenderer implements GLSurfaceView.Renderer {
 private static final String LOG_TAG = VortexRenderer.class.getSimpleName();

 private float red = 0.7f;
 private float green = 0.5f;
 private float blue = 0.8f;

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int w, int h) {
  gl.glViewport(0, 0, w, h);
  float ratio = (float) w / h;
  gl.glMatrixMode(GL10.GL_PROJECTION);
  gl.glLoadIdentity();
  gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
 }

 @Override
 public void onDrawFrame(GL10 gl) {
  gl.glClearColor(red, green, blue, 1.0f);
  gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
 }

 public void setColor(float r, float g, float b) {
  red = r;
  green = g;
  blue = b;
 }
}
Ok giờ bạn hãy chạy thử ứng dụng và click lên các vị trí khác nhau của màn hình. Thấy j nhỉ?