3
\$\begingroup\$

I'm writing a C++ game engine and I have a Renderer class that I'd like to present as a code example to a team that are interested in my skills.

The renderer is used to setup the OpenGL environment and then provides a RenderMesh function that will render a mesh with a texture and shader using the transformations for the view and the mesh.

I'm fairly new to OpenGL so I'm looking for places where I've made stupid mistakes.

Renderer.h

#pragma once

#include <iostream>
#include "GL\glew.h"
#include "GL\freeglut.h"
#include "glm\vec4.hpp"
#include "Structures\Mesh.h"
#include "Structures\Texture.h"
#include "Structures\Shader.h"

namespace Core
{
   // A class that offers functionality to render types of objects given
   // that objects definition and transform. In general it can be used 
   // to render 3D meshes using OpenGL shaders. This is an OpenGL specific
   // implementation for this project.
   class Renderer
   {
   public:
      // Creates a new renderer with a specific background colour, this 
      // is the colour displayed behind rendered objects in the view.
      Renderer(const glm::vec4& backgroundColour);

      // Initialises the renderer by setting up the OpenGL environment. 
      virtual void Initialise() const;

      // Sets the renders matrix mode, defaults to projection. 
      virtual void SetupMatrixMode() const;

      // Clears the render to the background colour set when the renderer
      // was constructed
      virtual void Clear() const;

      // Used to facilitate double buffering
      virtual void SwapBuffers() const;

      // Renders a given mesh with a texture and a shader. The projection
      // and view transforms are supplied to specify the position and orientation 
      // of the render view and the model transform specifies the position,
      // orientation and scale of the mesh.
      virtual void RenderMesh(
         const Structures::Mesh& mesh, 
         const Structures::Texture& texture,
         const Structures::Shader& shader,
         const glm::mat4& projectionTransform,
         const glm::mat4& viewTransform,
         const glm::mat4& modelTransform
         );

   private:
      glm::vec4 backgroundColour;

      // This variable is supplied to optimise the render code, if the same
      // shader program is needed more than once repeatedly it is not 
      // reassigned to OpenGL unnecessarily. 
      GLuint lastUsedShaderProgram;
   };
}

Renderer.cpp

#include "Renderer.h"

namespace Core
{
   Renderer::Renderer(const glm::vec4& backgroundColour) :
      backgroundColour(backgroundColour),
      lastUsedShaderProgram(-1)
   {
      glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); // DEPTH: use depth buffer, DOUBLE: double render to avoid flicker, RGBA: colour channel
   }

   void Renderer::Initialise() const 
   {
      glewInit(); // Must be called after window has been initialized

      IsGlewSupported();

      glEnable(GL_DEPTH_TEST); // turn on depth testing using the graphics card
      glPolygonMode(GL_FRONT, GL_FILL);

      // Enable alpha blending
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

      glFrontFace(GL_CW); // Can be used to specify face vertex ordering
   }

   void Renderer::SetupMatrixMode() const 
   {
      glMatrixMode(GL_PROJECTION);
   }

   void Renderer::Clear() const 
   {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear colour and depth bits
      glClearColor(backgroundColour.x, backgroundColour.y, backgroundColour.z, backgroundColour.w); // Clear to black
   }

   void Renderer::SwapBuffers() const 
   {
      glutSwapBuffers(); 
   }

   void Renderer::RenderMesh(
      const Structures::Mesh& mesh, 
      const Structures::Texture& texture,
      const Structures::Shader& shader,
      const glm::mat4& projectionTransform,
      const glm::mat4& viewTransform,
      const glm::mat4& modelTransform
      )
   {
      // Bind vertices
      glBindVertexArray(mesh.GetVertexArrayObject());

      // Setup program
      GLuint currentShaderProgram = shader.GetProgram();
      if(currentShaderProgram != lastUsedShaderProgram)
      {
         glUseProgram(currentShaderProgram);
      }
      lastUsedShaderProgram = currentShaderProgram;

      // Apply the texture to the shader texture sampler
      glActiveTexture(GL_TEXTURE0);
      glBindTexture(GL_TEXTURE_2D, texture.GetTextureObject());
      glUniform1i(shader.GetTextureSampler(), 0);

      // Calculate transformation
      glm::mat4 mvpTransform = projectionTransform * viewTransform * modelTransform;

      // Apply transformation
      GLuint shaderMvpTransform = shader.GetMvpTransform();
      glUniformMatrix4fv(shaderMvpTransform, 1, GL_FALSE, &mvpTransform[0][0]);

      // Draw
      glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh.GetVertices().size()); // GL_LINE_LOOP for wire mesh, GL_TRIANGLES for filled mesh
   }
}
\$\endgroup\$
5
\$\begingroup\$

A few tips:

  1. One-stage initialization is preferred. Technically, everything has an initialization process, but by declaring that everywhere, you defeat the point of a constructor. It also aligns with the RAII principles where the object isn't even finished being created if any initialization fails. One of the exceptions here is that with rendering processes, try/catch blocks can be a bit slower. Since this is a set-up routine, it should be okay.

  2. The "Renderer" type, in design, appears to be an abstract renderer, whether that be OpenGL, D3D, SDL, etc. So, it seems like you would want an "OpenGL" class that inherits from "Renderer" and put the related private members there.

  3. There really are a lot of things a renderer does, so you have to really think about interactions. Blending is pretty important, z-order drawing - usually used for transparency. What about things like anti-aliasing (MSAA, FXAA, etc.)? Are these renderer agnostic or does it pertain to the renderer? How about lighting? These are all design questions that need to be answered.

  4. If you are to load a, say, 3DS file with "assimp" as an example, textures may be included with the mesh. In this case, you may want special "default" objects to pass into the other parameters or change your design to have "DrawingEntities", where positions as well as their attributes such as textures, are defined. Then, you'd just say you want to render whatever this "DrawingEntity" is. You do have to keep in mind z-order with this approach, though!

In general though, I like your organization/style. I can't say that often. Good luck!

\$\endgroup\$
  • 1
    \$\begingroup\$ I like your first answer on this site. I can't say that often. Good luck! ;) (being totally serious here) \$\endgroup\$ – Nic Hartley Jan 8 '16 at 0:03
1
\$\begingroup\$

Just a few thoughts :

Your rendering code uses an uniform to store the transform matrix, so your call to glMatrixMode(GL_PROJECTION); is probably useless (and should be removed).

I also saw that your call to glClearColor should be done before the glClear call.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.