Reducing buffer object size - opengl

What i have now
#define QUAD_VERT_COUNT 4
#define QUAD_POS_COMP 3
typedef struct quad_pos
{
GLfloat x, y, z;
}quad_pos;
#define SIZE_QUAD_POS = sizeof(quad_pos) * QUAD_VERT_COUNT
static QUAD_BUFFER = 0;
void init_quad_buffer()
{
quad_pos* pos_data = malloc(SIZE_QUAD_POS);
pos_data[0].x = -1.0f;
pos_data[0].y = -1.0f;
pos_data[0].z = 0.0f;
pos_data[1].x = 1.0f;
pos_data[1].y = -1.0f;
pos_data[1].z = 0.0f;
pos_data[2].x = -1.0f;
pos_data[2].y = 1.0f;
pos_data[2].z = 0.0f;
pos_data[3].x = 1.0f;
pos_data[3].y = 1.0f;
pos_data[3].z = 0.0f;
QUAD_BUFFER = create_buffer(GL_ARRAY_BUFFER, GL_STATIC_DRAW, pos_data, SIZE_QUAD_POS);
free(pos_data);
}
void get_quad_buffer
{
return QUAD_BUFFER;
}
And drawning (part of it)
glBindBuffer(GL_ARRAY_BUFFER, get_quad_buffer());
glEnableVertexAttribArray(ss->attrib[0]);//attrib[o] is vertex pos
glVertexAttribPointer(ss->attrib[0], QUAD_POS_COMP, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, QUAD_VERT_COUNT);
Scaling, translating and rotating achieved with matrices and shaders, so yes this buffer never changes for every sprite.
But why we need to use GL_float for just -1.0, 1.0? GL_Byte will be enough.
typedef struct quad_pos
{
GLbyte x, y, z;
}quad_pos;
void init_quad_buffer()
{
quad_pos* pos_data = malloc(SIZE_QUAD_POS);
pos_data[0].x = -1;
pos_data[0].y = -1;
pos_data[0].z = 0;
....
}
Drawning
...
glVertexAttribPointer(ss->attrib[0], QUAD_POS_COMP, GL_BYTE, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, QUAD_VERT_COUNT);
Question 1: Do i need normalize set to GL_TRUE?
Question 2: GLclampf and GLfloat both 4 byted floats, but color values are from 0.0 to 1.0 so if i put them in GLbyte too (val/256, so 255 for 1.0, 128 for 0.5, 0 for 0) do i need GL_TRUE for normalize in glVertexAttribPointer?
Question 3: Do i really need padding in vertex data/other data? Adding fictitious pos_data.g, just for sizeof(pos_data) = 16 == Good for gpu?

In general, you could always aim for the half-float (16bit float) extensions to save memory.
Your implementation looks like causing some draw-call overhead. Normalizing (on the fly!) will cause additional overhead. For drawing multiple instances of this constant quad, I recommend the following to speed things up:
Implementation of a geometry-shader; let it generate, transform and emit the 4 vertices of the quad for you.
instanced drawing with a transform-buffer using a texture buffer object (TBO) containing the transform matrices for each quad instance (each matrix column will be accessed using the builtin uniform 'gl_InstanceID').
OR:
Supply the matrices via vertex attribute arrays (probably faster).
These two approaches can be implemented upon the same buffer data layout (just an array of matrices)

But why we need to use GL_float for just -1.0, 1.0? GL_Byte will be enough.
Please note this is not true in general, in most cases you will need a float for precision. And if you only have so few values and so simple geometry, the odds are quite high that there even isn't a reason at all to optimize it to glByte in the first place. You likely have very few vertices at all, so why would you want to save storage on them? This sounds like a very good example of premature optimization (I know, it's an overused term).
Now, for your actual questions:
No, not if you want the same functionality, if normalize is false, the -1 will convert to -1.0f, if it is true it will be more something like -0.0078125f (or -1/128.0f). So if you want to keep the same scale, you don't want it normalized.
Where do you get the idea that GLclampf and GLfloat are 8-byte floats? They are usually 4 byte floats. If you want to pass in RGB colors through vertex attributes, yes you should normalize them as OpenGL expects color components to be in the range [0.0f,1.0f]. But again: why don't you simply pass them as floats? What do you think to gain? In a simple game you probably have not enough colors to notice the difference and in a non-simple game you're more likely to be using textures.
Of this I am not sure. I know it was true for old GPU's (and I mean almost 10y back), but I don't know of any recent claims that this would actually improve something. And in any case, the best-known alignment was to prop all vertex-attributes for one vertex together into (a multiple of) 32 bytes, and that was for ATI cards. Byte alignment might be necessary for some trickier things/extensions, but I do not think you need to worry about it just yet.

Related

OpenGL Terrain System, small height difference between GPU and CPU

A quick summary:
I've a simple Quad tree based terrain rendering system that builds terrain patches which then sample a heightmap in the vertex shader to determine the height of each vertex.
The exact same calculation is done on the CPU for object placement and co.
Super straightforward, but now after adding some systems to procedurally place objects I've discovered that they seem to be misplaced by just a small amount. To debug this I render a few crosses as single models over the terrain. The crosses (red, green, blue lines) represent the height read from the CPU. While the terrain mesh uses a shader to translate the vertices.
(I've also added a simple odd/even gap over each height value to rule out a simple offset issue. So those ugly cliffs are expected, the submerged crosses are the issue)
I'm explicitly using GL_NEAREST to be able to display the "raw" height value:
As you can see the crosses are sometimes submerged under the terrain instead of representing its exact height.
The heightmap is just a simple array of floats on the CPU and on the GPU.
How the data is stored
A simple vector<float> which is uploaded into a GL_RGB32F GL_FLOAT buffer. The floats are not normalized and my terrain usually contains values between -100 and 500.
How is the data accessed in the shader
I've tried a few things to rule out errors, the inital:
vec2 terrain_heightmap_uv(vec2 position, Heightmap heightmap)
{
return (position + heightmap.world_offset) / heightmap.size;
}
float terrain_read_height(vec2 position, Heightmap heightmap)
{
return textureLod(heightmap.heightmap, terrain_heightmap_uv(position, heightmap), 0).r;
}
Basics of the vertex shader (the full shader code is very long, so I've extracted the part that actually reads the height):
void main()
{
vec4 world_position = a_model * vec4(a_position, 1.0);
vec4 final_position = world_position;
// snap vertex to grid
final_position.x = floor(world_position.x / a_quad_grid) * a_quad_grid;
final_position.z = floor(world_position.z / a_quad_grid) * a_quad_grid;
final_position.y = terrain_read_height(final_position.xz, heightmap);
gl_Position = projection * view * final_position;
}
To ensure the slightly different way the position is determined I tested it using hardcoded values that are identical to how C++ reads the height:
return texelFetch(heightmap.heightmap, ivec2((position / 8) + vec2(1024, 1024)), 0).r;
Which gives the exact same result...
How is the data accessed in the application
In C++ the height is read like this:
inline float get_local_height_safe(uint32_t x, uint32_t y)
{
// this macro simply clips x and y to the heightmap bounds
// it does not interfer with the result
BB_TERRAIN_HEIGHTMAP_BOUND_XY_TO_SAFE;
uint32_t i = (y * _size1d) + x;
return buffer->data[i];
}
inline float get_height_raw(glm::vec2 position)
{
position = position + world_offset;
uint32_t x = static_cast<int>(position.x);
uint32_t y = static_cast<int>(position.y);
return get_local_height_safe(x, y);
}
float BB::Terrain::get_height(const glm::vec3 position)
{
return heightmap->get_height_raw({position.x / heightmap_unit_scale, position.z / heightmap_unit_scale});
}
What have I tried:
Comparing the Buffers
I've dumped the first few hundred values from the vector. And compared it with the floating point buffer uploaded to the GPU using Nvidia Nsight, they are equal, rounding/precision errors there.
Sampling method
I've tried texture, textureLod and texelFetch to rule out some issue there, they all give me the same result.
Rounding
The super strange thing, when I round all the height values. They are perfectly aligned which just screams floating point precision issues.
Position snapping
I've tried rounding, flooring and ceiling the position, to ensure the position always maps to the same texel. I also tried adding an epsilon offset to rule out a positional precision error (probably stupid because the terrain is stable...)
Heightmap sizes
I've tried various heightmaps, also of different sizes.
Heightmap patterns
I've created a heightmap containing a pattern to ensure the position is not just offsetet.

How to draw points in random locations but within screen or window boundaries

I am using OpenGL library in my Visual C++ application where I want to draw say, 100 points in random locations and I would like to check if these points random co-ordinates or random locations that generated are within the screen or window boundaries. I tired using a (x,y,z) vertex option and I get the points vertical running along a line. If I try generating only (x,y) and drawing them then I do get a lot more points scattered but definitely not all 100 within the window dimensions.
my code looks something like this:
GLfloat dots_vert[99];
for (int i = 0; i < 99; i++){
if (i % 2 == 0)
dots_vert[i] = 0.0f;
else
dots_vert[i] = ((GLfloat)rand() / (GLfloat)RAND_MAX)*100.0f - (100.0f / 2);
}
glEnable(GL_POINT_SMOOTH);
glPointSize(3.0f);
glEnableClientState(GL_VERTEX_ARRAY);
GLuint vbo_ID;
glGenBuffers(1, &vbo_ID);
glBindBuffer(GL_ARRAY_BUFFER, vbo_ID);
glBufferData(GL_ARRAY_BUFFER, sizeof(dots_vert), dots_vert, GL_DYNAMIC_DRAW);
while (!GetAsyncKeyState(VK_DOWN)){
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo_ID);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glDrawArrays(GL_POINTS, 0, 100);
SwapBuffers(g_pOpenGLWindow->hDC);
Let me guide you through the glaring mistakes I can immediately see in that code.
First of all the obvious first mistake: you claim to be drawing 100 points but your dots_vert array is only 99 elements long. This is repeated in the following loop, where you go from 0 to 98 for a total of 99 times.
So first of all:
GLfloat dots_vert[100];
for (int i = 0; i < 100; ++i)
{
[...]
}
There is another huge mistake in there but we'll keep that for later, let's move on for now.
The second mistake is about the knowledge of the OpenGL API and computer graphics. First of all, your goal is to pass points to the GPU, so you need the glVertexAttribPointer function, that much you figured out. The absolute first thing you wanna do is to look at the glVertexAttribPointer reference documentation, so you have an idea of what you need. You need an index, a size, a type, a normalized flag, a stride and an offset.
Let's look at what the reference documentation says about the size parameter:
size
Specifies the number of components per generic vertex attribute. Must be 1, 2, 3, 4. Additionally, the symbolic constant GL_BGRA is accepted by glVertexAttribPointer. The initial value is 4.
This is immediately obvious to be crucial in determining what kind of data you're trying to pass to the GPU. You set the parameter to 3, which means that you have an x, a y and a z. But the previous code contradicts this. For starters, your dots_vert array is 100 elements long, and you want to draw 100 points, so you have enough for 100/100 = 1 component per point, not 3. But even worse, the inside of the for loop contradicts this even further, so let's go back and check the mistake I mentioned previously.
Mistake number three: your for loop consists of an if {} else {} statement, where you set the current element of the dots_vert array to a value of 0.0f if the index of the loop is even (if (i % 2 == 0)), and a random value between -50.0f and 50.0f otherwise. Assuming 1 component per point, this means that you're only generating the x coordinates, so you're working in a single dimension.
Clearly this is not what you intended to do, also because half of your points will be 0.0f and therefore they'll all overlap. So I assume you were trying to generate a random value for x and y, and set z to 0.0f, which would make much more sense. First of all, you have 3 components per point and therefore you'll need an array with 100*3 = 300 elements. So first of all, let's fix the previous code:
GLfloat dots_vert[300];
for (int i = 0; i < 300; ++i)
{
[...]
}
Much better. Now we need to generate a random x and y valye for each point, and set z to 0.0f since we don't need it. You wanna do all of the components at once in a single loop, so you want your loop to step by 3, not 1, so once again let's fix the previous code:
GLfloat dots_vert[300];
for (int i = 0; i < 300; i += 3)
{
[...]
}
Now we can generate x, y and z together in a single loop. This is the crucial part where understanding how computer graphics work, specifically in the context of the OpenGL API. OpenGL uses a coordinate system where the origin is in the middle of the screen, the x axis moves horizontally (positive x points to your right), the y axis moves vertically (positive y points up), and the z axis goes straight through the screen (positive z points out of the screen, towards you). Now this is the very important part: x, y and z are clipped to a specific range of values; anything outside of this range is ignored. For all coordinates, the range goes from -1.0f to 1.0f. Anything below of above that is not drawn at all.
So if you want to have 100 points to be inside the screen, ignoring projection which is outside of the scope of this exercise, you want to generate x and y in the -1.0f to 1.0f range, not -50.0f to 50.0f like you're doing there. You can keep z to 0.0f, doesn't really matter in this case. This is why most of your points fall outside of the screen: with that range, statistically speaking, around 98% of your points will fall outside of the clip space and will be ignored.
So ultimately this is what you want:
GLfloat dots_vert[300];
for (int i = 0; i < 300; i += 3)
{
dots_vert[i] = ((GLfloat)rand() / (GLfloat)RAND_MAX)*2.0f - 1.0f; // this is x
dots_vert[i+1] = ((GLfloat)rand() / (GLfloat)RAND_MAX)*2.0f - 1.0f; // this is y
dots_vert[i+2] = 0.0f; // this is z
}
Finally a reminder: when you do glDrawArrays(GL_POINTS, 0, 100); you're telling the GPU to draw 100 points. Each point is made of however many components you specified in the size parameter of the glVertexAttribPointer function. In this case you wanna draw 100 points, each point is made of 3 components, so the GPU expects an array of 100*3 = 300 floats. numbers. Anything less could result in either a segmentation fault or even worse an undefined behavior (which means anything can happen), so pay close attention to what you're doing and make sure you know exactly what kind of data you're passing to the GPU because you might end up with a nonsense result and you'll be stuck trying to figure out what went wrong. In this case, you have basically no code at all to check so it's easy to fix, but when you'll end up with a decent amount of code (and you will eventually), an error like this could mean hours or even days wasted trying to find the error.
As a bonus, feel free to ignore this one: technically a point is made of 4 components. This component is called w and its use is outside of the scope of this exercise so don't worry about it, just remember that it should always be set to 1.0f, unless you are doing projection.
So technically you could do this too:
GLfloat dots_vert[400];
for (int i = 0; i < 400; i += 4)
{
dots_vert[i] = ((GLfloat)rand() / (GLfloat)RAND_MAX)*2.0f - 1.0f; // this is x
dots_vert[i+1] = ((GLfloat)rand() / (GLfloat)RAND_MAX)*2.0f - 1.0f; // this is y
dots_vert[i+2] = 0.0f; // this is z
dots_vert[i+3] = 1.0f; // this is w
}
Then you set the size parameter of glVertexAttribPointer to 4 instead of 3, the result should be exactly the same.

OpenGL Uniform Buffer Object (Array) Skipping Every Other Vec2

Problem
There should not be gaps between those white lines. Those lines are composed of squares (eventually I won't be generating just another giant square; this is for debugging). For some reason when I send data through my Uniform Buffer Object (example below), I am getting gaps. It's almost as if it's skipping every other y value. There are actually two squares on each location instead of there being one at (y) and one at (y + 1).
Code Snippets
Generating data pointer array
blockData = new glm::vec2[24*24];
for (int x = 0; x < 24; x++) {
for (int y = 0; y < 24; y++) {
int i = x * 24 + y;
blockData[i] = glm::vec2(x, y);
}
}
In the rendering class
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::vec2) * blocksActive, blockData);
glBindBufferRange(GL_UNIFORM_BUFFER, uniBlockData, ubo, 0, sizeof(glm::vec2) * blocksActive);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindVertexArray(vao);
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0, blocksActive);
glBindVertexArray(0);
Vertex Shader (GLSL)
layout (std140) uniform blockData {
vec2 blockDataPosition[5184];
};
Testing
When I change blockData[i] = glm::vec2(x, y); to blockData[i] = glm::vec2(y, x); (switching y and x), the gaps move to the x-axis.
I have tried switching the x and the y in the for loop, but it does not affect it. This issue is somehow linked to the y variable.
What does affect it is if I switch the x and y around in int i = x * 24 + y;
Setting the vec2 to (x, x) results in a correctly placed diagonal.
Setting the vec2 to (y, y) results in an oddly placed diagonal (below)
Before switching to a UBO, I was just using a uniform in the shader and it worked fine. That is why I believe it has something to do with my sending of data through the UBO.
Well so what is happening is most likely an alignment issue. Using std140, you must align for 4 floats. However, without seeing all of your code, its not going to be possible for me to be completely certain how you are buffering your data. What I can suggest, however, is to query your block offsets using glGetActiveUniformsiv, and then align your objects based on that. So a typical example would be this :
char* bufferNames[] = [generate the values with a for loop, this should be pretty easy with string and sprintf e.g "blockData.blockDataPosition[0]" to "block.Data.blockDataPosition[5184]"];
GLuint uniformBlockIndex = glGetUniformBlockIndex(yourprogram, "block name");
GLuint uniformIndices[number of names in bufferNames];
glGetUniformIndices(yourprogram, the number of names in bufferNames, bufferNames, indices);
GLint uniformOffsets[the number of names in bufferNames];
glGetActiveUniformsiv(yourprogram, 1, &uniformIndices, GL_UNIFORM_OFFSET, uniformOffsets);
The offsets will tell you everything you need to know. They are the number of offset in bytes for every name. And you can use a for loop to fill each offset, or just print it out to see how everything is aligned. This should not be done in a loop, but once for initialization and such.
Tell me if you have any questions or you need a more specific example.

OpenGL quality difference between glDrawElements and immediate mode

I'm working for the first time on a 3D project (actually, I'm programming a Bullet Physics integration in a Quartz Composer plug-in), and as I try to optimize my rendering method, I began to use glDrawElements instead of the direct access to vertices by glVertex3d...
I'm very surprised by the result. I didn't check if it is actually quicker, but I tried on this very simple scene below. And, from my point of view, the rendering is really better in immediate mode.
The "draw elements" method keep showing the edges of the triangles and a very ugly shadow on the cube.
I would really appreciate some information on this difference, and may be a way to keep quality with glDrawElements. I'm aware that it could really be a mistake of mines...
Immediate mode
DrawElements
The vertices, indices and normals are computed the same way in the two method. Here are the 2 codes.
Immediate mode
glBegin (GL_TRIANGLES);
int si=36;
for (int i=0;i<si;i+=3)
{
const btVector3& v1 = verticesArray[indicesArray[i]];;
const btVector3& v2 = verticesArray[indicesArray[i+1]];
const btVector3& v3 = verticesArray[indicesArray[i+2]];
btVector3 normal = (v1-v3).cross(v1-v2);
normal.normalize ();
glNormal3f(-normal.getX(),-normal.getY(),-normal.getZ());
glVertex3f (v1.x(), v1.y(), v1.z());
glVertex3f (v2.x(), v2.y(), v2.z());
glVertex3f (v3.x(), v3.y(), v3.z());
}
glEnd();
glDrawElements
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, sizeof(btVector3), &(normalsArray[0].getX()));
glVertexPointer(3, GL_FLOAT, sizeof(btVector3), &(verticesArray[0].getX()));
glDrawElements(GL_TRIANGLES, indicesCount, GL_UNSIGNED_BYTE, indicesArray);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
Thank you.
EDIT
Here is the code for the vertices / indices / normals
GLubyte indicesArray[] = {
0,1,2,
3,2,1,
4,0,6,
6,0,2,
5,1,4,
4,1,0,
7,3,1,
7,1,5,
5,4,7,
7,4,6,
7,2,3,
7,6,2 };
btVector3 verticesArray[] = {
btVector3(halfExtent[0], halfExtent[1], halfExtent[2]),
btVector3(-halfExtent[0], halfExtent[1], halfExtent[2]),
btVector3(halfExtent[0], -halfExtent[1], halfExtent[2]),
btVector3(-halfExtent[0], -halfExtent[1], halfExtent[2]),
btVector3(halfExtent[0], halfExtent[1], -halfExtent[2]),
btVector3(-halfExtent[0], halfExtent[1], -halfExtent[2]),
btVector3(halfExtent[0], -halfExtent[1], -halfExtent[2]),
btVector3(-halfExtent[0], -halfExtent[1], -halfExtent[2])
};
indicesCount = sizeof(indicesArray);
verticesCount = sizeof(verticesArray);
btVector3 normalsArray[verticesCount];
int j = 0;
for (int i = 0; i < verticesCount * 3; i += 3)
{
const btVector3& v1 = verticesArray[indicesArray[i]];;
const btVector3& v2 = verticesArray[indicesArray[i+1]];
const btVector3& v3 = verticesArray[indicesArray[i+2]];
btVector3 normal = (v1-v3).cross(v1-v2);
normal.normalize ();
normalsArray[j] = btVector3(-normal.getX(), -normal.getY(), -normal.getZ());
j++;
}
You can (and will) achieve the exact same results with immediate mode and vertex array based rendering. Your images suggest that you got your normals wrong. As you did not include the code with which you create your arrays, I can only guess what might be wrong. One thing I could imagine: you are using one normal per triangle, so in the normal array, you have to repeat that normal for each vertex.
You should be aware that a vertex in the GL is not just the position (which you specify via glVertex in immediate mode), but the set of all attributes like position, normals, texcoords and so on. So if you have a mesh where an end point is part of different triangles, this is only one vertex if all attributes are shared, not just the position. In your case, the normals are per triangle, so you will need different vertices (sharing position with some other vertices, but using a different normal) per triangle.
I began to use glDrawElements
Good!
instead of the direct access to vertices by glVertex3d...
There's nothing "direct" about immediate mode. In fact it's as far away from the GPU as you can get (on modern GPU architectures).
I'm very surprised by the result. I didn't check if it is actually quicker, but I tried on this very simple scene below. And, from my point of view, the rendering is really better with the direct access method.
Actually its several orders of magnitudes slower. Each and every glVertex call causes the overhead of a context switch. Also a GPU needs larger batches of data to work efficiently, so glVertex calls first fill a buffer created ad-hoc.
Your immediate code segment must be actually understand as following
glNormal3f(-normal.getX(),-normal.getY(),-normal.getZ());
glVertex3f (v1.x(), v1.y(), v1.z());
// implicit copy of the glNormal supplied above
glVertex3f (v2.x(), v2.y(), v2.z());
// implicit copy of the glNormal supplied above
glVertex3f (v3.x(), v3.y(), v3.z());
The reason for that is, that a vertex is not just a position, but the whole combination of its attributes. And when working with vertex arrays you must supply the full attribute vector to form a valid vertex.

Rendering sprites from spritesheet with OpenGL?

Imagine the following scenario: you have a set of RPG character spritesheets in PNG format and you want to use them in an OpenGL application.
The separate characters are (usually) 16 by 24 pixels in size (that is, 24 pixels tall) and may be at any width and height without leaving padding. Kinda like this:
(source: kafuka.org)
I already have the code to determine an integer-based clipping rectangle given a frame index and size:
int framesPerRow = sheet.Width / cellWidth;
int framesPerColumn = sheet.Height / cellHeight;
framesTotal = framesPerRow * framesPerColumn;
int left = frameIndex % framesPerRow;
int top = frameIndex / framesPerRow;
//Clipping rect's width and height are obviously cellWidth and cellHeight.
Running this code with frameIndex = 11, cellWidth = 16, cellHeight = 24 would return a cliprect (32, 24)-(48, 48) assuming it's Right/Bottom opposed to Width/Height.
The actual question
Now, given a clipping rectangle and an X/Y coordinate to place the sprite on, how do I draw this in OpenGL? Having the zero coordinate in the top left is preferred.
You have to start thinking in "texture space" where the coordinates are in the range [0, 1].
So if you have a sprite sheet:
class SpriteSheet {
int spriteWidth, spriteHeight;
int texWidth, texHeight;
int tex;
public:
SpriteSheet(int t, int tW, int tH, int sW, int sH)
: tex(t), texWidth(tW), texHeight(tH), spriteWidth(sW), spriteHeight(sH)
{}
void drawSprite(float posX, float posY, int frameIndex);
};
All you have to do is submit both vertices and texture vertices to OpenGL:
void SpriteSheet::drawSprite(float posX, float posY, int frameIndex) {
const float verts[] = {
posX, posY,
posX + spriteWidth, posY,
posX + spriteWidth, posY + spriteHeight,
posX, posY + spriteHeight
};
const float tw = float(spriteWidth) / texWidth;
const float th = float(spriteHeight) / texHeight;
const int numPerRow = texWidth / spriteWidth;
const float tx = (frameIndex % numPerRow) * tw;
const float ty = (frameIndex / numPerRow + 1) * th;
const float texVerts[] = {
tx, ty,
tx + tw, ty,
tx + tw, ty + th,
tx, ty + th
};
// ... Bind the texture, enable the proper arrays
glVertexPointer(2, GL_FLOAT, verts);
glTexCoordPointer(2, GL_FLOAT, texVerts);
glDrawArrays(GL_TRI_STRIP, 0, 4);
}
};
Franks solution is already very good.
Just a (very important) sidenote, since some of the comments suggested otherwise.
Please don't ever use glBegin/glEnd.
Don't ever tell someone to use it.
The only time it is OK to use glBegin/glEnd is in your very first OpenGL program.
Arrays are not much harder to handle, but...
... they are faster.
... they will still work with newer OpenGL versions.
... they will work with GLES.
... loading them from files is much easier.
I'm assuming you're learning OpenGL and only needs to get this to work somehow. If you need raw speed, there's shaders and vertex buffers and all sorts of both neat and complicated things.
The simplest way is to load the PNG into a texture (assuming you have the ability to load images into memory, you do need htat), then draw it with a quad setting appropriate texture coordinates (they go from 0 to 1 with floating point coordinates, so you need to divide by texture width or height accordingly).
Use glBegin(GL_QUADS), glTexcoord2f(), glVertex2f(), glEnd() for the simplest (but not fastest) way to draw this.
For making zero top left, either use gluOrtho() to set up the view matrix differently from normal GL (look up the docs for that function, set top to 0 and bottom to 1 or screen_height if you want integer coords) or just make change your drawing loop and just do glVertex2f(x/screen_width, 1-y/screen_height).
There are better and faster ways to do this, but this is probably one of the easiest if you're learning raw OpenGL from scratch.
A suggestion, if I may. I use SDL to load my textures, so what I did is :
1. I loaded the texture
2. I determined how to separate the spritesheet into separate sprites.
3. I split them into separate surfaces
4. I make a texture for each one (I have a sprite class to manage them).
5. Free the surfaces.
This takes more time (obviously) on loading, but pays of later.
This way it's a lot easier (and faster), as you only have to calculate the index of the texture you want to display, and then display it. Then, you can scale/translate it as you like and call a display list to render it to whatever you want. Or, you could do it in immediate mode, either works :)