OpenGL ES Tutorial for Android-Part II-Building a polygon
December 4th, 2009 by Per-Erik Bergman-Android,
Embedded
Previous tutorial was all about setting up the GLSurfaceView. Be sure
Read it beacuse it's a really importent one to be able to continue.
Building a polygon
In this tutorial we will render our first polygon.
In this tutorial, we will show our first Polygon
3D models are built up with smaller elements (vertices, edges, faces, and polygons) which can be manipulated individually.
A 3D model is composed of a group of smaller elements (points, lines, surfaces, and polygon) that can be independently manipulated.
Vertex
A vertex (vertices in plural) is the smallest building block of 3D model. A vertex is a point where two or more edges meet. in a 3D model a vertex can be shared between all connected edges, paces and polygons. A vertex can also be a represent for the position
Of a camera or a light source. You can see a vertex in the image below marked in yellow.
Vertices that intersection two or more edges are called vertices, which are the smallest element of a 3D model. In 3D models, vertices can be shared by connected edges, faces, and polygon. Of course, vertices can also represent light sources or cameras. The yellow vertex of the annotation is the vertex.
To define the vertices on android we define them as a float array that we put into a byte buffer to gain better performance. look at the image to the right and the code below to match the vertices marked on the image to the code. On android, a floating point group is used to define the vertex. To improve performance, it is placed in a byte buffer (new feature of Java 5, NIO ). Four vertices are marked on the right. The following code defines them. |
|
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
};
// A float is 4 bytes, therefore we multiply the number if vertices with 4. a floating point occupies 4 bytes, so the byte buffer size is x4 (bytes)
ByteBuffer vbb = ByteBuffer. allocateDirect (vertices. length * 4 );
Vbb. order (ByteOrder. nativeOrder ());
FloatBuffer vertexBuffer = vbb. asFloatBuffer ();
VertexBuffer. put (vertices );
VertexBuffer. position (0 );
Don't forget that a float is 4 bytes and to multiply it with the number of vertices to get the right size on the allocated buffer.
OpenGL ES have a pipeline with functions to apply when you tell it to render. most of these functions are not enabled by default so you have to remember to turn the ones you like to use on. you might also need to tell these functions what to work. so
In the case of our vertices we need to tell OpenGL ES that it's okay to work with the vertex buffer we created we also need to tell where it is.
OpenGL Es pipelines can be understood as functions with a set of switch control functions, and many control functions are disabled by default. If you need a function, please remember to enable these functions first. In some cases, you may need to specify the purpose of these functions. For the vertices defined above, we need to tell OpenGL ES to enable vertex buffering and set vertex buffering.
// Enabled the vertex buffer for writing and to be used during rendering. Enable vertex Buffering
Gl. glableclientstate (GL10.GL _ VERTEX_ARRAY); // OpenGL
Docs.
// Specifies the location and data format of an array of vertex
// Coordinates to use when rendering. Specify the vertex position
Gl. glVertexPointer (3, GL10.GL _ FLOAT, 0, vertexBuffer); // OpenGL
Docs.
When you are done with the buffer don't forget to disable it.
Finally, don't forget to disable vertex buffering (after painting)
// Disable the vertices buffer.
Gl. glDisableClientState (GL10.GL _ VERTEX_ARRAY); // OpenGL
Docs.
Edge
Edge is a line between two vertices. they are border lines of faces and polygons. in a 3D model an edge can be shared between two adjacent faces or polygons. transforming an edge affects all connected vertices, faces and polygons. in OpenGL ES you don't
Define the edges, you rather define the face by giving them the vertices that wocould build up the three edges. if you wowould like modify an edge you change the two vertices that makes the edge. you can see an edge in the image below marked in yellow.
An edge (also known as a line) is a line between two vertices, and the border of a polygon is also an edge (Border border, framed by the surface side ). In 3D models, edges can be shared by connected surfaces. Moving an edge will affect all the points and faces connected to it. In OpenGL ES, you do not need to define an edge. You need to define a surface (triangle) consisting of three edges by a given vertex ), if you need to change an edge, you only need to change the two vertices that constitute the edge. Marked in yellow is an edge.
Face
Face is a triangle. Face is a surface between three corner vertices and three surrounding edges. Transforming a face affects all connected vertices, edges and polygons.
A surface is a triangle. A surface is a plane composed of three vertices. It is surrounded by three sides. Moving a plane will affect the connected vertex, edge, and polygon (why is there a polygon ?)
The order does matter.
Order is important!
When winding up the faces it's important to do it in the right direction because the direction defines what side will be the front face and what side will be the back face. why this is important is because to gain performance we don't want to draw both sides
So we turn off the back face. So it's a good idea to use the same winding all over your project. It is possible to change what direction that defines the front face with glFrontFace.
When you rotate a plane, it is important to define the correct direction, because the direction determines (when you rotate) which side is in front and which side is in the back. Why is it important? Because you do not need to draw the back and front at the same time during painting (just like when you look at the computer screen, you cannot see the back, but when you turn a certain angle, you can see the back ). Therefore, using the same rotation direction in the project is an incorrect choice. You can use the glFrontFace function to define the direction.
Gl. glFrontFace (GL10.GL _ CCW); // OpenGL docs
To make OpenGL skip the faces that are turned into the screen you can use something called back-face culling. what is does is determines whether a polygon of a graphical object is visible by checking if the face is wind up in the right order.
If you need OpenGL to skip (do not draw) the back, you can enable a feature called back occlusion. When the face orientation is correct, it determines whether the polygon of the graphic object is visible.
The following code enables the occlusion Function
Gl. glable (GL10.GL _ CULL_FACE); // OpenGL docs
It's ofcource possible to change what face side shoshould be drawn or not.
The following code blocks the back (if you draw a cube, you will not see the bottom of the screen)
Gl. glCullFace (GL10.GL _ BACK); // OpenGL docs
Polygon
Time to wind the faces, remember we have decided to go with the default winding meaning counter-clockwise. look at the image to the right and the code below to see how to wind up this square. Please refer to the graph on the right. We use the following code to define the vertex sequence array to structure the surface in the graph (the default aspect is counter-clockwise) How to translate wind? |
|
Private short [] indices = {0, 1, 2, 0, 2, 3 };
To gain some performance we also put this ones in a byte buffer.
Similarly, we use byte buffering to improve performance.
// Short is 2 bytes, therefore we multiply the number if vertices with 2. the Byte buffer length is array size x2. (a short occupies 2 bytes)
ByteBuffer ibb = ByteBuffer. allocateDirect (indices. length * 2 );
Ibb. order (ByteOrder. nativeOrder ());
Required buffer indexBuffer = ibb. asw.buffer ();
IndexBuffer. put (indices );
IndexBuffer. position (0 );
Don't forget that a short is 2 bytes and to multiply it with the number of indices to get the right size on the allocated buffer.
Render
Time to get something on the screen, there is two functions used to draw and we have to decide which one to use.
The following shows some things on the screen. Painting has the following two functions:
The two functions are:
Public abstract void
GlDrawArrays (int mode, int first, int count )//
OpenGL docs
GlDrawArrays draws the vertices in that order they are specified in the construction of our verticesBuffer.
GlDrawArrays are drawn in the order specified by constructing the verticesBuffer.
Public abstract void
GlDrawElements (int mode, int count, int type ,//
OpenGL docs
Buffer indices)
GlDrawElements need a little bit more to be able to draw. It needs to know the order which to draw the vertices, it needs the indicesBuffer.
GlDrawElements may be used more. It needs to know the order of vertex painting, so it needs a vertex sequence array.
Since we already created the indicesBuffer I'm guessing that you figured out that's the way we are going.
In the code above, we specified the vertex sequence array. So, I think you should know what to do next.
What is common for this functions is that they both need to know what it is they shoshould draw, what primitives to render. since there is some varous ways to render this indices and some of them are good to know about for debugging reasons. i'll go through
Them all.
Both functions have a parameter named 'int', which tells OpenGL ES what to draw (point, line, and surface). Let's call it the (original) display method here, there are many types of data. For more information, see:
What primitives to render
GL_POINTS
Draws individual points on the screen.
Draw separate points
GL_LINE_STRIP
Series of connected line segments. Draw line
GL_LINE_LOOP
Same as abve, with a segment added between last and first vertices. Draw a line from the first vertex and the last vertex.
GL_LINES
Pairs of vertices interpreted as individual line segments. Draw a line consisting of two vertex Pairs
GL_TRIANGLES
Triples of vertices interpreted as triangles. Draw a triangle
GL_TRIANGLE_STRIP
Draws a series of triangles (three-sided polygons) using vertices v0, V1, V2, then V2, V1, V3 (note the Order), then V2, V3, V4, and so on. the ordering is to ensure that the triangles are all drawn with the same orientation so that the strip can correctly
Form part of a surface. Draw a series of triangles, first v0, V1, V2, V2, V1, V3 (pay attention to the Order), then V2, V3, V4 and so on. Order is to ensure that the triangle is painted in the same direction, so that a part of a plane can be correctly formed.
GL_TRIANGLE_FAN
Same as GL_TRIANGLE_STRIP, distribution t that the vertices are drawn v0, v1, v2, then v0, v2, v3, then v0, v3, v4, and so on.
Similar to the GL_TRIANGLE_STRIP mode, the vertex sequence is v0, v1, v2, and v0, v2, v3, and v0, v3, and v4.
I think the GL_TRIANGLES is the easiest to use so we go with that one for now.
I think GL_TRIANGLES is the easiest to use, so we use it to draw (square)
Putting it all togetter
So let's putting our square together in a class.
Package se. jayway. opengl. tutorial;
Import java. nio. ByteBuffer;
Import java. nio. ByteOrder;
Import java. nio. FloatBuffer;
Import java. nio. Protocol buffer;
Import javax. microedition. khronos. opengles. GL10;
Public class Square {
// Our vertices.
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 writable buffer 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. asw.buffer ();
IndexBuffer. put (indices );
IndexBuffer. position (0 );
}
/**
* This function draws our square on screen.
* @ Param GL
*/
Public void draw (gl10 GL ){
// Counter-clockwise winding.
Gl. glfrontface (gl10.gl _ CCW); // OpenGL docs
// Enable face culling.
Gl. glable (gl10.gl _ cull_face); // OpenGL docs
// What faces to remove with the face culling.
Gl. glCullFace (GL10.GL _ BACK); // OpenGL docs
// Enabled the vertices buffer for writing and to be used
// Rendering.
Gl. glableclientstate (GL10.GL _ VERTEX_ARRAY); // OpenGL
Docs.
// Specifies the location and data format of an array of vertex
// Coordinates to use when rendering.
Gl. glVertexPointer (3, GL10.GL _ FLOAT, 0, // OpenGL
Docs
VertexBuffer );
Gl. glDrawElements (GL10.GL _ TRIANGLES, indices. length, // OpenGL
Docs
GL10.GL _ UNSIGNED_SHORT, indexBuffer );
// Disable the vertices buffer.
Gl. glDisableClientState (GL10.GL _ VERTEX_ARRAY); // OpenGL
Docs
// Disable face culling.
Gl. glDisable (GL10.GL _ CULL_FACE); // OpenGL docs
}
}
We have to initialize our square in the OpenGLRenderer class.
// Initialize our square.
Square square = new Square ();
And in the draw function call on the square to draw.
Public void onDrawFrame (GL10 gl ){
// Clears the screen and depth buffer.
Gl. glClear (GL10.GL _ COLOR_BUFFER_BIT | // OpenGL docs.
GL10.GL _ DEPTH_BUFFER_BIT );
// Draw our square.
Square. draw (gl); // (NEW)
}
If you run the application now the screen is still black. Why? Because OpenGL ES render from where the current position is, that by default is at point: 0, 0, 0 the same position that the view port is located. and OpenGL ES don't render the things that are
Too close to the view port. The solution to this is to move the draw position a few steps into the screen before rendering the square:
How do you run this program? You can see that the screen is still black. Why? Since OpenGL ES starts rendering from the current vertex (default: 0, 0), This vertex is occupied by windows, and OpenGL ES does not render it too close to this vertex. The solution is to move the painting point to the negative direction of the Z axis before rendering.
// Translates 4 units into the screen.
Gl. gltranslatef (0, 0,-4); // OpenGL docs
I will talk about the different transformations in the next tutorial.
A detailed description of moving will be discussed in the next tutorial.
Run the application again and you will see that the square is drawn but quickly moves further and further into the screen. openGL ES doesn't reset the drawing point between the frames that you will have to do yourself:
Run the program again and you will see the square shown, but soon it flies farther and farther (smaller and smaller) and disappears into the screen. This is because OpenGL ES does not reset the painting point when painting frames, which needs to be set manually.
// Replace the current matrix with the identity matrix
Gl. glloadidentity (); // OpenGL docs
Now if you run the application you will see the square on a fixed position.
References
The info used in this tutorial is collected from:
Android Developers
OpenGL ES 1.1 Reference Pages
You can download the source for this tutorial here:
Tutorial_Part_II
You can also checkout the code from:
Code.google.com
Previous tutorial:
OpenGL ES Tutorial for Android-Part I-Setting up the view
Next tutorial:
OpenGL ES Tutorial for Android-Part III-Transformations
Per-Erik Bergman
Consultant at Jayway