I'm working on creating a cloth simulation in openGL and I've been trying to use a VAO and VBO for rendering rather than the old immediate mode (glBegin, glEnd).
I reckon it's best if I only provide the relevant parts of the code rather than the all of it. So I currently have a class, Cloth, that stores the the mass (nodes) in a vector.
class Cloth {
private:
std::vector<Mass>masses;
public:
std::vector<Mass> getMasses() { return masses; };
// functions below
And a class, Mass.
class Mass {
glm::vec3 currentPos;
glm::vec3 oldPos;
glm::vec3 acceleration;
// functions below
I've createad a VBO like so
glGenBuffers(1, &VBO_ID);
glBindBuffer(GL_ARRAY_BUFFER, VBO_ID);
glBufferData(GL_ARRAY_BUFFER, cloth.getMasses().size() * sizeof(Mass), &cloth.getMasses().front(), GL_DYNAMIC_DRAW);
and a VAO
glGenVertexArrays(1, &VAO_ID);
glBindVertexArray(VAO_ID);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GL_FLOAT), 3*sizeof(GL_FLOAT)); // unsure about this part
From what I've gathered, by calling glVertexAttribPointer, you tell the VBO how to handle the data that has been given to it. I gave it a vector of type Mass.
What would be the correct way for me to tell it how to handle the data so that it uses currentPos as vertexes?
and
Is there a better (performance-wise) way of doing this, and if so, how and why?
For example, would it be best to use a struct to hold the currentPos?
I don't see anything wrong with the code. But with vertexBuffer attributes, you don't need to tell the openGL state machine to use currentPos as a vertex, this will be done in the vertex shader.
EDIT I see something wrong with the code. I thought you only sent primitive types. You cannot send your class to your GLSL code and hope that it will work. YOu can send data and the glVertexAttribPointer tells your shader the layout of your data. What you are doing cannot work.
I'd advise you to take a look at https://learnopengl.com/ it helped me a lot and is very complete.
You'll have:
layout (location = 0) in vec3 currentPos;
layout (location = 1) in vec3 oldPos;
layout (location = 2) in vec2 acceleration;
Then you output the position of the vertex with this:
gl_Position = mvp*vec4(currentPos, 1.0);
mvp beeing the Model View Projection matrix that you would probably send as a uniform.
What comes next is a bit out of the scope of your answer but might be interesting:
While I don't know exactly what you're trying to do, you should ne that in the vertex shader you cannot edit the vertex attributes, this means that considering how your code looks right now, you'll have to update your VBO every step of the simulation. you can either update it on you CPU (which might be slow), but it might be significantly faster to do it in a compute shader.
Related
I'm trying to learn some basic OpenGL 3D programming. I'm following this guide and some youtube videos. In particular I've reached the chapter which talks about model loading (using Assimp). I've more or less managed to make something work using the guide's code, then I tried to write a more structured program following a series of videos on youtube, in particular this one but when I finished and run the program, it stops, without showing anything if not a black screen. When running the program using the visual studio debugger, I get the following error:
Exception thrown at 0x00007FF9DABA7610 (nvoglv64.dll) in "program name.exe" 0xC0000005: Access violation reading location 0x0000000000000000
The code that generates the exception is
glBindVertexArray(this->VAO);
glDrawElements(GL_TRIANGLES, (this->indices).size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
the first line. I've tried searching for some similar errors on the internet, hoping for answers, and I found that these errors are often caused by a bad configured VAO, in particular this site suggests to carefully check the portion of code where the VAO is configured, which I did, and it seems fine. Moreover, when I try to use the same code to render a model from an array of vertices, it works fine. The method where the VAO is configured is the following one:
void Mesh::setup() {
// create buffers and array objects
glGenVertexArrays(1, &(this->VAO));
glGenBuffers(1, &(this->VBO));
glGenBuffers(1, &(this->EBO));
// configure buffers
glBindVertexArray(this->VAO);
{
// VBO
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
glBufferData(GL_ARRAY_BUFFER, (this->vertices).size() * sizeof(Vertex), &(this->vertices)[0], GL_STATIC_DRAW);
// data configuration
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Vertex::position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Vertex::normal));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Vertex::textureCoordinates));
// EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (this->indices).size() * sizeof(unsigned int), &(this->indices)[0], GL_STATIC_DRAW);
}
glBindVertexArray(0);
}
where Vertex is a custom structure of the form
typedef struct Vertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 textureCoordinates;
// convert an array of vertices into a vector of struct Vertex vertices
static std::vector<struct Vertex> genList(float* vertices, int nrVertices);
} Vertex;
My program is pretty much the same as the one showed on the youtube video (the classes are pretty much identical, the file tree structure might differ a bit).
As I can't write all the program, I will explain its structure:
There is a model class which loads a model from a file or from a class defined in the project:
Model(glm::vec3 position, glm::vec3 size) initializes the class variables
void loadModel(std::string path) loads a model from a file: it uses assimp to convert a model file into a aiScene* object from which the specific data is retrieved via: void processNode(aiNode* node, const aiScene* scene) (a recursive function which iterates through each Assimp's node), Mesh processMesh(aiMesh* mesh, const aiScene* scene) (a function called by processNode which converts the information inside aiMesh into a custom Mesh object that I will describe later), and std::vector<Texture> loadTexture(aiMaterial* material, aiTextureType type) (a function called by processMesh which retrieves all the textures of a material associated to a mesh; it returns a custom Texture object, which I will describe later)
void render(Shader* sahder) (Shader is a custom class that manages shaders) draws the model on the screen. Each mesh of the model is stored in a class variable and has a render method called by Model::render.
void init() it is a method that is meant to be overridden by a child class. In particular, for example, I've defined a class Cube : public Model which has a Cube constructor (that calls the Model super-constructor) and has an init method where the vertex data of the cube -initialized using an array then converted to a Vertex structure -, indices and textures are used to initialize a Mesh object, then stored into a meshes list inside Model class. Using this approach the cube model is perfectly rendered without any exception
There is a Mesh class which converts an Assimp's aiMesh* object into a custom object:
Mesh(std::vector<Vertex> vertices, std::vector<unsigned int> indices, std::vector<Texture> textures = {}) calls the void Mesh::setup() method
void setup() it is the method I've included before: it configures the VAO, EBO and VBO (which, I repeat, work perfectly with the cube class)
void render(Shader* shader) sets the correct value for texture units (distinguishing between diffuse and specular textures) and binds the textures. Then draw each vertex using glDrawElements, these are the lines that generate the exception.
There is a Texture class which loads textures and stores textures' parameters:
Texture(std::string path, std::string directory, aiTextureType type) initializes class variables
void generate() generates an OpenGL texture object
void load(bool flipv) loads the texture from an image (using stb library) and configures texture object parameters (e.g. wrap S or T and so on)
void bind() binds the texture to GL_TEXTURE_2D
In the main function I declared a Model object giving a path (which loaded succcesfully according to the debug std::cout statements I've inserted):
Model model(glm::vec3(0.0f), glm::vec3(1.0f))
model.loadModel("path");
Then in the mainloop, after enabling the shader:
model.render(&objectShader);
As I said before, I've checked my code and it seems to have no errors. I've searched online but couldn't find anything. I can't even think of a way to test the program and see where does this error come from. Have you go any suggestion on how to avoid this issue?
Thanks in advance for your effort and time in answering me! I don't really know if what I've explained about the program and what I've included about the code is enough. If you have any requests on the code or on how the program works that might help you find the issue, please ask me and I will edit this question.
I am using glMultiDrawElementsIndirect with a combination of ARB_bindless_texture to draw things. I fetch texture handles with the built in vertex shader variable gl_DrawID. This variable is said to to be a dynamically uniform expression which means I dont have to depend on NV_gpu_shader5 when accessing bindless textures inside the shader. It looks something like this.
#version 460 core
out flat int MaterialIndex;
void main()
{
...
MaterialIndex = gl_DrawID;
}
#version 460 core
#extension GL_ARB_bindless_texture : require
struct Material
{
sampler2D Albedo;
ivec2 pad0;
};
layout(std140, binding = 1) uniform BindlessUBO
{
Material Materials[256];
} bindlessUBO;
in flat int MaterialIndex;
void main()
{
Material material = bindlessUBO.Materials[MaterialIndex];
vec3 albedo = texture(material.Albedo, coords).rgb;
}
This works on a NVIDIA RTX 3050 Ti, supporting NV_gpu_shader5. The model with all of its meshes and textures gets drawn correctly in one draw call. However on a AMD RX 5700XT, not supporting the extension, the driver crashes after a few seconds. Both GPUs can render the model without any textures. So now my guess would be that MaterialIndex is not a dynamically uniform expression even though it gets its value from one. Does passing MaterialIndex from a vertex to a fragment shader make it a non d.u.e? How could I obtain the index of the drawing command within glMultiDrawElementsIndirect in the fragment shader as a d.u.e? Is there a way to accomplish what I am trying to do without NV_gpu_shader5?
After upgrading from AMD driver 19.12.2 to 21.12.1 the bug disappeared. So passing gl_DrawID from vertex to fragment shader using the flat qualifier does in fact preserve it as a dynamically uniform expression and I dont need to depend on NV_gpu_shader5 :).
I have a code structure that has a Render with the rendering loop, a Shader class and a Model class. I am able to get my shaders to work when I implement them in the renderer, but I'm looking to assign them to a model and call render(), however I'm having an odd issue, that the shader works outside of the model, but not inside.
Shader structure:
class Shader
{
public:
unsigned int ID;
// constructor generates the shader on the fly
// ------------------------------------------------------------------------
Shader(const char* vertexPath, const char* fragmentPath, const char* geometryPath = nullptr)
{
... Generate shaders...
//Assign program ID to the public unsigned int
ID = glCreateProgram();
...attach shaders to program and link...
}
//Install program for use in rendering
void use()
{
glUseProgram(ID);
}
}
Model Structure:
class TextureModel
{
public:
TextureModel(const std::string modelPath, const std::string texturePath)
{
...Initialize model, load in mesh,texture, create a VAO with vertex and
index VBOs, set glAttribArray values...
}
Render(Shader* shader)
{
//glUseProgram(4) <---------- (3)
shader->use(); <---------- (1)
glBindVertexArray(VAO);
glBindTexture(GL_TEXTURE_2D, texture);
glDrawElements(GL_TRIANGLES, IBO->GetCount(), GL_UNSIGNED_INT, (void*)0);
glBindVertexArray(0);
}
}
Renderer structure:
Shader* texShader;
TextureModel* Reindeer;
int main()
{
texShader = new Shader("Shaders/camera.vs", "Shaders/textured.fs", "Shaders/passThrough.gs");
Reindeer = new TextureModel("Models/reindeer_v1/reindeer_v1.obj",
"Models/reindeer_v1/reindeer_diffuse.jpg");
while (!glfwWindowShouldClose(window))
{
...Update camera, project and model matrices. Update the shader uniforms...
//texShader->use() <---------- (2)
Reindeer->Render(texShader);
glfwSwapBuffers(window);
}
}
The above code does not render my object.
At point (1) my model calls the texShader->use() passed in by TextureModel::Render(Shader*). For some reason this does not work, however if I call texShader->use() from the rendering loop it works, this is at point (2).
Also in debugging I found that the program ID assigned to texShader on creation is 4. If I put glUseProgram(4) in the model::Render(Shader*) block, this also works. Point (3).
Why does the glUseProgram() function work in all cases except when passed in to my model? I'm really confused at this one.
Looks like I've fixed it.
I've summarized my problem above, but in actual fact I am using two different shaders for different objects.
The both share the same vertex shader (using their own model matrices, with a camera uniform buffer object), use different fragment shaders, and share a geometry shader.
For some reason I am only able to have one instance of the geometry shader working at a time... I need to to further investigation.
Either way I've got the above issue solved.
Thanks
Suppose I have a vertex shader with the following vertex attributes:
layout(location = 0) in vec4 foo1;
layout(location = 1) in vec4 bar1;
layout(location = 2) in vec4 foo2;
layout(location = 3) in vec4 bar2;
And two VAOs which were initialized like this:
glBindVertexArray(vao1);
...
glVertexAttribPointer(0, ...);
glVertexAttribPointer(1, ...);
glBindVertexArray(vao2);
...
glVertexAttribPointer(2, ...);
glVertexAttribPointer(3, ...);
Then, if I bind them successively:
glBindVertexArray(vao1);
glBindVertexArray(vao2);
glDrawArrays(...);
what data will I get from foo1 and bar1 vertex attributes? Can I expect their values to be taken from buffer pointed by vao1, or will their state be undefined?
OpenGL objects are not like macros (except for display lists, which actually are like macros). They store state. When you bind a new object to the same target as a previous object, all of the governed state is taken up by the new object; the old one is unbound from that binding point.
Each VAO contains state for all of the available vertex attributes. The attributes default to being disabled. As such, if you only enable attributes 2 and 3 in a VAO, then when you bind that VAO, only attributes 2 and 3 will be enabled.
I am trying to draw a square with VBO in my native blackberry 10 application. My implementation is,
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glGenBuffers(1,&decompressTileImage->VBOId);
glBindBuffer(GL_ARRAY_BUFFER,decompressTileImage->VBOId);
glBufferData(GL_ARRAY_BUFFER,sizeof(tileCoordList)+sizeof(tileTextureCoordList),0,GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(tileCoordList),tileCoordList);
glBufferSubData(GL_ARRAY_BUFFER,sizeof(tileCoordList),sizeof(tileTextureCoordList),tileTextureCoordList);
glTexCoordPointer(3, GL_FLOAT, sizeof(tileTextureCoordList), (void*)sizeof(this->tileCoordList));
glVertexPointer(3, GL_FLOAT, sizeof(tileCoordList), 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisable(GL_TEXTURE_2D);
swapBuffers();
Here tileCoordList contains the co-ordinates for the square and tileTextureCoordList contains the corresponding texture. tileCoordList is a Square3D type structure.
typedef struct
{
Vertex3D lowerLeft;
Vertex3D lowerRight;
Vertex3D upperLeft;
Vertex3D upperRight;
}Square3D;
typedef struct
{
GLfloat x;
GLfloat y;
GLfloat z;
} Vertex3D;
4 Vertex3D represents a square in tileCoordList. Same goes for tileTextureCoordList. I can draw fine without the VBO though. I am using opengl-es 1.1
Another question.
If I create two VBOs. Suppose I bound the first one with its id and copied the data and drew it. And then comes the second one and I also bind it and copy the data and draw it. Now if I want to draw the first one again do I need to bind it again? If I need to bind again do I need to copy the data to buffer for that VBO again also?
It has transpired in the comments that tileCoordList and tileTextureCoordListare pointers to Square3D structures.
This means that the sizeof operator will give you the size of a pointer, not the size of the struct.
You could use e.g.
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(*tileCoordList), tileCoordList);
instead.
You are doing something weird to your buffer. Without knowing what tileCoordList is it's hard to say if you are adding the data correctly.
In general, regarding both of your concerns here: VBOs store the data for you. It is not only possible, but encouraged to do:
vbo_1, vbo_2;
vbo1.LoadData(data_1);
vbo2.LoadData(data_2);
main_loop {
vbo1.Bind();
Draw();
vbo2.Bind();
Draw();
}
I have used pseudocode here, but you should get the idea. I don't understand what the SubData call is supposed to do; in general, one simple glBufferData should be enough. Bind call sets the buffer as an active one.
This line made me cringe - why is VBOId public? Smells like a poorly designed abstraction to me:
glGenBuffers(1,&decompressTileImage->VBOId);
And oh, please don't use this->. The only thing it does here is worsen readability.