Saturday, March 15, 2014

OpenGL ES 2.0 trên android - Định nghĩa Shapes - Hình

Định nghĩa Shapes

Việc có thể để xác định Shapes để vẽ trong context của một khung nhìn OpenGL ES là bước đầu tiên trong việc tạo ra tác phẩm đồ họa cao cấp của bạn. Vẽ với OpenGL ES có thể là một chút khó khăn mà không biết một số điều cơ bản về cách OpenGL ES hy vọng bạn xác định đối tượng đồ họa.
Bài học này giải thích OpenGL ES phối hợp hệ thống liên quan đến một màn hình thiết bị Android, những điều cơ bản của việc xác định Shapes, bề mặt Shapes, cũng như xác định một tam giác và một hình vuông.

1.1.   Định nghĩa một Tam giác

OpenGL ES cho phép bạn xác định đối tượng rút ra sử dụng tọa độ trong không gian ba chiều. Vì vậy, trước khi bạn có thể vẽ một hình tam giác, bạn phải xác định tọa độ của nó. Trong OpenGL, cách điển hình để làm điều này là để xác định một mảng đỉnh của số thực. Cho hiệu quả tối đa, bạn viết các tọa độ vào một ByteBuffer, nó sẽ thông qua vào các đường ống dẫn đồ họa OpenGL ES để xử lý.
public class Triangle {

    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array

    static final int COORDS_PER_VERTEX = 3;

    static float triangleCoords[] = {   // in counterclockwise order:
             0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
             0.5f, -0.311004243f, 0.0f  // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values

    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {

        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (number of coordinate values * 4 bytes per float)
                triangleCoords.length * 4);
        // use the device hardware's native byte order
        bb.order(ByteOrder.nativeOrder());

        // create a floating point buffer from the ByteBuffer
        vertexBuffer = bb.asFloatBuffer();
        // add the coordinates to the FloatBuffer
        vertexBuffer.put(triangleCoords);
        // set the buffer to read the first coordinate
        vertexBuffer.position(0);
    }
}
Theo mặc định, OpenGL ES giả định một hệ thống phối hợp nơi [0,0,0] (X, Y, Z) xác định tâm của khung GLSurfaceView, [1,1,0] là góc trên bên phải của khung và [- 1, -1,0] là góc dưới bên trái của khung. Cho một minh họa của hệ thống phối hợp này, hãy xem hướng phát triển OpenGL ES.
Lưu ý rằng các tọa độ của Shapes này được xác định theo một thứ tự ngược chiều kim đồng hồ. Thứ tự bản vẽ là quan trọng vì nó xác định mặt trước và mặt sau của bề mặt của Shapes mà bạn vẽ, với mặt sau bề mặt, bạn có thể chọn không vẽ bằng cách sử dụng tính năng tiêu hủy mặt OpenGL ES. Để biết thêm thông tin về mặt và hủy, xem hướng dẫn phát triển OpenGL ES.

1.2.   Định nghĩa hình vuông

Xác định tam giác là khá dễ dàng trong OpenGL, nhưng nếu bạn muốn phức tạp hơn một chút? Gì nhỉ? một hình vuông? Có một số cách để làm điều này, nhưng một con đường điển hình để vẽ một hình dạng như vậy trong OpenGL ES là sử dụng hai hình tam giác được vẽ với nhau:
Figure 1. Drawing a square using two triangles.
Một lần nữa, bạn nên xác định các đỉnh theo một thứ tự ngược chiều kim đồng hồ cho cả hai hình tam giác mà đại diện cho hình dạng này, và đặt các giá trị trong một ByteBuffer. Để tránh việc xác định tọa độ hai chia sẻ bởi mỗi tam giác hai lần, sử dụng một danh sách bản vẽ để cho các đường ống dẫn đồ họa OpenGL ES cách vẽ các đỉnh. Đây là mã cho hình này:
public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;

    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);

        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}
Ví dụ này cung cấp cho bạn một cái nhìn vào những gì nó cần để tạo ra hình dạng phức tạp hơn với OpenGL. Nói chung, bạn sử dụng danh sách các hình tam giác để vẽ các đối tượng. Trong bài học tiếp theo, bạn tìm hiểu làm thế nào để vẽ các hình dạng trên màn hình.

2.      Vẽ Shapes

Sau khi bạn xác định shapes để được vẽ với OpenGL, bạn có thể muốn vẽ chúng. Vẽ hình với OpenGL ES 2.0 có thêm một chút mã hơn bạn có thể tưởng tượng, bởi vì các API cung cấp rất nhiều quyền kiểm soát các đường ống dẫn vẽ đồ họa.
Bài học này giải thích làm thế nào để vẽ các hình dạng bạn định nghĩa trong bài học trước bằng cách sử dụng OpenGL ES 2.0 API.

2.1.   Khởi tạo Shapes

Trước khi bạn vẽ bất kỳ thứ gì, bạn phải khởi tạo và tải các Shapes mà bạn muốn vẽ. Trừ khi cấu trúc của các Shapes thay đổi trong quá trình thực hiện, bạn nên khởi tạo chúng trong onSurfaceCreated () của renderer của bạn cho bộ nhớ và xử lý hiệu quả.
public void onSurfaceCreated(GL10 unused, EGLConfig config) {

    ...
    // initialize a triangle
    mTriangle = new Triangle();
    // initialize a square
    mSquare = new Square();
}

2.2.   Vẽ Shape

Vẽ một định nghĩa Shape bằng cách sử dụng OpenGL ES 2.0 đòi hỏi một số lượng đáng kể của mã, bởi vì bạn phải cung cấp rất nhiều chi tiết để các đường ống dẫn vẽ đồ họa. Cụ thể, bạn phải xác định như sau:
·         Vertex Shader - đồ họa OpenGL ES đang để hiển thị các đỉnh của một Shape.
·         Fragment Shader – mã OpenGL ES cho dựng hình bề mặt của một shape với màu sắc hoặc Textures.
·         Program - Một đối tượng OpenGL ES có chứa các bóng đổ bạn muốn sử dụng để vẽ một hoặc nhiều hình dạng.
Bạn cần ít nhất một vertex shader để vẽ một shape và một fragment shader để tô màu cho shape. Các bóng đổ phải được hoàn chỉnh và sau đó thêm vào một chương trình OpenGL ES, sau đó được sử dụng để vẽ hình dạng. Dưới đây là một ví dụ về cách xác định shaders cơ bản bạn có thể sử dụng để vẽ một hình dạng:
private final String vertexShaderCode =
    "attribute vec4 vPosition;" +
    "void main() {" +
    "  gl_Position = vPosition;" +
    "}";

private final String fragmentShaderCode =
    "precision mediump float;" +
    "uniform vec4 vColor;" +
    "void main() {" +
    "  gl_FragColor = vColor;" +
    "}";
Shaders chứa OpenGL Shading Language (GLSL) mã phải được biên dịch trước khi sử dụng nó trong môi trường OpenGL ES. Để biên dịch mã này, tạo ra một utility method trong lớp renderer của bạn:
public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}
Để vẽ shape của bạn, bạn phải biên dịch shader code, thêm chúng vào một đối tượng program OpenGL ES và sau đó liên kết các program. Làm điều này trong hàm khởi tạo đối tượng, vì vậy nó chỉ được thực hiện một lần.
Lưu ý: Biên dịch shaders OpenGL ES và các liên kết program là tốn kém về chu kỳ CPU và thời gian xử lý, vì vậy bạn nên tránh làm điều này nhiều hơn một lần. Nếu bạn không biết nội dung của các bóng đổ của bạn tại thời gian chạy, bạn nên xây dựng mã của bạn như vậy mà họ chỉ được tạo ra một lần và sau đó lưu lại để sử dụng sau này.
public class Triangle() {

    ...

    int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);

    int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

    mProgram = GLES20.glCreateProgram();             // create empty OpenGL ES Program
    GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
    GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
    GLES20.glLinkProgram(mProgram);                  // creates OpenGL ES program executables
}
Tại thời điểm này, bạn đã sẵn sàng để thêm các lời gọi vẽ shape của bạn. Vẽ hình với OpenGL ES yêu cầu bạn chỉ định một số thông số để cho các đường ống dẫn vẽ những gì bạn muốn vẽ và làm thế nào để vẽ nó. Kể từ khi lựa chọn vẽ có thể khác nhau bởi hình dạng, đó là một ý tưởng tốt để có các lớp hình dạng của bạn chứa logic vẽ của mình.
Tạo ra một hàm draw() để vẽ shape. Mã này thiết đặt vị trí và giá trị màu cho vertex shader và fragment shader, và sau đó thực hiện chức năng vẽ.
public void draw() {

    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}
Một khi bạn có tất cả các mã này, vẽ đối tượng này chỉ đòi hỏi một lời gọi đến draw() từ bên trong phương pháp onDrawFrame()của renderer của bạn. Khi bạn chạy ứng dụng, nó sẽ giống như thế này:
Figure 1. Triangle drawn without a projection or camera view.
Có một vài vấn đề với mã ví dụ này. Trước hết, nó sẽ không gây ấn tượng với bạn bè của bạn. Thứ hai, hình tam giác là một chút bị đè nén và thay đổi hình dạng khi bạn thay đổi hướng màn hình của thiết bị. Lý do hình dạng bị lệch là do thực tế là các đỉnh của đối tượng đã không được sửa chữa cho tỷ lệ diện tích màn hình, nơi các GLSurfaceView được hiển thị. Bạn có thể khắc phục vấn đề bằng cách sử dụng chiếu và xem ảnh trong bài học tiếp theo.

Cuối cùng, hình tam giác là hình cơ bản, nên có một chút nhàm chán. Trong bài học thêm chuyển độn, bạn làm cho hình dạng này xoay và sử dụng thú vị hơn của đường ống đồ họa OpenGL ES.

No comments:

Post a Comment