I'm currently refactoring my OpenGL program (used to be one single enormous file) to use C++ classes. The basic framework looks like this:
I have an interface Drawable
with the function virtual void Render(GLenum type) const = 0;
and a bunch of classes implementing this interface (Sphere
, Cube
, Grid
, Plane
, PLYMesh
and OBJMesh
).
In my main.cpp
I'm setting up a scene containing multiple of these objects, each with its own shader program. After setting uniform buffer objects and each program's individual uniforms, I'm calling glutMainLoop()
.
In my Display
function called each frame, the first thing I'm doing is setting up all the transformation matrices and finally call the above mentioned Render
function for every object in the scene:
void Display()
{
// Clear framebuffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix = glm::mat4(1.0);
projectionMatrix = glm::mat4(1.0);
normalMatrix = glm::mat4(1.0);
modelViewMatrix = glm::lookAt(glm::vec3(0.0, 0.0, mouse_translate_z), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));
modelViewMatrix = glm::rotate(modelViewMatrix, -mouse_rotate_x, glm::vec3(1.0f, 0.0f, 0.0f));
modelViewMatrix = glm::rotate(modelViewMatrix, -mouse_rotate_y, glm::vec3(0.0f, 1.0f, 0.0f));
projectionMatrix = glm::perspective(45.0f, (GLfloat)WINDOW_WIDTH / (GLfloat)WINDOW_HEIGHT, 1.0f, 10000.f);
// No non-uniform scaling (only use mat3(normalMatrix in shader))
normalMatrix = modelViewMatrix;
glBindBuffer(GL_UNIFORM_BUFFER, ubo_global_matrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(modelViewMatrix));
glBufferSubData(GL_UNIFORM_BUFFER, 1 * sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(projectionMatrix));
glBufferSubData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(normalMatrix));
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// ************************************************** //
// **************** DRAWING COMMANDS **************** //
// ************************************************** //
// Grid
if (grid->GetIsRendered())
{
program_GRID_NxN->Use();
grid->Render(GL_LINES);
program_GRID_NxN->UnUse();
}
// Plane
...
// Sphere
...
// Swap front and back buffer and redraw scene
glutSwapBuffers();
glutPostRedisplay();
}
My question now is the following: With the current code, I'm using the same ModelView
matrix for every object. What if I wanna translate only the sphere, or rotate only the plane without changing the vertex positions? Where is the best place to store the model matrix in a large OpenGL program? What about putting a protected member variable glam::mat4 modelMatrix
into the Drawable
interface? Also, should the model and the view matrix be split (for example using a Camera
class containing the view matrix only)?
My answer is mainly based off Tom Dalling's excellent tutorial, but with some minor changes.
Firstly all your view and projection matrix operations should go in the Camera class. Camera will provide a convenient way of getting the view and projection matrix by calling the matrix()
method.
glm::mat4 Camera::matrix() const {
return projection() * view();
}
Then for this example you'd have an Model Asset, which contains everything you need to render the geometry. This asset should be unique and stored in a ResourceManager or something similar.
struct ModelAsset {
Shader* shader;
Texture* texture;
GLuint vbo;
GLuint vao;
GLenum drawType;
GLint drawStart;
GLint drawCount;
};
Then you have an Model Instance, which has a pointer to the assest plus a unique transform matrix. This way you can create as many instances of a particular asset each one with its own unique transformation.
struct ModelInstance {
ModelAsset* asset;
glm::mat4 transform;
};
ModelInstance cube;
cube.asset = &asset; // An asset that you created somewhere else (e.g. ResourceManager)
cube.transform = glm::mat4(); // Your unique transformation for this instance
To render an instance you pass the view and model matrix as uniforms to the shader, and shader does the rest of the work.
shaders->setUniform("camera", camera.matrix());
shaders->setUniform("model", cube.transform);
Finally it's best when all your instances are grouped nicely in some resizable container.
std::vector<ModelInstance> instances;
instances.push_back(cube);
instances.push_back(sphere);
instances.push_back(pyramid);
for (ModelInstance i : instances) {
i.transform = glm::rotate(i.transform, getTime(), glm::vec3(0.0f, 1.0f, 0.0f));
}
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments