La clase Mesh llamada con el constructor predeterminado no funciona OpenGL C++
class c++11 (1)
Si bien cree que el problema no tiene nada que ver con el almacenamiento de los objetos en un vector, tengo la fuerte sensación de que probablemente sí. La forma en que encapsula objetos OpenGL en envoltorios de C ++ es una receta para el dolor, y probablemente lo esté descubriendo como muchos lo hicieron antes.
Los problemas típicos son causados por la combinación de lo que sucede cuando los objetos se copian y destruyen. Los objetos OpenGL que posee el contenedor C ++ se eliminan en el destructor:
mesh::~mesh()
{
glDeleteBuffers(1, &elementBuffer);
glDeleteBuffers(1, &vertexBuffer);
glDeleteVertexArrays(1, &vertexArrayObject);
}
Para ilustrar el problema con esto, veamos una secuencia típica. Digamos que tiene un vector de objetos de malla y un método para agregar una nueva malla a este vector (puntos anotados para referencia posterior):
std::vector<mesh> m_meshes;
void createMesh(...) {
mesh newMesh; // point 1
newMesh.changeMesh(...);
m_meshes.push_back(newMesh); // point 2
} // point 3
¿Parece inofensivo? No es para nada. Aquí pasaron cosas malas:
- Punto 1: se crea un nuevo objeto. El constructor crea los objetos OpenGL y almacena sus nombres en variables miembro.
- Punto 2: se agrega una copia del objeto de malla al vector, donde la copia se crea con el constructor de copias predeterminado. Esto significa que las variables miembro, que contienen los nombres de los objetos OpenGL, se copian.
- Punto 3: el objeto de malla queda fuera de alcance. Se invoca el destructor, que elimina los objetos OpenGL.
Lo que tiene después de todo esto es un objeto de malla almacenado en el vector, con nombres de objetos OpenGL almacenados en sus variables miembro, mientras que los objetos OpenGL reales se han eliminado. Esto significa que los nombres de los objetos almacenados en este objeto de malla ahora no son válidos.
El problema raíz es que su clase no tiene constructores de copia y operadores de asignación adecuados. Y desafortunadamente, no es posible implementarlos fácilmente al almacenar nombres de objetos OpenGL en variables miembro y generar / eliminar los nombres de objetos en constructor / destructor.
Hay varias formas de manejar esto. Ninguno de ellos es perfectamente bonito:
-
No genere / elimine los objetos OpenGL en constructor / destructor. En su lugar, use alguna forma de métodos
init()
/cleanup()
que invoque explícitamente. La desventaja es que debe tener cuidado de invocar estos métodos correctamente. Por ejemplo, si tiene un vector de objetos y desea eliminar el vector, debe invocarcleanup()
en todos los miembros del vector manualmente. -
Siempre haga referencia a los objetos con punteros. En lugar de tener un vector de objetos de malla, use un vector de punteros de objetos de malla. De esta manera, los objetos no se copian. También debe tener cuidado de administrar la vida útil de los objetos correctamente y no filtrarlos. Esto es más fácil si utiliza alguna forma de puntero inteligente en lugar de punteros desnudos.
-
Use alguna forma de híbrido, donde todavía usa objetos reales de C ++, pero almacenan los nombres de los objetos subyacentes de OpenGL en un objeto anidado que se cuenta como referencia. De esta manera, pueden implementar una semántica adecuada para copiar / asignar.
Creo que el enfoque más fácil y limpio es la opción 2 con el uso de punteros inteligentes.
Las versiones más nuevas de C ++ tienen punteros inteligentes en la biblioteca estándar, por lo que no es necesario implementar nada.
Por ejemplo, en C ++ 11, puede usar el tipo
std::shared_ptr<mesh>
para hacer referencia a sus objetos de malla.
El fragmento de código anterior se vería así:
std::vector<std::shared_ptr<mesh> > m_meshes;
void createMesh(...) {
std::shared_ptr<mesh> newMesh = std::make_shared<mesh>();
newMesh->changeMesh(...);
m_meshes.push_back(newMesh);
}
Para asegurarse de no copiar accidentalmente los objetos de todos modos, también es una buena idea declarar constructores de copia no implementados (privados) y operadores de asignación para la clase. Este tema explica cómo hacerlo mejor en C ++ 11: con las funciones miembro eliminadas explícitamente en C ++ 11, ¿todavía vale la pena heredar de una clase base no copiable? .
Creé una clase Mesh para OpenGL 3.3, funciona bien cuando creo la clase con un constructor no predeterminado, cuando creo los vértices cuando creo el objeto.
Sin embargo, ahora quiero tener varios objetos que pueda crear dinámicamente poniéndolos en un vector, así que tuve que agregar un constructor predeterminado. Uso las mismas funciones para configurar los datos del búfer que con el otro constructor ... pero No funciona.
Por lo que puedo decir, no se debe al hecho de que está en el vector, sino que tiene algo que ver con el constructor o con el hecho de que los datos del búfer se crean más tarde.
Realmente no estoy muy seguro.
Aquí están mis clases. (Cuando creo una malla que funciona, llamo al constructor con parámetros y cuando no funciona, construyo una malla sin parámetros y llamo a la función "changeMesh")
mesh.h
#ifndef MESH_H
#define MESH_H
#include <iostream>
#include <vector>
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
class mesh
{
public:
mesh();
mesh(std::vector<GLfloat> vertices, std::vector<GLuint> triangles, GLuint shaderProgram);
~mesh();
void changeMesh(std::vector<GLfloat> vertices, std::vector<GLuint> triangles, GLuint shaderProgram);
void render();
void Translate(glm::vec3 addVector);
void Rotate(glm::vec3 rotVector, GLfloat angle);
protected:
private:
GLuint vertexArrayObject, vertexBuffer, elementBuffer, shaderProgram;
std::vector<GLfloat> vertices;
std::vector<GLuint> indices;
glm::mat4 transform;
void setUpMesh();
void bindVertices();
};
#endif // MESH_H
mesh.cpp
#include "../include/mesh.h"
mesh::mesh(std::vector<GLfloat> vertices, std::vector<GLuint> indices, GLuint shaderProgram)
{
this->shaderProgram = shaderProgram;
this->vertices = vertices;
this->indices = indices;
setUpMesh();
}
mesh::mesh(){
glGenVertexArrays(1, &vertexArrayObject);
glBindVertexArray(vertexArrayObject);
glGenBuffers(1, &vertexBuffer);
glGenBuffers(1, &elementBuffer);
}
mesh::~mesh()
{
glDeleteBuffers(1, &elementBuffer);
glDeleteBuffers(1, &vertexBuffer);
glDeleteVertexArrays(1, &vertexArrayObject);
}
void mesh::changeMesh(std::vector<GLfloat> vertices, std::vector<GLuint> triangles, GLuint shaderProgram){
this->shaderProgram = shaderProgram;
this->vertices = vertices;
this->indices = indices;
bindVertices();
}
void mesh::setUpMesh(){
glGenVertexArrays(1, &vertexArrayObject);
glBindVertexArray(vertexArrayObject);
glGenBuffers(1, &vertexBuffer);
glGenBuffers(1, &elementBuffer);
bindVertices();
glBindVertexArray(0);
}
void mesh::bindVertices(){
glBindVertexArray(vertexArrayObject);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(GLfloat), this->vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), this->indices.data(), GL_STATIC_DRAW);
GLint amountDataPerVert = 5;
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, amountDataPerVert*sizeof(GLfloat), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, amountDataPerVert*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
glBindVertexArray(0);
}
void mesh::render(){
glBindVertexArray(vertexArrayObject);
glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "transform"), 1, GL_FALSE, glm::value_ptr(transform));
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
void mesh::Translate(glm::vec3 addVector){
transform = glm::translate(transform, addVector);
}
void mesh::Rotate(glm::vec3 rotVector, GLfloat angle){
transform = glm::rotate(transform, glm::radians(angle), rotVector);
}