A more complicated example

As you got familiar with the basics of the basics, it's now time to dwelve deeper into the nGENE and use other nodes types as well.
In this tutorial we will add a few lights and load meshes from files made in external editors.

In the last part we added a crate. As you remember we also disabled lights. Lights and shadows are factors which greatly enhance quality of the rendered scene and add sense of depth. nGENE Tech currently uses a deferred shading lighting technique. It gained great attention after DirectX 10 was released and is used by Climax, Epic and partialy by CryTek companies. Its biggest advantage lays in that you can add multiple lights and all of them are rendered in constant time instead of being related to geometry complexity.

In this tutorial we will load a .x file statue model which will cast shadows on the ground.

Lets begin with application class definition:

#include "FrameworkWin32.h"
#include "ScenePartitionerQuadTree.h"
 
using namespace nGENE::Application;
using nGENE::ScenePartitionerQuadTree;
 
class App: public FrameworkWin32
{
private:
    ScenePartitionerQuadTree* m_pPartitioner;
 
public:
    App() {}
    ~App() {}
 
    void createApplication();
};

The only difference in contrast to previous tutorial is introduction of ScenePartitionerQuadTree class. As you probably are aware of rendering too many objects would impact performance of the application. As in the majority of scenes most of objects are not visible for most of the time (eg. everything what is behind camera) it would be nothing else but waste. ScenePartitionerQuadTree which derives from ScenePartitioner class is responsible for filtering out this invisible objects.
Now, as we are rendering shadows in this tutorial we need to produce shadow maps which are rendered from light position, not the camera. Therefore we have to ensure that when we render shadow maps only visible nodes are rendered as well.

Now lets move to the class definition. We skip the main function as it stays the same.

void App::createApplication()
{
    FrameworkWin32::createApplication();
 
    m_pPartitioner = new ScenePartitionerQuadTree();
    SceneManager* sm = Engine::getSingleton().getSceneManager(0);
    sm->setScenePartitioner(m_pPartitioner);
 
    Renderer& renderer = Renderer::getSingleton();
    renderer.setClearColour(0);
    renderer.setCullingMode(CULL_CW);
    uint anisotropy = 0;
    renderer.getDeviceRender().testCapabilities(CAPS_MAX_ANISOTROPY, &anisotropy);
    for(uint i = 0; i < 8; ++i)
        renderer.setAnisotropyLevel(i, anisotropy);
 
    Camera* cam;
    cam = sm->createCamera(L"CameraFirstPerson", L"Camera");
    cam->setPosition(Vector3(0.0f, 5.0f, -10.0f));
    sm->getRootNode()->addChild(L"Camera", cam);
    Engine::getSingleton().setActiveCamera(cam);
 
    NodeMesh <MeshLoadPolicyXFile>* pSculp = sm->createMesh <MeshLoadPolicyXFile>(L"statue.x");
    pSculp->setPosition(0.0f, 0.0f, 7.0f);
    pSculp->setScale(0.25f, 0.25f, 0.25f);
    Surface* pSurface = pSculp->getSurface(L"surface_2");
    pSurface->flipWindingOrder();
    Material* matstone = MaterialManager::getSingleton().getLibrary(L"default")->getMaterial(L"dotCel");
    pSurface->setMaterial(matstone);
    sm->getRootNode()->addChild(L"Sculpture", *pSculp);
 
    Plane pl(Vector3::UNIT_Y, Point(0.0f, 0.0f, 0.0f));
    PrefabPlane* plane = sm->createPlane(pl, Vector2(400.0f, 400.0f));
    plane->setPosition(0.0f, 1.4, 0.0f);
    pSurface = plane->getSurface(L"Base");
    Material* matGround = MaterialManager::getSingleton().getLibrary(L"default")->getMaterial(L"normalMap");
    pSurface->setMaterial(matGround);
    sm->getRootNode()->addChild(L"Plane", plane);
 
    NodeLight* pLight = sm->createLight(LT_DIRECTIONAL);
    pLight->setPosition(-4.0f, 5.0f, 0.0f);
    pLight->setDirection(Vector3(0.7f, -0.5f, 1.0f));
    pLight->setDrawDebug(false);
    Colour clrWorld(204, 204, 0);
    pLight->setColour(clrWorld);
    pLight->setRadius(180.0f);
    sm->getRootNode()->addChild(L"WorldLight", *pLight);
    renderer.addLight(pLight);
}

We began by creating ScenePartitioner object. Apart from default constructor, ScenePartitionerQuadTree class has also constructor to which you can pass QUAD_TREE_DESC object. It describes characteristics of the quad tree (its size, minimum node size). However, default options fit our needs.

Then we specify a few rendering options:

  • clear colour - is responsible for background fill colour. We use black as in current version other colours may interfere with some post processing effects.
  • culling mode - is responsible for culling back faced polygons. We use CULL_CW option to cull them. Sometimes it may be necessary to disable culling, then use CULL_NONE option.
  • anisotropy level - we also set anisotropic filtering level to the maximum supported by your graphical adapter. We do not need to enable anisotropic filtering itself, as it is turned on by default.

Note we are not turning lights on as they are enabled by default.

After creating camera we are creating NodeMesh object. Compared to other create() methods this call might look a bit strange at first:

sm->createMesh <MeshLoadPolicyXFile>(L"statue.x");

However, it is very easy. As there are multiple 3D model formats (.3ds, .lwo, .obj, .x to name a few) nGENE does not provide special NodeMesh class for each of them. Instead it allows user to create format loaders and use them as policies for NodeMesh class. In the example above we use MeshLoadPolicyXFile which loads Microsoft DirectX .x files. Note that we are applying dotCel material to the loaded Mesh. dotCel is nothing else but popular toon shading material.

Then we are creating ground plane. First we create plane itself and then PrefabPlane node object. Nothing special here.

And then we are creating NodeLight which is … light of course:

NodeLight* pLight = sm->createLight(LT_DIRECTIONAL);
pLight->setPosition(-4.0f, 5.0f, 0.0f);
pLight->setDirection(Vector3(0.7f, -0.5f, 1.0f));
pLight->setDrawDebug(false);
Colour clrWorld(204, 204, 0);
pLight->setColour(clrWorld);
pLight->setRadius(180.0f);
sm->getRootNode()->addChild(L"WorldLight", *pLight);
renderer.addLight(pLight);

We create it passing LT_DIRECTIONAL literal. It means we are creating directional light. Then we set some attributes and in two last lines we are adding it to the engine. Both method calls are necessary to have scene lit.

Now you can compile and run…

Tut_1_2_2.jpg

Much better now! But still a long way to go to learn nGENE Tech. Note that shadows are rendered even though we did not specify to. nGENE uses shadows for all lights and materials by default.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License