PDA

View Full Version : Understanding VAO's/VBO's and drawing two objects.



michaelglaz
11-30-2014, 01:24 PM
I'm new to OpenGL 4. All the tutorials I've seen draw one object (usually a triangle) in the beginning. I'm having trouble understanding how VAO's and VBO's relate to an object. I want to draw two different objects: a spaceship (a triangle) and an asteroid(an octagon). I've figured out a way and was wondering if this is the correct way as I go further in my OpenGL adventures. I created one VAO and two VBO's (asteroid_buffer_object and ship_buffer_object). Then inside InitializeVertexBuffer() I'm generating the two buffer objects and binding them. Then in display() I'm again binding the asteroid_buffer_object, drawing it, and then binding ship_buffer_object and drawing it. So I'm wondering if this is correct or am I doing something redundant?




#include <algorithm>
#include <string>
#include <vector>
#include <cstdio>
#include <iostream>
#include <cmath>
#include <GL/glew.h>
#include <GL/freeglut.h>

using namespace std;

GLuint CreateShader(GLenum eShaderType, const std::string &strShaderFile)
{
GLuint shader = glCreateShader(eShaderType);
const char *strFileData = strShaderFile.c_str();
glShaderSource(shader, 1, &strFileData, NULL);

glCompileShader(shader);

GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
GLint infoLogLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);

GLchar *strInfoLog = new GLchar[infoLogLength + 1];
glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);

const char *strShaderType = NULL;
switch(eShaderType)
{
case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
}

fprintf(stderr, "Compile failure in %s shader:\n%s\n", strShaderType, strInfoLog);
delete[] strInfoLog;
}

return shader;
}

GLuint CreateProgram(const std::vector<GLuint> &shaderList)
{
GLuint program = glCreateProgram();

for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
glAttachShader(program, shaderList[iLoop]);

glLinkProgram(program);

GLint status;
glGetProgramiv (program, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
GLint infoLogLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

GLchar *strInfoLog = new GLchar[infoLogLength + 1];
glGetProgramInfoLog(program, infoLogLength, NULL, strInfoLog);
fprintf(stderr, "Linker failure: %s\n", strInfoLog);
delete[] strInfoLog;
}

for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
glDetachShader(program, shaderList[iLoop]);

return program;
}

GLuint theProgram;

const std::string strVertexShader(
"#version 330\n"
"layout(location = 0) in vec4 position;\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
"}\n"
);

const std::string strFragmentShader(
"#version 330\n"
"out vec4 outputColor;\n"
"void main()\n"
"{\n"
" outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);\n"
"}\n"
);

void InitializeProgram()
{
std::vector<GLuint> shaderList;

shaderList.push_back(CreateShader(GL_VERTEX_SHADER , strVertexShader));
shaderList.push_back(CreateShader(GL_FRAGMENT_SHAD ER, strFragmentShader));

theProgram = CreateProgram(shaderList);

std::for_each(shaderList.begin(), shaderList.end(), glDeleteShader);
}

const int num_asteroid_vertices = 8;
const int num_ship_vertices = 3;
GLfloat asteroid_vertices[num_asteroid_vertices][2];

void set_asteroid_vertices()
{
GLfloat angle = 0.0;
GLfloat r = 1.0;
for(int i=0; i < num_asteroid_vertices; i++)
{
angle = i * 2*M_PI/num_asteroid_vertices;
asteroid_vertices[i][0] = r*cos(angle);
asteroid_vertices[i][1] = r*sin(angle);
cout << r*cos(angle) << ", ";
cout << r*sin(angle) << endl;
}
}

GLfloat ship_vertices[][2] = {
{ -0.05f, -0.05f },
{ 0.05f, -0.05f },
{ 0.00f, 0.05f }
};

GLuint asteroid_buffer_object;
GLuint ship_buffer_object;
GLuint vao;


void InitializeVertexBuffer()
{
glGenBuffers(1, &asteroid_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

glGenBuffers(1, &ship_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(ship_vertices), ship_vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

}

//Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
void init()
{
set_asteroid_vertices();
InitializeProgram();
InitializeVertexBuffer();

glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
}

//Called to update the display.
//You should call glutSwapBuffers after all of your rendering to display what you rendered.
//If you need continuous updates of the screen, call glutPostRedisplay() at the end of the function.
void display()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);

glUseProgram(theProgram);

glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glDrawArrays(GL_LINE_LOOP, 0, num_asteroid_vertices);

glDisableVertexAttribArray(0);
glUseProgram(0);

glUseProgram(theProgram);

glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glDrawArrays(GL_LINE_LOOP, 0, num_ship_vertices);

glDisableVertexAttribArray(0);

glutSwapBuffers();
}

//Called whenever the window is resized. The new window size is given, in pixels.
//This is an opportunity to call glViewport or glScissor to keep up with the change in size.
void reshape (int w, int h)
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}

int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowSize(1000, 1000);
glutInitContextVersion(4, 3);
glutInitContextProfile(GLUT_CORE_PROFILE);
glutCreateWindow(argv[0]);

if (glewInit()) {
cerr << "Unable to initialize GLEW ... exiting" << endl;
exit(EXIT_FAILURE);
}

init();

glutDisplayFunc(display);

glutMainLoop();
}

GClements
12-01-2014, 06:42 AM
So I'm wondering if this is correct or am I doing something redundant?
You're not making use of VAOs. You create one VAO at start-up and leave it bound thereafter. This doesn't really provide you with any advantages over not using a VAO.

The basic way you use VAOs is e.g.:

Initialisation:


glGenVertexArrays(1, &asteroid_vao);
glBindVertexArray(asteroid_vao);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glGenVertexArrays(1, &ship_vao);
glBindVertexArray(ship_vao);
glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glBindVertexArray(0);


Rendering:


glUseProgram(theProgram);
glBindVertexArray(asteroid_vao);
glDrawArrays(GL_LINE_LOOP, 0, num_asteroid_vertices);
glBindVertexArray(ship_vao);
glDrawArrays(GL_LINE_LOOP, 0, num_ship_vertices);
glBindVertexArray(0);
glUseProgram(0);


A VAO stores the state set by the glBindBuffer(GL_ARRAY_BUFFER), glEnableVertexAttribArray, and glVertexAttribPointer calls (as well as a few others, e.g. glVertexAttribDivisor and glBindBuffer(GL_ELEMENT_ARRAY_BUFFER)). So you can use a single glBindVertexArray call instead of many individual calls. If you have multiple attributes, this becomes significant.

However: if the number of objects is large (much larger than one ship and a few asteroids), it's important to draw "batches" rather than individual objects. Typically, you wouldn't have a separate attribute array (or group of arrays) for each "object". You'd have one set of arrays containing the data for many objects which could conceivably be rendered in a single draw call.

If you have many objects in one set of arrays, you can still draw a subset of them by varying the offset/start/count/etc parameters in the draw calls (and/or using draw calls with more parameters, e.g. glMultiDrawElements, glDrawElementsIndirect, etc). But if the objects are spread across multiple sets of attribute arrays, you're forced to use multiple draw calls.

michaelglaz
12-01-2014, 07:50 PM
Why aren't these 2 called, like they are in the Red Book:

glGenBuffers(1, &asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);

GClements
12-02-2014, 09:56 AM
Why aren't these 2 called, like they are in the Red Book:

glGenBuffers(1, &asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);

Your code already has those, so I didn't duplicate it. The code to initialise the VAO should be added to the end of InitializeVertexBuffer(). It assumes that the VBOs already exist and contain data.

michaelglaz
12-04-2014, 11:33 AM
Your code already has those, so I didn't duplicate it. The code to initialise the VAO should be added to the end of InitializeVertexBuffer(). It assumes that the VBOs already exist and contain data.

Can you show me where these two lines go exactly?

GClements
12-04-2014, 01:59 PM
Can you show me where these two lines go exactly?
This is one way to do it:


void InitializeVertexBuffer()
{
glGenBuffers(1, &asteroid_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &asteroid_vao);
glBindVertexArray(asteroid_vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glGenBuffers(1, &ship_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(ship_vertices), ship_vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &ship_vao);
glBindVertexArray(ship_vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}

michaelglaz
12-07-2014, 09:07 PM
One more question. How do I draw ten asteroids? I have this so far:



struct asteroid
{
GLfloat asteroid_vertices[num_asteroid_vertices][2];
};

void set_asteroid_vertices()
{
GLfloat angle = 0.0;
GLfloat r = 0.25;

srand(time(NULL));

for(vector<asteroid>::size_type i=0; i != num_asteroids; i++)
{
asteroid a;

float x = (1 + rand() % 200 / 100.0) - 2;
float y = (1 + rand() % 200 / 100.0) - 2;

for(int j=0; j < num_asteroid_vertices; j++)
{
angle = j * 2*M_PI/num_asteroid_vertices;

a.asteroid_vertices[j][0] = r*cos(angle) + x;
a.asteroid_vertices[j][1] = r*sin(angle) + y;
}
asteroids.push_back(a);
}
}

void display()
{
...
glDrawArrays(GL_LINE_LOOP, 0, num_asteroid_vertices*asteroids.size());
...
}



But I don't know how to have the line loop end at the 8th vertex.

GClements
12-07-2014, 11:29 PM
But I don't know how to have the line loop end at the 8th vertex.
One option is to use GL_LINES, using indexed drawing (glDrawElements()) so you don't have to duplicate each vertex. The index array would look like

{0,1, 1,2, 2,3, 3,4, 4,5, 5,6, 6,7, 7,0, 8,9, 9,10, ....}

The other options require a relatively recent version of OpenGL.

The first is primitive restart (glEnable(GL_PRIMITIVE_RESTART) and glPrimitiveRestartIndex()). This also requires indexed rendering but can be used with GL_LINE_LOOP. Assuming the primitive restart index has been set to 9999, the index array would look like e.g.

[0,1,2,3,4,5,6,7,9999,8,9,10,....]

Essentially, the primitive restart index marks the boundary between primitives, enabling multiple loops, strips or fans to be drawn in a single call.

Another option is instanced rendering, i.e. glDrawArraysInstanced(). But for something as simple as asteroids, that's a bit like using a sledgehammer to crack a nut.

Essentially, you'd have two attribute arrays: one containing num_asteroids positions (which are the origin of each asteroid), and one containing num_asteroid_vertices vertices (which are the asteroid vertices relative to its origin). A vertex shader would add the two together to generate the actual vertex coordinates.

The main disadvantage is the added complexity. The main advantages are the memory saving (you only need num_asteroids + num_asteroid_vertices coordinate pairs, rather than num_asteroids * num_asteroid_vertices) and the fact that you only need to update one position to move an asteroid rather than needing to update all of its vertices.

michaelglaz
12-08-2014, 10:41 AM
I have the latest version of OpenGL. Which method would you recommend? I don't care if it's a sledgehammer method - I'm just interested in learning what will be useful for me down the road.

GClements
12-08-2014, 01:19 PM
I'm just interested in learning what will be useful for me down the road.
Then try all of them.

The first two approaches (lines and primitive restart) could be done fairly quickly, instanced rendering requires learning how to create and use shaders (which you'll want to do sooner or later).

michaelglaz
12-21-2014, 12:34 PM
So I added the following code to create 2 asteroids:



vector<GLuint> asteroid_indices {0,1, 1,2, 2,3, 3,4, 4,5, 5,6, 6,7, 7,0, 8,9, 9,10, 10,11, 11,12, 12,13, 13,14, 14,15, 8 };

GLuint IndexBufferId;





void InitializeVertexBuffer()
{
glGenBuffers(1, &IndexBufferId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(asteroid_indices), &asteroid_indices[0], GL_STATIC_DRAW);

...
}




void display()
{
...

glDrawElements(GL_LINES, 16, GL_UNSIGNED_INT, 0);

...
}


But this doesn't seem to work and the red book is not very specific on this.

GClements
12-22-2014, 04:41 AM
vector<GLuint> asteroid_indices {0,1, 1,2, 2,3, 3,4, 4,5, 5,6, 6,7, 7,0, 8,9, 9,10, 10,11, 11,12, 12,13, 13,14, 14,15, 8 };


The end of the array should be "14,15, 15,8};", i.e. the 15 should appear twice.





glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(asteroid_indices), &asteroid_indices[0], GL_STATIC_DRAW);


sizeof(asteroid_indices) will return the compile-time size of the std::vector structure, which doesn't include the size of the contained data (which is in a dynamically-allocated array referenced from the structure). You need e.g.


asteroid_indices.size() * sizeof(asteroid_indices[0])

The sizeof(...) part could be sizeof(GLuint), but using an expression is robust against changes to the type of asteroid_indices.





glDrawElements(GL_LINES, 16, GL_UNSIGNED_INT, 0);


The second argument to glDrawElements() is the number of indices (32 in this case), not the number of primitives (lines). I suggest using asteroid_indices.size() here.

michaelglaz
12-27-2014, 05:11 PM
Hmm, the asteroids aren't being rendered. Here's my InitializeVertexBuffer() and display() function:



void InitializeVertexBuffer()
{
glGenBuffers(1, &IndexBufferId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, asteroid_indices.size() * sizeof(asteroid_indices[0]), &asteroid_indices[0], GL_STATIC_DRAW);

glGenBuffers(1, &asteroid_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid)*asteroids.size(), &asteroids[0], GL_STATIC_DRAW);
cout << sizeof(asteroid) << endl;
cout << asteroids.size() << endl;
cout << sizeof(asteroid)*asteroids.size() << endl;
glGenVertexArrays(1, &asteroid_vao);
glBindVertexArray(asteroid_vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glGenBuffers(1, &ship_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(ship_vertices), ship_vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &ship_vao);
glBindVertexArray(ship_vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}




void display()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);

glUseProgram(theProgram);
glBindVertexArray(asteroid_vao);
glBindVertexArray(IndexBufferId);

glDrawElements(GL_LINES, asteroid_indices.size(), GL_UNSIGNED_INT, 0);

glBindVertexArray(ship_vao);
glDrawArrays(GL_LINE_LOOP, 0, num_ship_vertices);

glBindVertexArray(0);
glUseProgram(0);

glutSwapBuffers();
}

GClements
12-28-2014, 02:14 AM
glBindVertexArray(asteroid_vao);
glBindVertexArray(IndexBufferId);


The second call should be to glBindBuffer(), not glBindVertexArray(), i.e.


glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);

michaelglaz
01-24-2015, 03:27 PM
Then try all of them.

The first two approaches (lines and primitive restart) could be done fairly quickly, instanced rendering requires learning how to create and use shaders (which you'll want to do sooner or later).

I used GL_LINES with indexed drawing (glDrawElements) but I notice that this approach and the second one you mention (primitive restart (glEnable(GL_PRIMITIVE_RESTART) and glPrimitiveRestartIndex) require an array of indices. I'm going to have 10 asteroids on the screen at any one time with 8 vertices each. Wouldn't that require me to write out an array of 80 index values? That seems like a way too laborious of a process.

So I was wondering if you could give me an example of how glDrawArraysInstanced is used?

Alfonse Reinheart
01-24-2015, 03:59 PM
Wouldn't that require me to write out an array of 80 index values?

If it's the same object drawn 10 times, why not... just do that? Draw each one, with different transform parameters. If you increase the number into the tens of thousands, then you can try instancing to improve performance.

GClements
01-24-2015, 08:18 PM
I'm going to have 10 asteroids on the screen at any one time with 8 vertices each. Wouldn't that require me to write out an array of 80 index values? That seems like a way too laborious of a process.
The amount of data involved is negligible. And it sufficed until instanced rendering was added in OpenGL 3.1.


So I was wondering if you could give me an example of how glDrawArraysInstanced is used?
A rough outline is: you have an array containing the vertex positions for one asteroid (relative to the asteroid's origin), and another array containing the position of each asteroid. The latter would have a divisor (glVertexAttribDivisor) of 1. The vertex shader would have two attributes, which it would add together to obtain the actual vertex position.

The data would be rendered with e.g.


// initialisation
glBindVertexArray(asteroid_vao);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_vertices);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(0, 0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, asteroid_positions);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(1, 1);
glEnableVertexAttribArray(1);

// rendering
glBindVertexArray(asteroid_vao);
glDrawArraysInstanced(GL_LINE_LOOP, 0, num_vertices_per_asteroid, num_asteroids);

The vertex shader would look like


layout(location=0) in vec2 vertex;
layout(location=1) in vec2 position;

void main()
{
gl_Position = vec4(vertex + position, 0, 1);
}

EmJayJay
01-25-2015, 02:23 AM
-glEnableVertexAttribArray(0) |1
-glEnableVertexAttribArray(1) |1
-glEnableVertexAttribArray(2) |1
-glEnableVertexAttribArray(3) |1
-glBindVertexArray() |1
--glBindBuffer() |2
---glVertexAttribPointer(0,...) |3
----glVertexAttribDivisor(0,x) |4
---glVertexAttribPointer(2,...) |3
--glBindBuffer() |2
---glVertexAttribPointer(1,...) |3
----glVertexAttribDivisor(1,y) |4
---glVertexAttribPointer(3,...) |3
-glBindVertexArray(0) |1
-glBindBuffer(0) |1
-glDisableVertexAttribArray(0) |1
-glDisableVertexAttribArray(1) |1
-glDisableVertexAttribArray(2) |1
-glDisableVertexAttribArray(3) |1

VAO remembers attributes and attributes remember which buffer to read and how. Just remember to call glBindVertexArray(0) before unbinding anything else so the enabled states are saved. (Only buffer which is actually saved to the VAO instead of being referenced by vertex attributes is GL_ELEMENT_BUFFER, just call glBindBuffer(elementBufferID) and you are good with that).
Enable as many vertex attributes as your VAO needs. Your shader might have more vertex attributes in use than your VAO uses/has but you don't have to care about matching all the shader attributes. Just bind them to right locations. Vertex data to vertex attribute, color data to color attribute etc.

After that you just
-glUseProgram
--glUniform*()
-glBindVertexArray(x)
--glDraw*()
Rinse and repeat VAO binding and call glDraw*() commands or switch program and do everthing from the beginning (you can use Uniform Buffers to lower or eliminate the use of glUniform*() calls. If multiple VAOs use the same pattern then you should combine those VAOs into 1 and use offsets within the buffer to draw the right shapes. glDraw*BaseVertex() is the draw command to use. Then you can just bind a single VAO and just call glDraw*BaseVertex() multiple times with different offsets to draw the meshes.

PS: Just check what OpenGL version you are aiming at and use the available options.

michaelglaz
01-28-2015, 11:25 AM
Whereas before my asteroids were stored in a vector of float[2], I have now introduced an asteroid class. It looks like this:



class asteroid
{
std::pair<float, float> vertex;

std:vector<std::pair<float,float>> vertices;
}


where vertex is the location of the asteroid and vertices holds it's 8 vertices. Then I have an vector of asteroids. How do I draw the asteroids from this asteroids vector containing objects of class asteroid (specifically I think I need to know the offset and stride I think).

Alfonse Reinheart
01-28-2015, 01:27 PM
Offset and stride? If your intent is to use buffer objects for this data, then you have to actually store them in buffer objects. That means that your mesh data doesn't live in C++ data structures; it lives in GPU-accessible memory allocated and managed by buffer objects. Your C++ data structures would have some reference to where their data is, so that it can be rendered. But it wouldn't own that memory.

michaelglaz
01-28-2015, 03:41 PM
Well when I ran this first time around, without using OO, i created a vertex like so:



struct vertex{
float[2] loc;
}

vector<vertex> vertices;


Then I was able to get OpenGL to read the data from &vertices[0]. So I guess my question is can this be done with a class that has two members: a pair<float,float> and a vector<pair<float,float>>?

EmJayJay
01-29-2015, 05:56 AM
Yes, I have done it. You can put classes in the buffer but they will be read as the glVertexAttribPointer defines them to be read.

michaelglaz
01-29-2015, 05:59 AM
Well that is my question....how do I know at which offset does the vertices vector start?

Alfonse Reinheart
01-29-2015, 06:09 AM
Well that is my question....how do I know at which offset does the vertices vector start?

"Which offset" into what?

Arrays in C++ start at... the beginning of the array. For std::vector, if you want a pointer to the first element of the array, you use std::vector::data. Or if you are lacking C++11, then you use &vertices[0].

michaelglaz
01-29-2015, 10:44 AM
I apologize, I'm not familiar with the terminology. The problem is is that I'm not quite sure how glBufferData() and glVertexAttribPointer() work. In my last incarnation of code I had the following code drawing 10 asteroids.



struct asteroid
{
GLfloat asteroid_vertices[num_asteroid_vertices][2];
};

vector<asteroid> asteroids;

GLuint asteroid_buffer_object;
GLuint asteroid_vao;
GLint* startindexes;
GLsizei* numberindexes;

void set_asteroid_vertices()
{
startindexes = new GLint[asteroids.size()];
numberindexes = new GLsizei[asteroids.size()];
for(int i = 0; i != asteroids.size(); i++){
startindexes[i] = num_asteroid_vertices*i;
numberindexes[i] = num_asteroid_vertices;
}
}

void InitializeVertexBuffer()
{
glGenBuffers(1, &asteroid_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid)*asteroids.size(), &asteroids[0], GL_STATIC_DRAW);

glGenVertexArrays(1, &asteroid_vao);
glBindVertexArray(asteroid_vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
}

void display()
{
glUseProgram(theProgram);
glBindVertexArray(asteroid_vao);
glMultiDrawArrays(GL_LINE_LOOP, startindexes, numberindexes, asteroids.size());
}


My question is how do I duplicate this now when instead of having a struct with a float[num_asteroid_vertices][2] inside I have the following class:



class asteroid
{
std::pair<float, float> vertex;
std::vector<std::pair <float, float>> vertices;


with asteroids being a vector<asteroid>

Alfonse Reinheart
01-29-2015, 11:07 AM
The problem is is that I'm not quite sure how glBufferData() and glVertexAttribPointer() work.

You could start by reading the OpenGL Wiki articles on buffer objects (https://www.opengl.org/wiki/Buffer_Object) and vertex specification (https://www.opengl.org/wiki/Vertex_Specification). Those two functions are not directly related.

The problem is that you're using the wrong data structure, and you don't really seem to realize that the point of a buffer object is that the mesh data is not stored in C++ data structures.

In short, your asteroid class makes no sense.

michaelglaz
01-29-2015, 11:41 AM
You could start by reading the OpenGL Wiki articles on buffer objects (https://www.opengl.org/wiki/Buffer_Object) and vertex specification (https://www.opengl.org/wiki/Vertex_Specification). Those two functions are not directly related.

The problem is that you're using the wrong data structure, and you don't really seem to realize that the point of a buffer object is that the mesh data is not stored in C++ data structures.

In short, your asteroid class makes no sense.

What do you mean it doesn't make sense. Do you mean it won't work with OpenGL specification? Should I not use C++? Or just not use C++ structures?

Alfonse Reinheart
01-29-2015, 12:27 PM
Your asteroid class holds mesh data. But mesh data lives on the GPU in buffer objects. Therefore, it makes no sense for an asteroid class to have a std::vector of data, since std::vectors cannot get their memory from buffer objects (well, they could, but it would require persistent mapping (https://www.opengl.org/wiki/Buffer_Object#Persistent_mapping) and a specialized std::vector allocator, and you probably don't want to do either).

This would be no less true if you were using classic C-style arrays and malloc. Your problem is not that you're using a std::vector of std::pairs. Your problem is that your data is in CPU memory instead of GPU buffer objects (https://www.opengl.org/wiki/Buffer_Object). You can use CPU memory to build your data, but you must send it to OpenGL to store permanently.

Your asteroid class should store a buffer object name. Or better yet, a buffer object + the offset into that buffer's memory for where it's data starts, so that you can use the same buffer for all asteroids. Each asteroid would have its own VAO that references the buffer, as well as the number of vertices in an asteroid.

Your original code uploaded the data for a single asteroid to a buffer object. So your code for multiple asteroids should do something similar. Either each asteroid should have its own buffer, or there should be a single communal buffer that stores all data for all asteroids (with each asteroid having an offset to where it's particular data is). Once you have it there, you can have your asteroid class store a VAO and the number of vertices to draw that asteroid with.

michaelglaz
01-29-2015, 07:30 PM
First of all I want to thank you for your help Alfonse. The Red Book is not very helpful on programmable GPUs.

GClements suggests the following for instanced drawing: "you have an array containing the vertex positions for one asteroid (relative to the asteroid's origin), and another array containing the position of each asteroid. The latter would have a divisor (glVertexAttribDivisor) of 1. The vertex shader would have two attributes, which it would add together to obtain the actual vertex position."

So I'm guessing I can save the position of each asteroid within a class variable and then have a global array of vertices which would be shared by all asteroids? But I don't understand why I would need a separate VAO for each asteroid?

So can I define the following:


class asteroid
{
float position[2];
}


or should I have a global positions array of all asteroids?

GClements
01-31-2015, 12:28 AM
So I'm guessing I can save the position of each asteroid within a class variable
You could, but you shouldn't. More generally, you shouldn't be trying to store in objects data which is used more-or-less directly for rendering; you should either be using arrays which can be copied directly to a buffer with a single glBufferSubData() call, or just using the buffer as the primary store.


and then have a global array of vertices which would be shared by all asteroids?
Yes.


But I don't understand why I would need a separate VAO for each asteroid?
If you were doing it "right", you wouldn't. But attempts to force OOP where it isn't appropriate may necessitate that.


or should I have a global positions array of all asteroids?
Yes.

Although it doesn't actually matter when dealing with trivial amounts of data, if you want decent performance in real-world programs, you have to stop thinking in terms of individual objects and instead think in terms of arrays. You may be able to wrap some of this up in OO style, but you have to start with what the GPU wants and design any OO structures around that, not the other way around.

Basically, the last few decades of software design have tended to focus around collections (e.g. arrays) of structures. But the GPU wants the data transposed, i.e. a collection of arrays. In fact, that's often better for performance even on the CPU, because "similar" data is contiguous in memory, meaning that if you want to process e.g. vertex (spatial) coordinates, you don't end up transferring the texture coordinates between CPU and RAM solely because they're interleaved with the vertex coordinates.

michaelglaz
02-01-2015, 11:15 AM
For Instanced Drawing GClements said I should have the following code. But from the code it seems like asteroid_vertices and asteroid_positions are arrays of floats. But glBindBuffer takes a GLuint.



// initialisation
glBindVertexArray(asteroid_vao);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_vertices);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(0, 0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, asteroid_positions);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(1, 1);
glEnableVertexAttribArray(1);

Alfonse Reinheart
02-01-2015, 12:04 PM
But glBindBuffer takes a GLuint.

No, glBindBuffer takes a Buffer Object (https://www.opengl.org/wiki/Buffer_Object) name. I have not been constantly linking to that page because I like copying stuff from my browser's link bar. I sincerely expect you to read it, and either understand what it's saying or come back with questions about it.

GClements
02-02-2015, 08:57 AM
For Instanced Drawing GClements said I should have the following code. But from the code it seems like asteroid_vertices and asteroid_positions are arrays of floats.
In the code which you quoted, they're assumed to be the names of buffers (which contain arrays of floats).

michaelglaz
03-01-2015, 10:40 AM
GClements, Alfonse,

I've read about buffer objects. I've also read and re-read your guys' posts in this thread. I realize now that this idea of "OO being the savior of humanity" has been beaten into my head at the university and at my job as a web developer. So I want to thank you guys for your patience with me. I'm having a problem figuring out how much to encapsulate in classes. GClements, you said I need to stop thinking of objects and start thinking in terms of arrays. This makes sense. The way I've been thinking about designing this is that I'll have 3 objects: a ship, a bullet, and an asteroid. They would all inherit from base class Projectile and each object would store its location in their respective GLfloat pos[2] private data member. Is there a better way to design this?

michaelglaz
03-01-2015, 10:49 AM
ok, I'm gonna go with Instanced Rendering and this:

Essentially, you'd have two attribute arrays: one containing num_asteroids positions (which are the origin of each asteroid), and one containing num_asteroid_vertices vertices (which are the asteroid vertices relative to its origin). A vertex shader would add the two together to generate the actual vertex coordinates.

Alfonse Reinheart
03-01-2015, 12:44 PM
That's a reasonable way of handling that.


The way I've been thinking about designing this is that I'll have 3 objects: a ship, a bullet, and an asteroid. They would all inherit from base class Projectile and each object would store its location in their respective GLfloat pos[2] private data member. Is there a better way to design this?

The problem with this design (in general) is that you put the actual source of the position in the base class. There should be "GetPosition" and "SetPosition" pure-virtual accessor methods in the base class. But you should not restrict each derived classes as to where they're positions are stored.

By doing that, all of the asteroids can have their positions stored in a single array. Then, when it comes time to render all of the asteroids, you upload that array into your buffer object that contains positions (using appropriate streaming techniques (https://www.opengl.org/wiki/Buffer_Object_Streaming)), and render with that.

GClements
03-01-2015, 02:08 PM
Is there a better way to design this?
Given the small amounts of data involved, it wouldn't be a disaster if you duplicated the position data. I.e. store each object's position in the object itself, then before rendering iterate over the objects and copy their positions to the array.

If there were more objects, you might want to omit any such data from the object and store an index instead. Getting/setting the position (etc) would update the array entry corresponding to the object's index. However, this changes the object's semantics, e.g. copying an object copies the index into the attribute arrays, not the attribute data itself (shallow copy rather than deep copy).

Beyond that, providing object-oriented interfaces to data which is stored otherwise (e.g. database records) is relatively common problem, although it tends not to be covered much in academic OOP courses.

Alfonse Reinheart
03-01-2015, 02:42 PM
However, this changes the object's semantics, e.g. copying an object copies the index into the attribute arrays, not the attribute data itself (shallow copy rather than deep copy).

Note that this can always be fixed with a good copy constructor.

michaelglaz
03-29-2015, 05:05 PM
Ok, so I found some more in depth tutorials and I've got my 10 asteroids floating around in random directions and also have my spaceship flying around. I transfer my vertices (via glBufferData()) of all the asteroids and the spaceship in my init() function. My next step is bullets. Since the bullets are dynamically generated and there can be anywhere from 1 to 10 bullets drawn at any one time how or where do I transfer the vertices of each bullet to the GPU? Do I just have an array of 10 bullets from the beginning and a flag for each bullet as to whether it is visible or not? Or should I create each bullet each time the fire key is pressed?

GClements
03-29-2015, 08:32 PM
My next step is bullets. Since the bullets are dynamically generated and there can be anywhere from 1 to 10 bullets drawn at any one time how or where do I transfer the vertices of each bullet to the GPU? Do I just have an array of 10 bullets from the beginning and a flag for each bullet as to whether it is visible or not? Or should I create each bullet each time the fire key is pressed?

For such a small amount of data, it really wouldn't matter if you transferred all bullet-related data each frame, and set the count parameter to glDrawArrays() or glDrawElements() to the number of live bullets.

If there was more data and the rendering order didn't matter, you could "delete" a bullet by moving the last bullet into the slot for the deleted bullet, meaning that the data for the live bullets was contiguous, avoiding the need for a visibility flag.

michaelglaz
04-22-2015, 07:32 AM
Ok, I'm successful at detecting collisions between asteroids and the spaceship. Next is to animate the destruction of the ship. In the original game the 3 sides of the ship (the line segments) travel in different directions on impact. The only way I can think of doing this is by creating a VAO/VBO for each of the 3 line segments that make up the ship. Is this right or am I not seeing a simpler way?