Tutorials
Articles
OpenGL Demos
Games
OpenGL Misc
MSG Board
About
Donate
Links
Home
Megabyte Softworks
C++, OpenGL, Algorithms




Current series: OpenGL 3.3
(Return to list of OpenGL 3.3 tutorials)

Download (139 KB)
5837 downloads. 10 comments
5.) Indexed Drawing

Hello there! This is the 5th OpenGL 3.3 tutorial. In this one, we will try to save some memory with indexed drawing. However, this might not always be our best bet, as we will see.

Indexed Drawing

In previous tutorial, we could see, that when rendering pyramid, we copied the same pieces of data over and over again. Now if we'd like to change the pyramid somehow (move one of its vertices for example), we would have to change the data in several place, or better said, in every copied instance of the vertex. And that's just not good. What we will try to do now, is to create a list of UNIQUE vertices, and then tell OpenGL indices, instead of real data, so it can dereference them to obtain real data.

For now, we leave the pyramid, and take a better object for purposes of this tutorial - a simple 4x4 heightmap. We will return to pyramid in the end, with conclusion, whether it has a meaning to do indexed drawing for it too.

Heightmap

Heightmap is probably the simplest way to render terrains and surfaces. As input, we have a map of heights (hence heightmap), that are distributed usually over square grid. In this tutorial, we will create a simple 4x4 heightmap manually, to see the advantage of indexed drawing (notice, that 4x4 means the size of grid, not the number of quads in heightmap - if we want to render at least one quad made from two triangles, our heightmap must be at least 2x2). These are the control points of our heightmap:

The picture above shows a wireframe render of our simple heightmap. Now imagine, if we rendered the heightmap using glDrawArrays and GL_TRIANGLES rendering mode. Some vertices would be copied only once (the corner vertices), vertices on the boundaries of the heightmap (except corner ones) would be copied thrice (3x LULZ) and the ones inside even 4x! That's a real waste of space, and if we were to update vertex data during the runtime (like programming earthquakes), then updating data would really kill us. A lot better option is to use triangle strips, when rendering heightmaps. The order of rendering using triangle strips will be like this (numbers mean the order in which the vertices are sent to a pipeline):
We can see with ease, that now we require a lot less space. Now each vertex is copied max twice, which is a lot better. But that's not the best solution. You may guess, that we want to have a separate list of vertices, and then tell OpenGL not vertices, but indices of vertices we want to send to rendering pipeline. That's the indexed drawing. To draw indirectly, not telling the exact values, but rather to tell where they are stored. Now if we change the vertex data in vertices list, they will change everywhere they're used! The indices will stay the same, so updating things is now easy!

Now how to render whole heightmap: for each rendered row in heightmap, we begin with a new triangle strip. There are multiple numbers around some grid points. It just means, that they are sent more times (max twice) to the pipeline. With each new row, we must start a new triangle strip. So we are left with two options - either create a for loop, that will call rendering function (which is glDrawElements, will be explained soon), or we can somehow tell OpenGL, that after sending 8 vertices, which form a full row in heightmap, we want to RESTART our drawing. And luckily, there's a way of doing it. It's called glPrimitiveRestartIndex. This function has one parameter - index value, that doesn't address any vertex, but rather tells OpenGL, that we want to restart our drawing (like in old times calling glEnd() and then glBegin()). So when we create a list of indices in our 4x4 heightmap, numbers 0-15 will represent vertices and number 16 will represent our primitive restart index.

We want to render 3 rows with triangle strips. Each row consists of sending down eight vertices, or in this case, telling OpenGL which eight vertices to send, and after that, we want to restart primitives. This results in making list of 8x3 + 3 (for restarting) - resulting in 27 indices. A little save of space can be achieved, if we don't restart primitive after rendering last row (there's no need), saving one index, making total of 26 indices. Of course, storing indices on GPU also requires some space, but it's a LOT less than copying vertex data over and over again, and it also eases our lifes by making updating of data on-the-fly very convenient.

Now let's have a look at initializing heightmap data and indices:


glBindVertexArray(uiVAOHeightmap);
glBindBuffer(GL_ARRAY_BUFFER, uiVBOHeightmapData);

glm::vec3 vHeightmapData[HM_SIZE_X*HM_SIZE_Y]; // Here the heightmap vertices will be stored temporarily

float fHeights[HM_SIZE_X*HM_SIZE_Y] =
{
   4.0f, 2.0f, 3.0f, 1.0f,
   3.0f, 5.0f, 8.0f, 2.0f,
   7.0f, 10.0f, 12.0f, 6.0f,
   4.0f, 6.0f, 8.0f, 3.0f
};

float fSizeX = 40.0f, fSizeZ = 40.0f;

FOR(i, HM_SIZE_X*HM_SIZE_Y)
{
   float x = float(i%HM_SIZE_X), z = float(i/HM_SIZE_X);
   vHeightmapData[i] = glm::vec3(
      -fSizeX/2+fSizeX*x/float(HM_SIZE_X-1), // X Coordinate
      fHeights[i],                           // Y Coordinate (it's height)
      -fSizeZ/2+fSizeZ*z/float(HM_SIZE_Y-1)   // Z Coordinate
      );
}

glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3)*HM_SIZE_X*HM_SIZE_Y, vHeightmapData, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

First we create VAO and VBO as always. Next, we have manually written heightmap heights. It has 4x4 = 16 values. In the following for loop, we just calculate vertices positions. We want our whole heightmap to be centered in its model coordinates, and we want its X size to be 40.0 (float fSizeX) and its Z size to be 40.0 as well, so that it will form a square (float fSizeZ). Now how to center it? Let's examine this for X axis (Z will be analogous). Calculation for X coordinate is this: Xcoord = -fSizeX/2+fSizeX*column/float(HM_SIZE_X-1). First, we move by half of total size to the left, and then we want to linearly move along the full size, depending on which column we are setting. Then we divide it by maximal possible column value, it's 4-1 = 3 (columns are numbered from 0 to 3). We have previously calculated column as i % HM_SIZE_X, for increasing values of i, it will give us numbers 0, 1, 2, 3, 0, 1, 2, 3... Hope you got it, it's good if you understand this, I know this may be trivial thing for pros, but for newcomers, the code above may seem scary, but after thinking of it for a while, the anxiety should disappear LULZ. We do the analogous thing for Z coordinates, and Y coordinates are the heights defined previously. After all's been said and done, we can upload vertex data to GPU.

Now we must deal with indices. We create a VBO for that. Notice, that we don't use GL_ARRAY_BUFFER, but GL_ELEMENT_BUFFER instead. It tells OpenGL that this buffer stores indices. Then we create (in this case manually) list of indices. Have a look at the code:


glGenBuffers(1, &uiVBOIndices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, uiVBOIndices);
int iIndices[] =
{
   0, 4, 1, 5, 2, 6, 3, 7, 16, // First row, then restart
   4, 8, 5, 9, 6, 10, 7, 11, 16, // Second row, then restart
   8, 12, 9, 13, 10, 14, 11, 15 // Third row, no restart
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(iIndices), iIndices, GL_STATIC_DRAW);
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(HM_SIZE_X*HM_SIZE_Y);

As you can see, after each row there's number 16, that tells OpenGL to restart drawing. With indices ready, we upload them to GPU, and within binded VAO, these indices will be active indices. It means, that after binding this VAO and rendering using indexed drawing, these will be the indices OpenGL reads. Then we enable primitive restarting, and set restart index to 16 with glPrimitiveRestartIndex. Now we have everything set up, and we are ready to proceed with rendering.


glm::mat4 mModelView = glm::lookAt(glm::vec3(0, 60, 30), glm::vec3(0, 0, 0), glm::vec3(0.0f, 1.0f, 0.0f));

glm::mat4 mCurrent = glm::rotate(mModelView, fRotationAngle, glm::vec3(0.0f, 1.0f, 0.0f));
glUniformMatrix4fv(iModelViewLoc, 1, GL_FALSE, glm::value_ptr(mCurrent));
glBindVertexArray(uiVAOHeightmap);
glDrawElements(GL_TRIANGLE_STRIP, HM_SIZE_X*(HM_SIZE_Y-1)*2+HM_SIZE_Y-1, GL_UNSIGNED_INT, 0);

fRotationAngle += appMain.sof(30.0f);

The main change in rendering after binding VAO is the rendering function. Now we don't call glDrawArrays, but glDrawElements instead. Now the vertices are pulled from binded index array. Number of indices (the second parameter) is number_of_rows (HM_SIZE_Y-1) times number_of_indices_per_row (HM_SIZE_X*2) + number_of_restarts (HM_SIZE_Y-1 is number of rows, and minus additional one for one saved restart at the end, resulting in HM_SIZE_Y-2). We tell OpenGL that indices are of unsigned integer type, which is partially true, because we use integers, not unsigned integers, but as long as the vertex count doesn't exceed 2^31 - 1, we don't need to worry LULZ.

There are also slight changes in shaders. We removed the input variable color, since we don't provide it, but rather we calculate the color of fragment depending on fragment's Y position. We can see that the greatest height in our heightmap is 12.0, so we just interpolate vertex position among fragments, and depending we divide it by 12.0. This gives us a slight shadow feel.

!!! Important !!!

There is however one important thing we need to make clear. Vertex position is a vertex attribute. Another attributes can be color, texture coordinates and so on. But we can have only one index array for ALL vertex attributes. We cannot have separate indices for positions, colors or texture coordinates. This makes indexed rendering not appropriate for rendering some objects. In case of heightmap, it's perfect choice (or rendering closed surfaces or skin). But for objects, like our pyramid from last tutorial, it's not much of a good choice, because there are vertices with same position, but different colors. We would have to make a new vertex for each such distinct combination. And this doesn't make things easier for sure.

Going fullscreen

This is piece of code, located in win_OpenGLApp.cpp, that deals with creating window in fullscreen mode:


bool COpenGLWinApp::createWindow(string sTitle)
{
   if(MessageBox(NULL, "Would you like to run in fullscreen?", "Fullscreen", MB_ICONQUESTION | MB_YESNO) == IDYES)
   {
      DEVMODE dmSettings = {0};
      EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmSettings); // Get current display settings

      hWnd = CreateWindowEx(0, sAppName.c_str(), sTitle.c_str(), WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, // This is the commonly used style for fullscreen
      0, 0, dmSettings.dmPelsWidth, dmSettings.dmPelsHeight, NULL,
      NULL, hInstance, NULL);
   }
   else hWnd = CreateWindowEx(0, sAppName.c_str(), sTitle.c_str(), WS_OVERLAPPEDWINDOW | WS_MAXIMIZE | WS_CLIPCHILDREN,
      0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL,
      NULL, hInstance, NULL);

   // ... function continues
}

Basically, we just ask user if he wants fullscreen mode, and if his answer is yes, then we will get current display settings, using EnumDisplaySettings function, and we will create window with size of screen resolution (dmPelsWidth and dmPelsHeight). We also change window style - we don't want any borders or title bar, we just want a plain, flat window, that will cover whole screen. And window style combination WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN is most suitable for that.

Finally...

The result of all the above magic is this:

And that's all for today. Hope you learned something from this tutorial, and stay tuned for the next tutorial, in which we will cover loading textures and applying them to our objects.



Download (139 KB)
5837 downloads. 10 comments
 
Name:

E-mail:
(Optional)
Entry:

Enter the text from image:



Smileys




Fvtjjtounse (vregsdvsdref@aol.com) on 28.09.2017 09:35:20
cash advance payday loans online <a href="http://cashadvanceamericasev.org/">payday loans</a> online loans no credit [url=http://cashadvanceamericasev.org/]payday loans[/url] ’
Matthewsnips (matthewtieda@erotraff.top) on 21.09.2017 15:47:08
buy prescription drugs from canada
<a href="http://canadianpharmacyrxbsl.com/">canadian pharmacy</a>
mail order pharmacies
<a href=http://canadianpharmacyrxbsl.com/?buy-cialis-online>buy cialis online</a>
canada drugs pharmacy
<a href="http://canadianpharmacyrxbsl.com/?metoprolol-100-mg">metoprolol 100 mg</a>
RalphBoync (ralphshier@canadianpharmacyseo.us) on 05.09.2017 22:36:33
combine cialis and viagra

<a href="http://cialisxrm.com/">cialis generic</a>

cialis rezeptfrei wien

<a href=http://cialisxrm.com/>buy cialis online</a>
Georgetrist (kedrakecz12@outlook.com) on 16.08.2017 21:17:06
I very like your website and I recomend it to my friends, if you want your can look at my website <a href="http://dziw.eu/metaleszlachetne3/">http://trafne-zdjecia.pl/przemysl-i-budownictwo/skup-metali-szlachetnych-coraz-bardziej-popularny/ </a> You will love this website.
GymGuyon (dejabradshaw@diamondfacade.net) on 21.07.2017 13:38:04
Have a person been feeling tired almost all the day, fatigued, slower or maybe just experience bloated, it might be a good time to take into consideration a natural body cleansing. You even generate toxins in the <a href=http://1i1.io/4>raspberry ketones cleanse</a> body every time from normal metabolic procedures. Visit their detoxification diet website for tools and guidelines for generating a safe, effective and gentle detox diet that's perfect for you.
Anytime you hear of the particular detox diet plan, you more than likely start thinking about cleaning your own body of toxic substances. These devices leave a lot of radiation inside our bodies, which needless to say increases the risk associated with cancer. I have got a very simple remedy for you, ladies: extremely simple detox, and this means eating detox super foods. The most <a href=http://go.faks.us/UEon1>do raspberry ketones really work for weight loss</a> common and natural approach to start a body detoxing is to stop the bad habits and kick start a detox diet, nonetheless it is important to consult your physician before you decide to kick start a body cleansing plan, as it ought to usually be carried out there under the advice in addition to monitor of a medical practitioner.
Detox diets have already been proven to have several health benefits and are strongly recommended. The particular B vitamins, Vitamin C, chromium, and zinc can sort out sugar withdrawal symptoms. Also you can have <a href=http://go.faks.us/GctL2>obsession formula review</a> got breads as well since quickly pull in order for you, yet stay clear of having butter. Detox diet programs normally emphasizes on including unprocessed foods as properly as adding fresh veggies and fruits.
If you have a busy lifestyle you will probably find oneself reaching out for and buying sugary meals due in order to the convenience; however, when you have planned a new non-sugar menu of snack foods packed at home a person are very likely to stick in order to a sugar detoxification diet <a href=http://go.spikeseo.top/mBKwD>slimming garcinia cambogia</a> plan. The sudden dash of sugar into your blood vessels stream can give an individual an quick energy increase but with time your body becomes depleted of vitamins, your metabolism becomes ineffective, you suffer from lack of energy and bad weight regulation.
Once all these harmful toxins get accumulated in the body, they can deplete our body of energy and make us even more susceptible to different type of health problems. Organic detox tools and remedies, like dry skin cleaning <a href=http://go.spikeseo.top/U58JP>does green tea detox your body</a> and detox baths, can be used to ease detox symptoms and enhance effective detoxification. The body feels even more energetic and for that will reason you continue problems away.
Cefvexia (Eliggesy@protop-mail.eu) on 17.07.2017 04:27:16
<a href=http://kosmetyki-profesjonalne.eu/kosmetyki-homeopatyczne/>http://kosmetyki-profesjonalne.eu/kosmetyki-homeopatyczne/</a> - profesjonalne kosmetyki.
nikola (futog@mail.ru) on 15.01.2015 04:23:20
glEnable(GL_PRIMITIVE_RESTART);
you forgot to disable it
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(HM_SIZE_X*HM_SIZE_Y);
glDisable(GL_PRIMITIVE_RESTART);

if you try to render something else without disable it...
Sundaram on 06.04.2014 21:52:24
thanks for the tutorial amidst your busy schedule. I'm learning from these :)
CarstenT on 14.12.2013 22:12:14
GREAT!
Got hung up on the exact same setup I eventually found my error here (bytesize vs elementCount ;o/)
So releaved ..
seo plugin (odmqqq@gmail.com) on 24.05.2017 15:22:42
Hello Web Admin, I noticed that your On-Page SEO is is missing a few factors, for one you do not use all three H tags in your post, also I notice that you are not using bold or italics properly in your SEO optimization. On-Page SEO means more now than ever since the new Google update: Panda. No longer are backlinks and simply pinging or sending out a RSS feed the key to getting Google PageRank or Alexa Rankings, You now NEED On-Page SEO. So what is good On-Page SEO?First your keyword must appear in the title.Then it must appear in the URL.You have to optimize your keyword and make sure that it has a nice keyword density of 3-5% in your article with relevant LSI (Latent Semantic Indexing). Then you should spread all H1,H2,H3 tags in your article.Your Keyword should appear in your first paragraph and in the last sentence of the page. You should have relevant usage of Bold and italics of your keyword.There should be one internal link to a page on your blog and you should have one image with an alt tag that has your keyword....wait there's even more Now what if i told you there was a simple Wordpress plugin that does all the On-Page SEO, and automatically for you? That's right AUTOMATICALLY, just watch this 4minute video for more information at. <a href="http://www.SEORankingLinks.xyz">Seo Plugin</a>
seo plugin http://www.SEORankingLinks.xyz
Jump to page:
1