Đị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:
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:
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