• Advertisement
Sign in to follow this  

C++ / OpenGL Particle Generator help

Recommended Posts

I wrote a code for a particle generator. It runs, but it doesn't show anything. Here is the code: 

Emitter.cpp:

#include "Emitter.h"
 
 
Emitter::Emitter(GLuint nrParticles, glm::vec3 position, GLfloat radius, char *imgName)
{
this->m_Position = position;
this->m_radius = radius;
 
for (int i = 0; i < nrParticles; i++)
{
this->particles.push_back(Particle(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, "blur.png"));
}
 
}
 
void Emitter::Draw(Shader &shader)
{
for (int i = 0; i < particles.size() - 1; i++)
{
particles[i].Draw(shader);
}
}
 
void Emitter::Update(GLfloat dt)
{
for (int i = 0; i <= particles.size() - 1; i++)
{
particles[i].Update(dt);
}
}
 
void Emitter::SetParticlesAttributes()
{
 
for (int i = 0; i <= particles.size() - 1; i++)
{
particles[i].setLife(10.0f); //(((rand() % 10) + 1) * 15);
particles[i].setVelocity(glm::vec3(0.0f, 2.0f, 0.0f)); //(((rand() % 10) + 1) * 2));
}
}

Particle.cpp:
#include "Particle.h"
#include "Camera.h"
GLfloat vertices[] = {
0.5f,  0.5f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f,  0.5f, 0.0f, 0.0f, 1.0f
};
 
//Camera camera(glm::vec3(0.0f, 0.0f, 10.0f));
Vbo vbo;
Ebo ebo;
 
Texture texture;
 
GLuint indices[] = {  // Note that we start from 0!
0, 1, 3,   // First Triangle
1, 2, 3    // Second Triangle
};
 
Particle::Particle(glm::vec3 position, glm::vec3 velocity, GLfloat life, GLchar *imgName)
{
this->m_Position = position;
this->m_Velocity = velocity;
this->m_Life = life;
 
this->vao.Init(1);
vbo.Init(1);
ebo.Init(1);
 
this->vao.Bind();
vbo.Bind(vertices, 20);
ebo.Bind(indices, 6);
this->vao.AttribPointer(0, 3, 5, 0);
this->vao.AttribPointer(1, 2, 5, 3);
this->vao.UnBind();
 
texture.Init(1, imgName);
 
}
 
void Particle::Draw(Shader &shader)
{
 
shader.Use();
 
glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;
 
model = glm::translate(model, glm::vec3(this->m_Position));
model = glm::scale(model, glm::vec3(30.0f, 30.0f, 0.0f));
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
projection = glm::perspective(45.0f, (float)1600 / (float)1200, 0.1f, 100.0f);
 
GLuint modelLoc(glGetUniformLocation(shader.Program, "model"));
GLuint viewLoc(glGetUniformLocation(shader.Program, "view"));
GLuint projLoc(glGetUniformLocation(shader.Program, "projection"));
 
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
 
texture.Bind();
glUniform1i(glGetUniformLocation(shader.Program, "ourTexture"), 0);
 
this->vao.Bind();
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
this->vao.UnBind();
 
}
 
void Particle::Update(GLfloat dt)
{
if (this->m_Life > 0)
{
this->m_Position += m_Velocity;
}
else
{
//m_Life -= dt;
}
}
 
void Particle::setLife(GLfloat life)
{
this->m_Life = life;
}
GLfloat Particle::getLife()
{
return this->m_Life;
}
 
void Particle::setPosition(glm::vec3 position)
{
this->m_Position = position;
}
glm::vec3 Particle::getPosition()
{
return this->m_Position;
}
 
void Particle::setVelocity(glm::vec3 velocity)
{
this->m_Velocity = velocity;
}
glm::vec3 Particle::getVelocity()
{
return this->m_Velocity;
}

Main.cpp:
#include <iostream> 
#include <glad/glad.h>
#include<KHR\khrplatform.h>
//GLFW
#include <glfw3.h>
 
#include "Shader.h"
#include "Camera.h"
 
// GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "stb_image.h"
 
#include "Emitter.h"
 
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
 
// settings
const unsigned int SCR_WIDTH = 1600;
const unsigned int SCR_HEIGHT = 1200;
 
Emitter *e1;
 
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
 
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
 
// glad: load all OpenGL function pointers
// ---------------------------------------
 
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
 
// build and compile our shader zprogram
// ------------------------------------
Shader shader("particle.vs", "particle.frag");
e1 = new Emitter(100, glm::vec3(0.0f), 30.0f, "blur.png");
 
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
GLfloat dt = glfwGetTime();
// input
// -----
processInput(window);
e1->Update(dt);
 
// render
// ------
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
 
e1->Draw(shader);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
 
// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
 
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
 
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
 
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
{
std::cout << "Space Pressed" << std::endl;
e1->SetParticlesAttributes();
}
}
 
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and 
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}

I tried to debugged it, but I didn't succeed to figure out the problem. :(
Little help please ? :D

Share this post


Link to post
Share on other sites
Advertisement

Missing assets most likely if this is copied from a tutorial and it compiles.

 

ED: I assume this was mostly from a tut because of the window creation line having the title "LearnOpenGL"

Edited by fleabay

Share this post


Link to post
Share on other sites

You could try using CodeXL to see if your OpenGL commands are actually handles by the gpu. This should also show you the GPU state and such so that you can see what is actually wrong.

Renderdoc is another tool for this but the openGL support for that is not optimal had it been d3d or vullkan you could have used that tool which is really good at debugging graphics and is used in the industry by AAA game companies.

Share this post


Link to post
Share on other sites

@fleabay: I copied from a tutorial just the main.cpp. The rest of the classes I wrote them. :P

@NightCreature83: I'm pretty sure the OpenGL commands are handled by the gpu because is not the first time when I use them. I already created a snake and a pong game. :P
 

Share this post


Link to post
Share on other sites

Tell us what you have tried.

I see you have a few other similar topics, the pattern is that you get blocked and post your full program here, looking for someone else to spot the problem.

This approach is inefficient. You need to wait for someone to spot your error, which is a waste of your time. Also you have different people reading large amounts of code every time, as you cannot narrow down the source of the issue.

A better approach would be to invest some time into learning how to debug these situations for yourself.

For example, in a recent thread you were strongly advised to add more error checking code. Start there, before you do anything else.

Part of this is learning to use your tools, such as a debugger. As a beginner, I remember finding the debugger rather intimidating so I learned to use "printf debugging", which is basically inserting print statements at different points to allow you to understand how your program is running and what values different variables might have at the time. While it is worth your time investing in a proper debugger, even using printf debugging you'll be able to find and fix many bugs.

There are techniques to speed up debugging, for example disabling portions of your program so you can focus on a smaller part and checking that for correctness. For example, start by checking that a single static particle will draw as expected, before enabling multiple particles and movement.

Share this post


Link to post
Share on other sites

@rip-off: I know, I'm posting the code here and waiting for somebody to give me a clue. But in this time I still try to figure it what is the problem.:P
I tried to not use the emitter object and to create just a single particle object and it works. It draws the particle. But with the emitter object, it doesn't draw. Not even one.
Also I used "couts" to see if is updating the positions of the particles, and is updating.

Edited by alx119

Share this post


Link to post
Share on other sites

So that is a good start! There is more narrowing down to do. Here are some examples:

* Have you tried an emitter with just one particle?
* Have you tried two particles without the emitter?
* Have you tried making the particles stationary?

Share this post


Link to post
Share on other sites

@fleabay: I copied from a tutorial just the main.cpp. The rest of the classes I wrote them. :P

@NightCreature83: I'm pretty sure the OpenGL commands are handled by the gpu because is not the first time when I use them. I already created a snake and a pong game. :P
 

I have used D3D11 for a while now I still use these tools to figure out what goes wrong if something isnt showing up as I expect them to show up. Using tools only makes it easier to find the location where your problem is and thus much easier to solve your issue.

Share this post


Link to post
Share on other sites

@rip-off: All three of them I tried. Something is wrong.I think from the emitter. What I realized is when I try let's say with 10 particles, and I use "cout" after the draws method it gives me Draw 0, 1, 2... 8. So it doesn't print the 10th draw which should be "Draw 9".

Share this post


Link to post
Share on other sites

Progress, excellent!

So, take another look at the code:

void Emitter::Draw(Shader &shader)
{
    for (int i = 0; i < particles.size() - 1; i++)
    {
        particles[i].Draw(shader);
    }
}
 
void Emitter::Update(GLfloat dt)
{
    for (int i = 0; i <= particles.size() - 1; i++)
    {
        particles[i].Update(dt);
    }
}
 
void Emitter::SetParticlesAttributes()
{
    for (int i = 0; i <= particles.size() - 1; i++)
    {
        particles[i].setLife(10.0f); //(((rand() % 10) + 1) * 15);
        particles[i].setVelocity(glm::vec3(0.0f, 2.0f, 0.0f)); //(((rand() % 10) + 1) * 2));
    }
}
 

Can you spot any reason why Draw might not draw the last element? In particular, can you see any differences between the Draw() function and the others?

Share this post


Link to post
Share on other sites

Ok, I figured that instead of particles.size() - 1 I must write particles.size() at the drawing function and the differences are <= and <. I corrected them to be honestly before you to reply so my code is:  < particles.size() in all functions. :P
But still not drawing. :D.Just print the "couts". I'm so frustrated. :D

Share this post


Link to post
Share on other sites

You said it works if you create a single particle instance, but not if you spawn a single particle instance from the emitter. Correct?

If so, create a single particle instance manually, and spawn a single particle instance from the emitter. Using breakpoints and/or debug printing, compare the states of the one you created manually vs the one that was spawned from the emitter.

Share this post


Link to post
Share on other sites

Ok, so in main.cpp I instantiated a particle with position.x = 1 and another particle from emitter with position.x = 0. I drew and from emitter and from particle and updated as well. Because I was so confuse after don't know how many changes, I don't remember if it was drawing from emitter. I've changed a few things on the emitter constructor but it didn't work. So I came back to my old code from constructor. When I run the application both particles were drawn at 1 and 0 position <_<. If I delete the instantiated particle from main it doesn't work that from emitter.I'm so confused.

 

Edit:I forgot to say, when it's working, it works even with more particles in the same position. :wacko:

Edited by alx119

Share this post


Link to post
Share on other sites

Indeed, most programmers would write "i < N" for loops that should run N times, rather than i <= N - 1. It would be a good idea to do this consistently from now on, to prevent such bugs.

Now, it sounds like you've discovered something very interesting from a debugging perspective, which is you have two versions of the program, one of which behaves as expected and another which does not.

As Lactose says, you can start running the two versions and comparing the various variables involved. Keep the number of particles small and it might be easier to debug.

Share this post


Link to post
Share on other sites

I've made 2 projects.One for instantiated particle and one for instantiated emitter. I've debugged and I've played a little(or more :D) and then I saw that on the emitter project, after I insert the particle objects into the vector, the constructors are destroyed (which is normal, because are going out of scope). But the objects shouldn't be kept with them variables and all stuff inside the vector ?

What I saw more was that with the Emitter Project, the bindings with the Vao's, Vbo's and Ebo's are destroyed as well, to say like that... they don't have the same values like in Particle Project.
For 1st particle I should have VaoID = 1, VboID = 1, EboID = 2 and for the other: VaoID = 2, VboID = 3, and Ebo = 4.

Here is what the buffers contain:

 [attachment=36250:emitterER.png]

I tried to create a copy constructor (for the first time). I'm not sure is good how I did it. The new code is:

For Constructor:

Particle::Particle(const Particle &particle)
{
this->m_Position = particle.m_Position;
this->m_Velocity = particle.m_Velocity;
this->m_Life = particle.m_Life;
}

For creating particle objects and inserting them into the vector:

Emitter::Emitter(GLuint nrParticles, glm::vec3 position, GLfloat radius, GLchar *imgName)
{
this->m_Position = position;
this->m_radius = radius;
 
for (int i = 0; i < nrParticles; i++)
{
this->particles.push_back(Particle(Particle(glm::vec3(0.0f), glm::vec3(0.0f), 0.0f, imgName)));
}
}

Although I've made this change, still doesn't work. The values of Vao, Vbo, Ebo are the ones from the picture. If Is still because of the copy constructor, please let me know because is the first time when I'm working with it and I'm not sure if it's ok. 

Edit: I debugged and I saw that it does't create the copy constructor.

Edited by alx119

Share this post


Link to post
Share on other sites

Excellent observation, that is indeed the sequence of IDs that you might expect, that you see a deviation is significant. 3452816845 is an interesting number, not by itself but rather because many of your variables seem to have this value.

Googling it gives me https://stackoverflow.com/questions/18673913/why-variables-are-initialized-with-max-value-instead-of-zero, which reveals that the value has a special meaning:

3452816845 = 0xcdcdcdcd, the value used to initialize memory allocated from the heap.

(I knew about this already, but this shows how you might investigate yourself).

 

So what is happening? This example program might illustrate:

#include <iostream>
#include <vector>
 
struct Foo {
 
  Foo() {
    std::cout << "Constructor for " << this << std::endl;
  }
 
  ~Foo() {
    std::cout << "Destructor for " << this << std::endl;
  }
 
  Foo(const Foo &other) {
    std::cout << "Copying " << &other << " into " << this << std::endl;
  }
 
  Foo &operator=(const Foo &other) {
    std::cout << "Assigning " << &other << " to " << this << std::endl;
    return *this;
  }
 
};
 
int main() {
  std::vector<Foo> v;
  std::cout << "Before insertion" << std::endl;
  v.push_back(Foo());
  std::cout << "End of main" << std::endl;
}

If you run this, you'll see something like:


Before insertion
Constructor for 0x7fff8957a718
Copying 0x7fff8957a718 into 0xfdd030
Destructor for 0x7fff8957a718
End of main
Destructor for 0xfdd030

Note that a Foo object was created, copied and destroyed during the "push_back". This is the lifecycle of the temporary Foo() instance. The copy lives on in the vector, and is destroyed after the last line of main().

It looks like your Particle class is more complex than it seems at first, it has complex inner types. The question is really whether these complex inner types can be copied and / or assigned. Remember the rule of three, which is that any class that has a destructor should probably have a copy constructor and assignment operator (leaving aside move for the moment), or be "non-copyable", which can be done (in modern C++) by deleting the problematic functions (to prevent them being automatically generated), or simulated in older C++ by declaring the functions private and leaving them unimplemented.

Generally you do not want to implement copying semantics for external resources, such as textures that reside on the graphics card. Not only is it tricky, but also it is likely to have performance issues. You don't generally want to have lots of distinct copies of the same texture on the GPU, that is just wasting memory and wasting time switching between textures. I'd recommend changing your Texture, Vbo and Ebo classes to be non-copyable.

Once you do this, code that attempts to insert Particles into the container should stop compiling. I was never much of a graphics programmer, and it has been a long while since I've dabbled, but my guess would be to think about managing the textures and related objects at a higher level than the particles, for example the emitter could manage these resources. Particles should be as lightweight as possible, managing GPU resources is the opposite of this.

Share this post


Link to post
Share on other sites

Thank you very much for your help guys. Every time I had problems, people from this forum helped me always.
@rip-off: Thank you so much. I will make some changes to my code. :P Thanks again. :)

Edited by alx119

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Advertisement