Use VBO extension
The original article http://nehe.gamedev.net Lesson 45.
Translation: XheartBlue Pan Li Liang Stanly Lee 2004-4-18
Homepage: http://gamehunter.3322.net/xpertsoft/
MSN / Email: XheartBlue@etangc.om
One of the biggest goals of any 3D app is speed, you need to limit the actual rendering of triangles from beginning to end, whether you are sorting, culling, or hierarchical detail (LOD). If other methods are invalid, you want to simply improve the speed of the polygon, usually use OpenGL to provide optimization methods, the vertex array is a better way. Plus the most recent expansion named Vertex Buffer Object, which greatly improves the application's FPS. ARB_VERTEX_BUFFER's extension work and vertex arrays are very similar, except, ARB_VERTX_Buffer loads data to high performance memory of the graphics card. It greatly reduces the rendering time. Of course, this extension is relying on newer hardware, not all graphics cards are supported, so we must use some techniques to balance.
In this tutorial, we will:
1: Load data from the height figure.
2: Use the vertex array more efficient to submit grid data to OpenGL.
3: Load the data into high-performance memory via VBO (Vertex_Buffer_Object).
Now let's define the parameters of several applications.
#define mesh_resolution 4.0f // Variety each vertex corresponds to several pixels
#define mesh_heightscale 1.0F // Mesh Height Scale
// # Define no_vbos // If defined, forced not using VBO
The two parameters starting are defined for high graphs. The first defines the resolution of each pixel in the high graph. The latter is set to load the scale of the vertical direction in the vertical direction. The third constant, if you are defined, it will be forced to use VBO.
Next, we will define the constant, data type, and function pointers of VBO extensions.
// VBO extension definition from Glext.h
#define GL_Array_buffer_arb 0x8892
#define gl_static_draw_arb 0x88E4
TypeDef void (Apientry * PfnglbindBuffRBProc) (GLENUM TARGET, GLUINT BUFFER);
TypeDef void (Apientry * PfngldeleteBuffrsarbproc) (Glsizei N, Const Gluint * Buffers);
TypeDef void (Apientry * PfngnGlgenBuffersarbproc) (Glsizei N, Gluint * Buffers);
TypeDef void (Apientry * PfnglbufferDataArbproc) (Glenum Target, int size, glenum usage);
// VBO extension function pointer
PfnglgenBuffersarbProc GlgenBuffersarb = NULL; // VBO Name Generating Function
PfnglbindBuffRBProc glbindbuffrarb = null; // VBO binding function
Pfnglbufferdataarbproc glbufferdataarb = null; // VBO Data Load Function PfngLDeleteBuffersarbProc GLDELETEBUFFERSARB = NULL; // VBO Delete Function
I just contain the things you need for this demo program. If you need more other extensions, I suggest you download the latest Glext.h file in http://www.opengl.org and use the definition inside (this will make your program more than a certain extent Aesthetic). We will inquire into those functions we will use.
Now let's define basic mathematics objects, plus our own grid, all of which are a very simple design of this demo, I suggest that you have developed a math library.
Class CVert // Vertex
{
PUBLIC:
Float X; // x Component
Float y; // y company
Float z; // z company
}
TypeDef Cvert CVEC; // Defines CVEC and CVERT.
Class ctexcoord // texture coordinate class
{
PUBLIC:
Float u; // u Component
Float v; // v Component
}
Class Cmesh
{
PUBLIC:
// Grid data
INT m_nvertexcount; // Number of vertices
CVERT * m_pvertices; // vertex data
CTEXCOORD * m_PTEXCOORDS; // Texture coordinates
Unsigned int m_ntextureid; // Texture ID ID
// VBO's name
Unsigned int m_nvbovertices; // Vertex VBO's name
Unsigned int m_nvbotexcoords; // Texture coordinate VBO name
// Temporary data
AUX_RGBIMAGEREC * m_PTextureImage; // Data of high graph
PUBLIC:
Cmesh (); // Mesh Constructor
~ Cmesh (); // Mesh Deconstructor
// High graph load function
Bool Loadheightmap (Char * Szpath, Float Flheightscale, float flresolution);
// Get a height of a point
Float pTheight (int NX, int NY);
// Bind the VBO object.
Void Buildvbos ();
}
Most of the code is self-commented. Please note that I put the vertices and textures. This is not required, and I will give it to explain this.
Now let's define global variables. The first is that VBO is not supported flag variable, which will be set in the initialization code. Next is our grid object. And angle of rotating around Y axis. The variable used to calculate the FPS. I decided to write an extent that the FPS-based thing to display this code is optimized.
BOOL g_fvbosupported = false; // arb_vertex_buffer_Object is supported?
CMESH * g_pmesh = null; // Grid data
FLOAT G_FLYROT = 0.0f; // Rotate
INT g_nfps = 0, g_nframes = 0; // FPS and FPS counters
DWORD g_dwlastfps = 0; // Finally calculate the time of FPS
Let's directly the definition of the CMESH function, starting with LoadHeightmap. For those who have not been exposed to Heightmap, I can understand Heightmap, a HeightMap is a two-dimensional data group, usually an image, which specifies the height of the terrain grid in a vertical. There are many ways to achieve a high graph, but there is almost no perfect. My implementation method is to read data from a 3 channel (24bit) bitmap, using the illuminance algorithm to determine the height defined by the data, so regardless of the color image you use, the result is the same. So you can use color images to define a height map. Personal suggestion of four channels of images. Such as TGA, etc.. We can use its alpha channel to represent the height. However, just for this tutorial, I think a simple Bitmap is still the most appropriate. first of all. We determine that a high graph does not exist. if it exists. We use the GLAUX image load routine, which may be more useful to write your own image load routine, but this has exceeded the scope of this tutorial.
Bool Cmesh :: Loadheightmap (Char * Szpath, Float Flheightscale, Float Flresolution)
{
// Error-Checking
File * ftest = fopen (szpath, "r"); // Open the Image
IF (! ftest) // Make Sure It Was Found
Return False; // if not, the file is missing
Fclose (ftest); // done with the handle
// load texture data
m_ptextureiMage = auxdibimageload (szpath); // Utilize Glaux's Bitmap Load Routine
Now, things have become interesting. First of all, I want to point out that my high graph will generate three vertices for each triangle. The vertex is not shared with other triangles, I will explain it later why I do this, but you need to know this before the code.
I start calculating the total number of vertices in grid. The algorithm is like this: (Terrain Width / Resolution) * 3 Vertices in A Triangle * 2 Triangles In A Square). Then allocate the data and then populate the data.
// generate Vertex Field
m_nvertexcount = (int) (m_ptextureImage-> sizex * m_ptextureImage-> sizey * 6 / (flresolution * flresolution);
m_pvertices = new cVEC [m_nvertexcount]; // allocate Vertex Data
m_ptexcoords = new ctexcoord [m_nvertexcount]; // allocate tex coord data
INT NX, NZ, NTRI, NINDEX = 0; // Create Variables
Float Flx, FLZ;
For (NZ = 0; NZ
{
For (Nx = 0; NX
For (ntri = 0; NTRI <6; NTRI )
{
// Using this Quick Hack, Figure the x, z position of the point
FLX = (float) NX (ntri == 1 || ntri == 2 || ntri == 5)? flresolution: 0.0f);
FLZ = (FLOAT) NZ ((ntri == 2 || ntri == 4 || ntri == 5)? flresolution: 0.0f);
// set the data, using ptheight to obtain the y y value
m_pvertices [nindex] .x = flx - (m_ptextureImage-> sizex / 2);
M_pvertices [nindex] .y = ptheight ((int) FLX, (int) FLZ) * FlHeightscale;
M_pvertices [nindex] .z = flz - (m_ptextureImage-> sizey / 2);
// stretch the texture across the entire mesh
M_PTEXCOORDS [NINDEX] .u = flx / m_ptextureImage-> sizex;
M_PTEXCOORDS [NINDEX] .V = flz / m_ptextureImage-> sizey;
// increment ou Index
NINDEX ;
}
}
}
Finally, the texture of the high graph will be loaded into OpenGL. Then release the backup of our texture data. This is similar to the previous tutorial.
// load the texture Into OpenGL
GlGentextures (1, & m_ntextureid); // Get An Open ID
GlbindTexture (GL_Texture_2D, M_NTexture); // Bind The Texture
GLTexImage2D (GL_Texture_2D, 0, 3,
M_PTextureImage-> SIZEX, M_PTEXTUREIMAGE-> SIZEY, 0, GL_RGB,
GL_unsigned_byte, m_ptextureImage-> data);
GLTEXPARAMETERI (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
Gltexparameteri (GL_Texture_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// free the texture data
IF (M_PTextureImage)
{
IF (M_PTextureImage-> DATA)
Free (M_PTextureImage-> Data);
Free (m_ptextureImage);
}
Return True;
}
PTHEight This function is relatively simple. It calculates the height of the data that needs to be queried, and detects, and then calculates the height, the calculation of the brightness value is also very simple. You can see it, so I will not talk more.
FLOAT CMESH :: PTHEIGHT (int NX, int NY) {
// Calculate The Position in The Texture, Careful Not to overflow
INT NPOS = ((NX% M_PTextureImage-> SIZEX) ((NY% M_PTextureImage-> Sizey) * m_ptextureImage-> sizex) * 3;
FLOAT FLR = (FLOAT) m_ptextureImage-> data [npos]; // get the red company
Float flg = (float) m_ptextureImage-> data [npos 1]; // Get the green component
Float flb = (float) m_ptextureImage-> data [npos 2]; // Get the blue component
Return (0.299F * FLR 0.587F * FLG 0.114F * FLB); // Calculate The Height Using The Luminance Algorithm
}
Long live, now is the time to tell the vertex array and VBOS. What is Vertex Arrays, which is such a system, you can tell OpenGL, where is your vertex data, then divided into subsequent sequences to render, only a few function calls are required. The result of doing this is to reduce the call of the function (Glvertex, et al.), Increase the speed of the program. What is VBOS? Vertex Buffer Object uses high-speed graphics memory instead of ordinary system RAM memory. It not only reduces the memory operation of each frame, but also reduces the transmission between the data card and the CPU, in my experiment, VBO greatly increases the frame rate, rather than improving a little bit.
Now let's create a Vertex Buffer Objects. In fact, there are two methods to do, one of which is a method called "mapping". I think the easiest way is also the best way. The process is as follows: First use GLGENBUFFERSARB to generate a "name" that available VBO, substantially, one name is a ID number, OpenGL uses this ID to associate your data. Because a number does not necessarily represent an effective VBO name. Then, we bind VBO objects through the GlbindBuffRB function and activate it. Finally, we load the data into the graphics card, which can be implemented through glBufferDataArB, passing the data pointer and size into in, GlbufferDataAarb will copy your data to the graphics memory, which means that we don't have to maintain this data, So we can delete it (data in memory)
Void cmesh :: buildvbos ()
{
// generate and bind the Vertex Buffer
GLGENBUFFERSARB (1, & M_nvbovertices); // Get a Valid Name
GLBINDBUFFERARB (GL_ARRAY_BUFFER_ARB, M_NVBOVERTICES); // Bind The Buffer
// load the data
GlbufferDataArb (GL_Array_buffer_arb,
m_nvertexcount * 3 * sizeof (float), m_pvertices, gl_static_draw_arb;
// generate and bind the texture coordinate buffer
Glgenbuffersarb (1, & m_nvbotexcoords); // Get a Valid Name
GLBINDBUFFERARB (GL_ARRAY_BUFFER_ARB, M_NVBOTEXCOORDS); // Bind The Buffer
// load the data
GlbufferDataArb (GL_Array_buffer_arb,
m_nvertexcount * 2 * sizeof (float),
M_PTEXCOORDS, GL_STATIC_DRAW_ARB);
// Our Copy of The Data IS No Longer Necessary, IT IS Safe In The Graphics Card
Delete [] m_pvertices; m_pvertices = null;
DELETE [] M_PTEXCOORDS; M_PTEXCOORDS = NULL;
}
All right. It is time to initialize. We allocate memory and load data, then we test GL_ARB_VERTEX_Buffer_Object is not support, if support, we get a pointer to all VBO extended functions through the WGlgetProcadDress function, and then build our VBO object. note. If VBO is not supported, we will keep data like usually, please also note that the forced shutdown VBOS mentioned earlier.
// load the mesh data
g_pmesh = new cmesh (); // instantiateur mesh
IF (! g_pmesh-> loadingheightmap ("terrain.bmp", // load ur hothtmap
Mesh_heightscale, Mesh_Resolution))))
{
MessageBox (Null, "Error Loading Heightmap", "Error", MB_OK;
Return False;
}
// Check for VBOS Supported
#ifndef no_vbos
g_fvbosupported = ISEXtensionsionsUpported ("GL_ARB_VERTEX_Buffer_Object");
IF (g_fvbosupported)
{
// Get Pointers to the GL Functions
GlgenBuffersarb = (PfnglgenBuffersarbproc) WGLGetProcaddress ("GlgenBuffersarb");
GlbindBuffRB = (PfnglbindBuffRBProc) WGLGetProcaddress ("GlbindBufferB");
GlbufferDataArb = (pfnglbufferdataarbproc) WGLGetProcaddress ("GlbufferDataArb");
GLDELETEBUFFERSARB = (PfngldeleteBuffersarbproc) WGlgetProcaddress ("GldeleteBuffersarb);
// load Vertex Data Into the Graphics Card Memory
g_pmesh-> buildvbos (); // build the vbos}
#ELSE / * NO_VBOS * /
g_fvbosupported = false;
#ENDIF
ISEXtensionsionSupported is a function that can be obtained from OpenGL.org, and the change in my function is: It is clearer with my vulgar point of view.
Bool ISEXtensionsionSupported (Char * SztargeTextension)
{
Const unsigned char * pszextensions = null;
Const UNSIGNED Char * Pszstart;
Unsigned char * pszwhere, * pszterminator;
// Extension Names SHOULD NOT HAVE SPACES
Pszwhere = (unsigned char *) strchr (sztargetextension, '');
IF (pszwhere || * sztargetextension == '/ 0')
Return False;
// Get Extensions String
Pszextensions = Glgetstring (GL_EXTENSIONS);
// search the extensions String for an exact copy
Pszstart = pszextensions;
For (;;)
{
Pszwhere = (unsigned char *) strstr (const char *) pszstart, sztargetextension;
IF (! pszwhere)
Break;
PSZTERMINATOR = PSZwhere Strlen (SztargeTextension);
IF (pszwhere == pszstart || * (pszwhere - 1) == ')
IF (* pszterminator == '|| * pszterminator ==' / 0 ')
Return True;
Pszstart = pszterminator;
}
Return False;
}
This function is relatively simple. Some people use strstr to search for sub-strings, but obvious OpenGL.org does not trust this function, I don't agree with them.
Basically all do it. What we have to do is to render the data.
Void Draw (void)
{
GLCLEAR (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_bit); // Clear Screen and Depth Buffer
GLLoadIdentity (); // reset the modelview matrix
// Get FPS
IF (gettickcount () - g_dwlastfps> = 1000) // When A Second Has Passed ...
{
g_dwlastfps = gettickcount (); // Update Our Time Variable
g_nfps = g_nframes; // save the fps
g_nframes = 0; // reset the fps counter
CHAR SZTITLE [256] = {0}; // Build The Title Stringsprintf (Sztitle, "Lesson 45: Nehe & Paul Frazee's VBO Tut -% D Triangles,% D fps",
g_pmesh-> m_nvertexcount / 3, g_nfps);
IF (g_fvbosupported) // incrude a notice about VBOS
Strcat (Sztitle, ", Using VBOS");
Else
STRCAT (SZTITLE, ", NOT USING VBOS");
SetWindowText (g_window-> hwnd, sztitle); // set the title
}
g_nframes ; // increment OUR FPS Counter
//Move the camera
GLTranslateF (0.0F, -220.0F, 0.0F); // Move Above the Terrain
GLROTATEF (10.0F, 1.0F, 0.0F, 0.0F); // Look Down Slightly
GLROTATEF (G_FlyRot, 0.0F, 1.0F, 0.0F); // Rotate The Camera
Quite simple, every second, we have saved the value of the frame counter as the value of the FPS. Then clear the frame counter. Then, we move Camera on Terrain (if you change the high graph, maybe you need to adjust him), and do some rotation, g_flyrot is incremented by each Update call.
To use vertex arrays (and VBOS), you need to tell OpenGL what kind of data you want to provide, so the first step is to open the client's GL_VERTEX_ARRAY and GL_TEXTURE_COORD_ARRAY two status, then I will come to set our data pointer, I want to Unless you have multiple Mesh objects, you don't have to do this when you render each frame. But this relationship is not big, so I don't think it is a big problem.
To set a pointer for a specific data type, you need to use the corresponding function - GlvertExPointer and GLTEXCOORDPOINTER, which is very simple in our example, the total number of variables in the vertex (a vertex has 2, one texture coordinates have 2 ), Data type (FLOAT), Stride (if the vertex is not stored in a continuous data structure), and the pointer of the data, you can also use GlinterleaveDarrays, and put all data Save in a large buffer, but I chose to separate the data, so that better demonstrates how to use multiple VBOS.
Speaking of VBOS. It is not too different, the only difference is the supplied data pointer. After we bind a VBO, you can set the data pointer to null. please look below:
// set Pointers to Our Data
IF (g_fvbosupported)
{
GLBINDBUFFERARB (GL_Array_buffer_arb, g_pmesh-> m_nvbovertices);
GlvertexPointer (3, GL_FLOAT, 0, (Char *) NULL); // SET The Vertex Pointer to the Vertex BufferglbindBuffRB (GL_Array_buffer_arb, g_pmesh-> m_nvbotexcoords);
Gltexcoordpointer (2, GL_FLOAT, 0, (Char *) NULL); // set the texcoord pointer to the texcoord buffer
Else
{
GlvertExPointer (3, GL_FLOAT, 0, G_PMESH-> m_pvertices); // set the vertex pointer to our Vertex Data
GLTEXCOORDPOINTER (2, gl_float, 0, g_pmesh-> m_ptexcoords); // set the vertex pointer to our texcoord data
}
Rendering is very easy.
// render
GLDRAWARRAYS (GL_TRIANGLES, 0, G_PMESH-> m_nvertexcount); // Draw All of the Triangles AtCe
Here we use GLDRAWARRAYS to give the data to OpenGL.GLDRAWARRAYS to detect which client status is activated, then use its pointer to render. We tell OpenGL geometric primitives, starting from that index, and how many vertices are rendered. There are many other ways to submit data to render. Such as GlaRrayElement, but this is the fastest way. You will notice that GLDRAWARRAYS is not between Glbegin and Glend. Because there is no need.
GLDRAWARRAYS is why I choose to share the vertices between triangles. Because sharing is impossible, the best way to optimize memory usage is to use Triangle Strips, but it is not the range of this tutorial. Maybe you still realize that you want to specify a vector for each vertex, which means you have to use the vector, each with a companion vector, if you calculate the vector to improve the visual authenticity of the rendering results for each vertex.
Now that we have to do is to close the vertex array, here, we will end.
// disable pointers
GLDISABLECLIENTATATE (GL_VERTEX_ARRAY); // Disable Vertex Arrays
GLDISABLECLIENTSTATE (GL_TEXTURE_COORD_ARRAY); // Disable Texture Coord Arrays
}