Skip to content

Render your first mesh

Johan Nilsson Hansen edited this page Nov 26, 2017 · 26 revisions

This tutorial will go through all the steps of the code needed to load and render a mesh on a GameObject. Setup game project with Bubba3D using CMake is a prerequisite for this tutorial. The code will not compile unless this is setup in advance.

Includes and constants

We start off by including needed header files and defining some constants that we are going to use later in the code.

#include <GL/glew.h>
#include <GL/freeglut.h>
#include <PerspectiveCamera.h>
#include <StandardRenderer.h>
#include <Scene.h>
#include <GameObject.h>
#include "ShaderProgram.h"
#include "Window.h"
#include "Renderer.h"
#include "ResourceManager.h"
#include "constants.h"

Renderer *renderer;

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

Camera

Then we add a function for setting up the camera. The perspective camera works by capturing everything inside a pyramid starting at the camera position and the middle of the base residing in the "look-at" position. The width and height is defined by the field of view together with the aspect ratio.

Camera *camera;
static const chag::float3 UP_VECTOR = chag::make_vector(0.0f, 1.0f, 0.0f);
void setupCamera() {
  camera = new PerspectiveCamera(
               chag::make_vector(10.0f, 10.0f, 10.0f), // Camera position
               chag::make_vector(0.0f, 0.0f, 0.0f),    // Look-at
               UP_VECTOR,                        // Reference vector
               45,                               // Field of view in degrees
               float(SCREEN_WIDTH) / float(SCREEN_HEIGHT), // aspect ratio
               0.1f,                             // Near clip
               50000.0f);                        // Far clip
}

Loading the mesh

In Bubba-3D every bit of game logic and all rendering is done via GameObjects. GameObjects represent entities in the game that has a model matrix and Components that execute things. Components are class instances with a specific purpose that are attached to GameObjects in order to give the GameObjects meaning. An example of such a Component is StandardRenderer which we will use in this tutorial to render our mesh.

The constructor for StandardRenderer takes three parameters:

  1. The mesh that shall be renedered at the GameObjects position.
  2. A reference to the GameObject it will be attached to.
  3. A shader to use while rendering.

When StandardRenderer is created and attached to the GameObject, player in this case, the GameObject is ready to be inserted into the scene. As the mesh will cast shadows, we add the GameObject to the scene by calling addShadowCaster( ... ) on the Scene object. If the mesh were transparent we would call addTransparentObject( ... ) instead.

std::shared_ptr<GameObject> player;
Scene scene;

void loadMeshes() {
  /* Shader setup done once for all meshes that use it */
  std::shared_ptr<ShaderProgram> standardShader = ResourceManager::loadAndFetchShaderProgram(SIMPLE_SHADER_NAME, "", "");

  /* Load player mesh and attach it to the player GameObject */
  std::shared_ptr<Mesh> playerMesh = ResourceManager::loadAndFetchMesh("../meshes/player.obj");
                                            // references are from the the build folder

  player = std::make_shared<GameObject>(playerMesh);
  player->setLocation(chag::make_vector(0.0f, 0.0f, 0.0f));
  StandardRenderer* stdrenderer = new StandardRenderer(playerMesh, standardShader);
  player->addRenderComponent(stdrenderer);
  player->setDynamic(true);

  /* Add the player to the scene */
  scene.addShadowCaster(player);
}

Lights

We also need some lighting in the scene, otherwise all rendered objects will be pitch black. There are several different types of lights, but for now lets add a simple directional light to the scene.

void loadLights() {
    std::shared_ptr<DirectionalLight> directionalLight = std::make_shared<DirectionalLight>();
    directionalLight->diffuseColor= chag::make_vector(0.50f,0.50f,0.50f);
    directionalLight->specularColor= chag::make_vector(0.050f,0.050f,0.050f);
    directionalLight->ambientColor= chag::make_vector(0.500f,0.500f,0.500f);

    directionalLight->direction= -chag::make_vector(0.0f,-10.0f,10.0f);
    scene.directionalLight = directionalLight;
}

Main and loop functions

Finally we setup the main function which you may know is the starting point for the program. In the main function, we define the window and the renderer, setup callback functions for the different window events and call the loadMesh and setupCamera functions that we defined earlier.

// Called when not drawing
void idle(float timeSinceStart,float timeSinceLastCall) {
  scene.update(timeSinceLastCall);
}

// Called by the window mainloop
void display(float timeSinceStart,float timeSinceLastCall) {
  renderer->drawScene(camera, &scene, timeSinceStart);
}

// Called when the window gets resized
void resize(int newWidth, int newHeight) {
  renderer->resize(newWidth, newHeight);
}

// Called at start of the program
int main(int argc, char *argv[]) {
  Window* win = new Window(SCREEN_WIDTH, SCREEN_HEIGHT, "Awesome Bubba-3D Game");

  // Setup callbacks for window events
  win->setResizeMethod(resize);
  win->setIdleMethod(idle);
  win->setDisplayMethod(display);

  // Create and initialize the Renderer
  renderer = new Renderer();
  renderer->initRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);

  loadMeshes();
  loadLights()
  setupCamera();

  win->start(60);
  return 0;
}