PDA

View Full Version : Trying to render a heightmap as a VBO, no error just not showing up.



Exempt
08-14-2013, 05:50 PM
Edit: I'm trying to use opengl 2.1 to avoid shaders for now.

I can render a simple triangle without issue as a vbo but when I made this function and try to render a height map it doesn't display anything. I'm guess it's something to do with trying to fill the data with a vector<float> but not to sure.

It's probably something simple but I'm pretty new to opengl and 3d programming in general -.-, thanks for any help.

I load a bmp image (gray scale) and just fill a vector<float> like so..

std::vector<float> loadHeightMap(const char* fileName, float tileSize, float maxHeight)
{
std::vector<float> vec;
SDL_Surface* img = SDL_LoadBMP(fileName);
if(!img)
{
std::cout<<"Failed to load " << fileName << "." << std::endl;
return vec;
}

for(int i = 0; i < img->h; i++)
{
for(int j = 0; j < img->w; j++)
{
Uint32 pixel = ((Uint32*)img->pixels)[i * img->pitch/4 + j];
Uint8 r, g, b;
SDL_GetRGB(pixel, img->format, &r, &g, &b);

vec.push_back((float)i * tileSize);
vec.push_back((float)(r / 255) * maxHeight);
vec.push_back((float)j * tileSize);
}
}
SDL_FreeSurface(img);

return vec;
};

Init the vbo...

glGenBuffers(1, &vboModel);
glBindBuffer(GL_ARRAY_BUFFER, vboModel);
glBufferData(GL_ARRAY_BUFFER, heightMap.size(), heightMap.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

and finally I try to draw the vbo

glBindBuffer(GL_ARRAY_BUFFER, vboModel);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, NULL);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);

Edit: Well I see why it doesn't display anything cause i'm not actually making tryings filling the vector like I currently am... Guess i'll have to think more lol.

I edited the height map function to actually make vertice but I see only 1 triangle.


std::vector<float> loadHeightMap(const char* fileName, float tileSize, float maxHeight)
{
std::vector<float> vec;
SDL_Surface* img = SDL_LoadBMP(fileName);
if(!img)
{
std::cout<<"Failed to load " << fileName << "." << std::endl;
return vec;
}

for(int i = 0; i < img->h; i++)
{
for(int j = 0; j < img->w; j++)
{
Uint32 pixel = ((Uint32*)img->pixels)[i * img->pitch/4 + j];
Uint8 r, g, b;
SDL_GetRGB(pixel, img->format, &r, &g, &b);

vec.push_back((float)i * tileSize);
vec.push_back((float)(r / 255) * maxHeight);
vec.push_back((float)j * tileSize);

vec.push_back((float)i+1 * tileSize);
vec.push_back((float)(r / 255) * maxHeight);
vec.push_back((float)j * tileSize);

vec.push_back((float)i * tileSize);
vec.push_back((float)(r / 255) * maxHeight);
vec.push_back((float)j+1 * tileSize);
}
}
SDL_FreeSurface(img);

return vec;
};

edit: I changed

glDrawArrays(GL_TRIANGLES, 0, (heightMap.size())); and now I have a huge square but the height doesn't seem to be right... hmm

Edit: I changes the load height map function to load a 4th vertex and use triangle strips to render it... seems to work better but I still have no Z or height value...

Seems that r is also equal to 0... odd. r should be a range from 0-255.. the image I use has colors from black to near white all over it...

Edit: The issue is Uint32 pixel = ((Uint32*)img->pixels)[i * img->pitch / 4 + j]; ... seems that it doesn't return the correct pixel so I guess i'll have to find a new way to find the currect pixel.


Edit..again: Well I have it loading mountains and stuff now but... all the vertex are flat so it's got gaps lol... I see why it's happening but not sure of a solution just yet -.-

GClements
08-14-2013, 08:35 PM
I can render a simple triangle without issue as a vbo but when I made this function and try to render a height map it doesn't display anything. I'm guess it's something to do with trying to fill the data with a vector<float> but not to sure.



glBufferData(GL_ARRAY_BUFFER, heightMap.size(), heightMap.data(), GL_STATIC_DRAW);


heightMap.size() is the number of floats in the vector, but the second argument to glBufferData() is the number of bytes, so you're only uploading a quarter of the data.



I changes the load height map function to load a 4th vertex and use triangle strips to render it... seems to work better but I still have no Z or height value...





Uint8 r, g, b;
...
vec.push_back((float)(r / 255) * maxHeight);


As r and 255 are both integers, the division will yield an integer, so the value of r/255 will be either zero (if r<255) or one (if r==255). You need to convert at least one of the operands to the division to a float, e.g.


vec.push_back((r / 255.0f) * maxHeight);

Converting the result of the division to a float won't work; you're just converting 0 to 0.0f every time.

Exempt
08-15-2013, 12:17 PM
Edit: I changed this hole post because of some new changes it was pointless to leave it as it was with no replies to it.

I've got the terrain loading correctly side from it loading a flat plane over the map... I have no idea why as it shouldn't be there at all...

I init the vbo like so...

glGenBuffers(1, &vboModel);
glBindBuffer(GL_ARRAY_BUFFER, vboModel);
glBufferData(GL_ARRAY_BUFFER, heightMap.size()*sizeof(float), heightMap.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

I draw like so..


glBindBuffer(GL_ARRAY_BUFFER, vboModel);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, NULL);
glDrawArrays(GL_TRIANGLE_STRIP, 0, heightMap.size()*sizeof(float));
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);

my new loadHeightMap..

std::vector<float> loadHeightMap(const char* fileName, float tileSize, float maxHeight)
{
std::vector<float> vec;
std::vector<std::vector<float>> heights;
SDL_Surface* img = SDL_LoadBMP(fileName);
if(!img)
{
std::cout<<"Failed to load " << fileName << "." << std::endl;
return vec;
}



std::vector<float> tmp;
for(int i = 0; i < img->h; i++) //I load the heights into a vector<vector<float>> now for easy access.
{
tmp.clear();
for(int j = 0; j < img->w; j++)
{
Uint32 pixel = ((Uint32*)img->pixels)[i * img->pitch / 4 + j];
Uint8 r, g, b;
SDL_GetRGB(pixel, img->format, &r, &g, &b);

tmp.push_back((float)r/255.0);
}
heights.push_back(tmp);
}

for(int z = 0; z < heights.size()-1; z++) //I just use 2 vertice now for the triangle strip.
{
for(int x = 0; x < heights[0].size()-1; x++)
{
vec.push_back(x * tileSize);
vec.push_back(heights[x][z] * maxHeight);
vec.push_back(z * tileSize);

vec.push_back(x * tileSize);
vec.push_back(heights[x][z+1] * maxHeight);
vec.push_back(z+1 * tileSize);
}
}
SDL_FreeSurface(img);

return vec;
};

Edit: Could this be caused because I use triangle strips? I might need to make a new triangle strip for each row of vertex?

GClements
08-15-2013, 03:09 PM
Could this be caused because I use triangle strips?
Yes.

I might need to make a new triangle strip for each row of vertex?
Yes. Or use glDrawElements(GL_TRIANGLES) to draw it all in one go.

Exempt
08-15-2013, 03:13 PM
GL_TRIANGLES would require me to change my loader to add complete triangles though right?

GClements
08-15-2013, 03:19 PM
GL_TRIANGLES would require me to change my loader to add complete triangles though right?
With glDrawElements(), you'd just store each vertex as it's loaded, with no duplicates. The index array specifies which vertices form each triangle.

Exempt
08-15-2013, 03:21 PM
I will give it a go, thanks for the help. :)

Exempt
08-15-2013, 04:35 PM
Ok, I've got glDrawElements drawing... something. It's not drawing anything like what I had with gldDrawArrays though, it's just a huge blob.

I init like this..

unsigned int vboModelInd[] = {0, 1, 2};
GLuint vboModel, ind;

glGenBuffers(1, &vboModel);
glGenBuffers(1, &ind);
glBindBuffer(GL_ARRAY_BUFFER, vboModel);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ind);
glBufferData(GL_ARRAY_BUFFER, heightMap.size()*sizeof(float), heightMap.data(), GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vboModelInd), vboModelInd, GL_STATIC_DRAW);

I draw like this...

glBindBuffer(GL_ARRAY_BUFFER, vboModel);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ind);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, NULL);
glDrawElements(GL_TRIANGLES, heightMap.size()*sizeof(float), GL_UNSIGNED_INT, 0);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

I'm filling my data like this... I'm also tried with just adding 1 point at a time.

for(int z = 0; z < heights.size()-1; z++)
{
for(int x = 0; x < heights[0].size()-1; x++)
{
vec.push_back(x * tileSize);
vec.push_back(heights[x][z] * maxHeight);
vec.push_back(z * tileSize);

vec.push_back(x * tileSize);
vec.push_back(heights[x][z+1] * maxHeight);
vec.push_back(z+1 * tileSize);

vec.push_back(x+1 * tileSize);
vec.push_back(heights[x+1][z] * maxHeight);
vec.push_back(z * tileSize);
}
}

Edit: Hm, I guess all this has to be used with indexed drawing... Oh joy another confusing bunch of days to figure this junk out.

GClements
08-16-2013, 02:18 AM
Ok, I've got glDrawElements drawing... something. It's not drawing anything like what I had with gldDrawArrays though, it's just a huge blob.

I init like this..

unsigned int vboModelInd[] = {0, 1, 2};
The index array needs to have one element for each vertex (duplicated vertices will have duplicated indices). For an grid with M*N vertices ((M-1)*(N-1) squares) rendered using GL_TRIANGLES, you'll need 6*M*N indices (i.e. 2 triangles = 6 vertices per grid square).

The indices can be generated like:


std::vector<GLuint> index;
for (int row = 0; row < rows-1; row++) {
for (int col = 0; col < cols-1; col++) {
int i0 = row*cols+col;
int i1 = i0 + cols; // = (row+1)*cols+col

index.push_back(i0);
index.push_back(i0+1);
index.push_back(i1);

index.push_back(i1);
index.push_back(i1+1);
index.push_back(i0+1);
}
}


Essentially, glDrawArrays() is equivalent to glDrawElements() with an index array of [0,1,2,3,...]. glDrawArrays() is seldom useful when drawing triangles or quads, as you end up needing to duplicate vertices, which is more expensive than duplicating indices. It is useful with points (which have no topology) and sometimes with line strips/loops, but less useful with general wireframe meshes.

Exempt
08-16-2013, 09:27 AM
If I wanted to create a real index like 0, 1, 2, 3, 2, 1 to reuse vertice correctly I'd also have to remove the duplicated vertex from my data right?

I think I may go ahead and bump up to opengl 3.3 soon so I can use the primitive restart method but that's for triangle strips and I'm not sure why I need a index for triangle strips since really I just need the first 2 point and then just one point to continue the strip after that... I've read its faster with way but primative restart requires a index which seems pointless to me for triangle strips...

I'd try to test this but I've got no access to my pc atm so I'm just thinking out loud here.. :)

GClements
08-16-2013, 10:01 AM
If I wanted to create a real index like 0, 1, 2, 3, 2, 1 to reuse vertice correctly I'd also have to remove the duplicated vertex from my data right?
There would be no point in duplicating the vertices, but if they were present you could construct an index array which either ignored them or used them. The example code I posted assumes that the vertices are "packed", i.e. the vertex comprising elements 3*n,3*n+1,3*n+2 is for row (n/cols) and column (n%cols).


I think I may go ahead and bump up to opengl 3.3 soon so I can use the primitive restart method but that's for triangle strips and I'm not sure why I need a index for triangle strips since really I just need the first 2 point and then just one point to continue the strip after that...
If you try to render a grid using strips, you'll find that the end of each row doesn't work out; one of the last two vertices will be on the "far" side of the strip. Triangle strips/fans used to have a performance benefit on older hardware, but nowadays there isn't really any advantage beyond reducing the size of the index arrays.

The main problem with using glDrawArrays() for typical meshes is that you have to duplicate most of the interior vertices, which wastes memory and complicates matters if you need to modify vertices dynamically. It's more useful for points and line strips/loops.

Note that, for strips, you can get by without primitive restart by just repeating the first vertex of a new section, effectively linking the sections with degenerate triangles (their area will be zero, so nothing will be drawn). That won't work with fans, though (or with glPolygonMode(GL_LINE) if the sections are disjoint).

Exempt
08-16-2013, 10:11 AM
I see, so I just fill my data with complete triangles then i can just pass 0,1,2,3,4... As my index and it will work with glDrawElements.

Just to make sure I'm right, the index is just a single point right?

Edit: to optimize this more I could use indexing like i said before to reuse points for the triangles...

GClements
08-16-2013, 10:33 AM
I see, so I just fill my data with complete triangles then i can just pass 0,1,2,3,4... As my index and it will work with glDrawElements.

Just to make sure I'm right, the index is just a single point right?

The vertex data passed to glVertexPointer() is just the vertices, e.g. for a rectangular grid:

GLfloat vertices[rows*cols][3];

The ordering can be whatever is convenient, but it would make sense to use the same order in which you read the heights from the image file.

The index array is one GLubyte/GLushort/GLuint (depending upon how many bits you need) per vertex. You need as many indices for glDrawElements() as you would need complete vertices for glDrawArrays(). Rather than using vertex[first+0], vertex[first+1], etc, it uses vertex[indices[0]], vertex[indices[1]], etc. So instead of than duplicating or re-ordering vertices, you just duplicate or re-order the indices.

For a height map generated from an M*N image and rendered with GL_TRIANGLES, you'd have M*N vertices (M*N*3 floats) and 6*(M-1)*(N-1) indices. For GL_QUADS, the number of indices would be 4*(M-1)*(N-1). Using triangle strips could reduce the number of indices further, but there probably isn't a need unless M*N is huge (in which case, storing the heights as a texture and generating the vertex data in a vertex shader would provide much bigger savings).

Exempt
08-16-2013, 11:19 AM
What exactly do you mean by "packed" for data? Just removing all duplicate vertex?

edit:I'm starting from the bottom again and I can make glDrawElements work perfectly fine using this index and data.. IS this what you mean by packing the data?

unsigned int vboModelIndArray[] = {
0, 1, 2,
2, 1, 3,
2, 3, 4,
4, 3, 5,
4, 5, 6,
6, 5, 7,
6, 7, 8,
8, 7, 9
};
float modelData[] = {
0.0, 0.0, 0.0,
0.0, 0.0, 1.0,
1.0, 0.0, 0.0,
1.0, 0.0, 1.0,
2.0, 0.0, 0.0,
2.0, 0.0, 1.0,
3.0, 0.0, 0.0,
3.0, 0.0, 1.0,
4.0, 0.0, 0.0,
4.0, 0.0, 1.0
};

EDIT: After some thought I figured out how I could make the vertex listed like this but I'll have to create a function to return the index...

unsigned int vboModelIndArray[] = {
//first row
0, 5, 1,
1, 5, 6,
1, 6, 2,
2, 6, 7,
2, 7, 3,
3, 7, 8,
3, 8, 4,
4, 8, 9,
//second row
5, 10, 6,
6, 10, 11,
6, 11, 7,
7, 11, 12,
7, 12, 8,
8, 12, 13,
8, 13, 9,
9, 13, 14
};
float modelData[] = {
1.0, 0.0, 0.0,
2.0, 0.0, 0.0,
3.0, 0.0, 0.0,
4.0, 0.0, 0.0,
5.0, 0.0, 0.0,

1.0, 0.0, 1.0,
2.0, 0.0, 1.0,
3.0, 0.0, 1.0,
4.0, 0.0, 1.0,
5.0, 0.0, 1.0,

1.0, 0.0, 2.0,
2.0, 0.0, 2.0,
3.0, 0.0, 2.0,
4.0, 0.0, 2.0,
5.0, 0.0, 2.0,
};

Edit: I have a new question so I'll make a new thread on this since this is far off from the original topic.

GClements
08-16-2013, 11:42 PM
What exactly do you mean by "packed" for data? Just removing all duplicate vertex?
Yes. Just fill the vertex array with rows*cols vertices in "raster" order.



edit:I'm starting from the bottom again and I can make glDrawElements work perfectly fine using this index and data.. IS this what you mean by packing the data?
That looks correct.