Example Application

There are a number of examples included to get started developing using Shade. What follows is the first tutorial for Shade, Hello World. This example is overly commented to give an idea of what is going on with the program, and is meant to give an idea on what an application built in Shade will resemble.

ShadeHelloWorldApplication.hpp

#ifndef _SHADE_HELLO_WORLD_APPLICATION_HPP_
#define _SHADE_HELLO_WORLD_APPLICATION_HPP_

// Includes all the Shade classes for the Application
#include "Shade.hpp"

namespace Shade
{
  /**
   * A simple hello world application for Shade.
   *
   * In the first tutorial for Shade we create a simple scene
   * showcasing the Earth.
   *
   * This tutorial discusses: Creating a ShaderEffect, Laying out the
   * scene, and moving the Camera around the scene.
   *
   * \note The code for this is meant as a tutorial and contains a 
   *       ridiculous amount of commenting.
   * \author Don Olmstead
   * \version 1.0
   */
  class HelloWorldApplication : public SHADE_WINDOWED_APPLICATION_CLASS
  {
    public:
      HelloWorldApplication();
      virtual ~HelloWorldApplication();

      virtual void onOpen();
      virtual void onClose();
      virtual void onIdle(double time);

      virtual void onMotion(int x, int y);

    protected:
      /** The root node of the scene */
      NodePtr rootNode;
      /** The pivot node of the Camera */
      NodePtr cameraPivot;
      /** The camera associated with the scene */
      CameraNodePtr cameraNode;
      /** The Earth */
      SpherePtr earth;

      void attachEarthEffect();
  } ;
}

#endif

ShadeHelloWorldApplication.cpp

#include "ShadeHelloWorldApplication.hpp"
using namespace Shade;

/**
 * Implements the WindowedApplication.
 *
 * This macro creates the main entrypoint into the application.
 * Each WindowedApplication class you make will require this macro
 * in order to start the application.
 *
 * This macro expands to the int main() function and hooks
 * the application.
 */
SHADE_IMPLEMENT_WINDOWED_APPLICATION(HelloWorldApplication);

/**
 * Default constructor for HelloWorldApplication.
 */
HelloWorldApplication::HelloWorldApplication()
{
  // Set any of the member variables to NULL
  // Do NOT initialize any member variables in the constructor
  // Do all initialization in the onOpen() method.
  rootNode    = 0;
  cameraPivot = 0;
  cameraNode  = 0;
  earth       = 0;
}

/**
 * Destructor for HelloWorldApplication.
 */
HelloWorldApplication::~HelloWorldApplication()
{
  // All the member variables should have been removed
  // by the time we hit this method. So for the most part
  // this method will be left blank.
}

/**
 * Initializes the application.
 *
 * In onOpen we open up any windows we require and initialize
 * our member variables. We also setup the scene and the render
 * target(s) in this method.
 */
void HelloWorldApplication::onOpen()
{
  // We want to call the parent method just in case there
  // are any tasks that need to be done by the parent class.
  // The parent method should be called at the start of the
  // child's method.
  SHADE_CALL_PARENT_APPLICATION_METHOD(onOpen());

  // Open a Window to display to.
  openWindow("Hello World (Shade Tutorial #1)", 10, 10, 1433, 501);

  // Create the root node of the scene.
  rootNode = new Node();

  // Create the camera pivot
  cameraPivot = new Node();

  // Create the Camera used to display the scene.
  CameraPtr camera = new Camera();

  // There are two different ways to set the frustum.
  // The first is by setting all the values yourself.
  // The second, which is the one we're using, sets the
  // frustum using a method that mirrors the OpenGL
  // function gluPerspective.
  camera->setFrustum(35.0, 14.0 / 5.0, 1.0, 100.0);

  // Create the CameraNode and attach the Camera to it.
  // The CameraNode class allows a Camera to be moved around
  // the scene the same as any other Object in the scene.
  cameraNode = new CameraNode(camera);
  cameraNode->moveInZ(5.0f);

  // Create the Earth and then create its effect.
  earth = new Sphere(1.0f, 64, 64);
  attachEarthEffect();

  // Create a light for the scene.
  PointLightPtr light = new PointLight();

  // Create the LightNode and attach the Light to it.
  // The LightNode class allows a Light to be moved around
  // the scene the same as any other Object in the scene.
  LightNodePtr lightNode = new LightNode(light);
  lightNode->moveInZ(5.0f);

  // Attach the Light to the root node so it effects all
  // items in the scene
  rootNode->attachLight(light);

  // Attach all the Nodes and Geometries to the root node of
  // the scene. The structure will look like this
  //        rootNode ----- lightNode
  //       /        \
  //  cameraPivot  earth
  //      |
  //  cameraNode
  cameraPivot->attachChild(cameraNode);

  rootNode->attachChild(cameraPivot);
  rootNode->attachChild(earth);
  rootNode->attachChild(lightNode);

  // Update the state of the scene. This method is used to update
  // the scene, which includes setting the location of the camera,
  // updating any bounding volumes, and anything else that has to
  // do with changes within the scene based on time.
  rootNode->updateGS();
  rootNode->updateRS();

  // Finally we create the ScreenRenderTarget and attach
  // all the necessary data to it.
  ScreenRenderTargetPtr renderTarget = new ScreenRenderTarget();
  renderTarget->setCamera(camera);
  renderTarget->setRootNode(rootNode);

  // Whenever you want to see what Objects are currently created
  // the Object::printObjectsInUse method can be used. This method
  // is called at startup and at termination to show what Objects
  // were created during initialization and what Objects still exist
  // at termination. We'll call it here to show what Objects we've
  // created at this point.
  Object::printObjectsInUse(std::cout);
}

/**
 * Closes the application.
 *
 * In onClose we cleanup any objects we've created during the
 * course of the application.
 */
void HelloWorldApplication::onClose()
{
  // Remove any member variables from the application.
  // Since we use smart pointers for our Objects all we need
  // to do is to set all of them to NULL (0) and they will 
  // delete themselves.
  rootNode   = 0;
  cameraNode = 0;
  earth      = 0;

  // We want to call the parent method just in case there
  // are any tasks that need to be done by the parent class.
  // The parent method should be called at the end of the
  // child's method.
  SHADE_CALL_PARENT_APPLICATION_METHOD(onClose());

  // If there are any Objects that are left over by the time
  // the program is being terminated an assert will be thrown
  // when compiled in debug mode.
}

/**
 * Idle loop for the application.
 *
 * Here we update the scene. Use this method to do any time
 * based animations.
 */
void HelloWorldApplication::onIdle(double time)
{
  // Rotate the Earth around. An arbitrary rotation is
  // picked for this application. We will update this
  // in the next tutorial to update properly based on the
  // time the application is running
  earth->rotateInY(0.2f);

  // We want to update the global state of the scene. This
  // will update any controllers attached to the scene, and will
  // update any Camera's attached to the scene, among other things.
  rootNode->updateGS(0);

  // We want to call the parent method just in case there
  // are any tasks that need to be done by the parent class.
  // The parent method should be called at the end of the
  // child's method.
  SHADE_CALL_PARENT_APPLICATION_METHOD(onIdle(time));
}

/**
 * Motion callback for the application.
 *
 * Motion is defined as a mouse button being held while the mouse
 * pointer is moved. This method is used to handle those events.
 *
 * \param x The x position of the mouse pointer.
 * \param y The y position of the mouse pointer.
 */
void HelloWorldApplication::onMotion(int x, int y)
{
  // We want to call the parent method just in case there
  // are any tasks that need to be done by the parent class.
  // The parent method should be called at the start of the
  // child's method.
  SHADE_CALL_PARENT_APPLICATION_METHOD(onMotion(x, y));

  // A wrapper class is provided for the mouse. This allows
  // for state querying. In our case we want to see the change
  // in position of the Mouse since the last callback.
  int changeX, changeY;

  mouse->getChangeInPosition(changeX, changeY);

  // If the left button is pressed we want to rotate around
  // the camera pivot node
  if (mouse->isPressed(Mouse::LEFT))
    cameraPivot->rotate(changeY * 0.1f, changeX * 0.1f, 0.0f);

  // If the right button is pressed we want to move the camera
  // forward or backward
  if (mouse->isPressed(Mouse::RIGHT))
    cameraNode->moveInZ(changeY * 0.1f);
}

/**
 * Attaches a ShaderEffect to the Earth.
 *
 * This method illustrates how to create an arbitrary ShaderEffect
 * and how to use it. Since the Effect framework is very important for
 * using Shade this gives a very basic example on how to use it.
 *
 * The effect we are using is from the OpenGL Shading Language book
 * (aka The Orange Book) for texturing the Earth in a realistic manner.
 * An explanation of how the shader works is outside of the scope of 
 * this tutorial, but is available in Chapter 10 of the Orange Book.
 */
void HelloWorldApplication::attachEarthEffect()
{
  // First we create the VertexShader, FragmentShader and
  // ShaderProgram and bind them together.
  VertexShaderPtr   earthVertex;
  FragmentShaderPtr earthFragment;

  earthVertex   = new VertexShader  ("Shaders/Earth.vert");
  earthFragment = new FragmentShader("Shaders/Earth.frag");

  ShaderProgramPtr earthProgram = new ShaderProgram();

  earthProgram->attachShaderObject(earthVertex);
  earthProgram->attachShaderObject(earthFragment);

  // Next we create the ShaderEffect and attach the
  // ShaderProgram to it.
  ShaderEffectPtr earthEffect = new ShaderEffect();

  earthEffect->setShaderProgram(earthProgram);

  // The shader requires 3 textures to do its work.
  // There is one for the daytime view. One for the nightime view.
  // And one for the clouds.
  TexturePtr dayTime   = new Texture("Images/Day.jpg");
  TexturePtr nightTime = new Texture("Images/Night.jpg");
  TexturePtr clouds    = new Texture("Images/Clouds.jpg");

  // Now we attach the Textures to the effect. We attach
  // each Texture to a different texture unit. Most video cards
  // have 8 texture units, but this isn't always the case so make
  // sure that there are enough texture units for any effect you
  // are creating.
  earthEffect->setTexture(dayTime,   0);
  earthEffect->setTexture(nightTime, 1);
  earthEffect->setTexture(clouds,    2);

  // Now we set any uniform variables used by the program.
  // The setUniform method is templated so we can use whatever 
  // primitive type we want and the compiler will sort out what
  // OpenGL function to call.
  earthEffect->setUniform("LightIndex",      0);
  earthEffect->setUniform("EarthDay",        0);
  earthEffect->setUniform("EarthNight",      1);
  earthEffect->setUniform("EarthCloudGloss", 2);

  // Finally we attach the Effect to the Earth, which we're
  // assuming is already constructed.
  earth->attachEffect(earthEffect);

  // The Effect is attached to the Sphere and now it will use
  // the shader to render itself. As you can see we would get
  // cleaner code if we made an EarthEffect class. Moving this
  // code to a new class will happen in the next tutorial.
}

The Result

Result on a single display Result on a tiled display