FelixJones.co.uk

Home | About | FAQ | Email | GitHub | LinkedIn | Twitter

June 2nd, 2014

Deferred Renderer

This is something I've been working on over a few days and it continues from my shadow mapping experiments.

Background

I have learnt a lot of new things since creating my first scene engine and I feel comfortable enough to create a new engine entirely from scratch using pure OpenGL, rendered to any context you give it.

For the test application I am using SDL2 for managing the context, but porting to mobile and native platform GL contexts should be very easy to do.


This is my first test of the deferred rendering technique, it is a port of my shadow mapping demonstration program using this new renderer.

On the left of the screen you can see the depth, world normal, shadow and colour buffers that are composed to produce the final result.

Scene Engine

The deferred renderer is built using a custom scene engine made by myself. It organises the scene into a tree and registers nodes into draw lists based on their material. By doing this the shader, texture and array object bindings can be reduced, giving back some performance.

Example Scene

1.xiGraphicsDriver * const graphicsDriver = xiGraphicsDriver::Get(); // xiGraphicsDriver is a singleton
2.xiSceneManager * const sceneManager = xiSceneManager::Create(); // We can have multiple scene engines
3.
4.xiFileSystem * const fileSystem = xiFileSystem::Get(); // xiFileSystem also a singleton
5.
6.xiMaterial * material = nullptr; // Pointer to our default material type
7.
8.// Open the material .wad file, this is UDMF data
9.xiReadFile * const materialFile = fileSystem->CreateAndOpenFile( "material.wad" );
10.if ( materialFile ) {
11.    // Load the material from the wad
12.    material = xiMaterial::CreateFromFile( materialFile );
13.    if ( material ) {
14.        // If the material successfully created, make a cube
15.        xiCubeNode * const cube = xiCubeNode::Create( sceneManager );
16.        if ( cube ) {
17.            // Set the material
18.            cube->SetMaterial( material );
19.
20.            // We can release the cube here as the sceneManager retains a reference
21.            cube->Release();
22.        }
23.        material->Release(); // The cube will retain a reference
24.    }
25.    materialFile->Release(); // No longer need the .wad, so release the memory
26.}
27.
28.fileSystem->Release(); // Don't need to use the file-system anymore
29.
30.// Create a camera
31.xiCameraNode * const camera = xiCameraNode::Create( sceneManager );
32.if ( camera ) {
33.    // Set it to 90 degree FOV with a near depth of 1.0f and far depth of 100.0f
34.    camera->BuildPerspective( 90.0f, ASPECT_RATIO, 1.0f, 100.0f );
35.
36.    // Vector for camera's position and looking target
37.    const vec3_t cameraPosition = { 0.0f, 10.0f, 10.0f };
38.    const vec3_t cameraTarget = { 0.0f, 0.0f, 0.0f };
39.    camera->SetTarget( &cameraTarget );
40.    camera->SetPosition( &cameraPosition );
41.
42.    sceneManager->SetActiveCamera( camera ); // Set this camera as active
43.
44.    camera->Release(); // Release camera reference, sceneManager still retains one
45.}
46.
47.// Clear to the colour white RGBA
48.const rgba_t clearColour = { 1.0f, 1.0f, 1.0f, 1.0f };
49.
50.// While the context is valid (Can be drawn to)
51.while ( FlipContext() ) {
52.    // Clear the screen's depth and colour buffers
53.    // Colour will clear to clearColour's value
54.    graphicsDriver->ClearToColour( &clearColour, xiGraphicsDriver::CLEAR_ALL );
55.
56.    // ::Render() will;
57.    // Update Nodes
58.    // Register them for drawing based on material
59.    // Render null-nodes (Such as camera)
60.    // Render nodes with solid materials
61.    // Render nodes with alpha materials based on distance from camera plane
62.    sceneManager->Render();
63.
64.    // The camera's render call will upload the following matrices to the graphicsDriver;
65.    // view
66.    // projection
67.    // viewProjection
68.
69.    // All other nodes are expected to upload their model matrix to the graphicsDriver
70.}
71.
72.// Release the memory of sceneManager and graphicsDriver
73.// The sceneManager's deconstructor will release the memory of the nodes
74.sceneManager->Release();
75.graphicsDriver->Release();

This might look like a lot of code, but most of that is comments and null-pointer checks, if you can guarantee that your video card supports your desired OpenGL version and that you can load a valid material.wad file then you can pretty much remove all of those null-pointer checks.

Oculus Rift Support

While I don't yet own one, implementing support for VR devices is something I'd like to start doing with my engines, so after setting up the deferred renderer the next thing I did was add a left and right eye camera setup with an Oculus Rift intended distortion shader.

Initialising with VR

1.// Create a texture for drawing everything to
2.// We can then draw this to the screen with the distortion shader
3.textures.vrTexture = xiTexture::Create( SCREEN_WIDTH, SCREEN_HEIGHT, xiTexture::TEXTURE_RGB );
4.textures.vrTexture->SetWrapMode( xiTexture::WRAP_CLAMP );
5.textures.vrTexture->SetFilteringMode( xiTexture::FILTER_NEAREST );
6.
7.// Create a render target
8.// Attach our texture to the target's colour buffer
9.renderTargets.vrTarget = xiRenderTarget::Create();
10.renderTargets.vrTarget->AttachTexture( textures.vrTexture, xiRenderTarget::BUFFER_COLOUR_0 );
11.
12.// Create our main camera
13.// This will be our "head" which moves around
14.cameraNodes.deferredCamera = xiCameraNode::Create( sceneManager );
15.
16.// Create the left and right eyes
17.// They are children to the main camera
18.cameraNodes.leftEye = xiCameraNode::Create( sceneManager, cameraNodes.deferredCamera );
19.cameraNodes.rightEye = xiCameraNode::Create( sceneManager, cameraNodes.deferredCamera );
20.
21.// Offsets of the left eye to the right eye
22.// These are reversed as the camera forwards is -z
23.const vec3_t leftEyeOff = { EYE_DISTANCE, 0.0f, 0.0f };
24.const vec3_t rightEyeOff = { -EYE_DISTANCE, 0.0f, 0.0f };
25.cameraNodes.leftEye->SetPosition( &leftEyeOff );
26.cameraNodes.rightEye->SetPosition( &rightEyeOff );
27.
28.// Load the screen-space warp material
29.xiReadFile * const vrFile = fileSystem->CreateAndOpenFile( "oculus.wad" );
30.if ( vrFile ) {
31.    // This file attaches a vertex, geometry and fragment shader for the warping
32.    // Also sets some constant uniforms for the shader
33.    materials.vrMaterial = xiMaterial::CreateFromFile( vrFile );
34.    // Bind the texture to the texture unit 0 slot for this material
35.    materials.vrMaterial->BindTexture( textures.vrTexture, xiMaterial::SLOT_0 );
36.
37.    vrFile->Release(); // Release the vrFile memory
38.}

VR Render Loop

1.if ( isVirtualReality ) {
2.    // Half the screen will be for each eye
3.    const uint16_t halfWindowWidth = ( windowWidth >> 1 );
4.
5.    // Get current active camera (Should be the main camera)
6.    xiCameraNode * const activeCamera = sceneManager->GetActiveCamera();
7.
8.    const vec3_t * const cameraTarget = activeCamera->GetTarget(); // Get camera's looking target
9.    const vec3_t * const cameraAbs = activeCamera->GetAbsolutePosition(); // Camera's world position
10.    const mat4_t * const cameraProjection = activeCamera->GetProjectionMatrix();
11.        
12.    vec3_t eyeDir;
13.    // Get the direction vector between the target and position
14.    Vector_Sub( &eyeDir, cameraTarget, cameraAbs );
15.
16.    // Get the position of the eyes within the world
17.    const vec3_t * const leftAbs = cameraNodes.leftEye->GetAbsolutePosition();
18.    const vec3_t * const rightAbs = cameraNodes.rightEye->GetAbsolutePosition();
19.        
20.    vec3_t leftTarg;
21.    vec3_t rightTarg;
22.    // Add the direction vector to each of the eye positions for their target
23.    Vector_Add( &leftTarg, leftAbs, &eyeDir );
24.    Vector_Add( &rightTarg, rightAbs, &eyeDir );
25.
26.    // Update their target positions so the eyes are looking straight forward
27.    cameraNodes.leftEye->SetTarget( &leftTarg );
28.    cameraNodes.rightEye->SetTarget( &rightTarg );
29.
30.    // Copy in the original camera's projection matrix
31.    cameraNodes.leftEye->SetProjectionMatrix( cameraProjection );
32.    cameraNodes.rightEye->SetProjectionMatrix( cameraProjection );
33.
34.    // Set the gl draw area to the left side of the window
35.    glViewport( 0, 0, halfWindowWidth, windowHeight );
36.    sceneManager->SetActiveCamera( cameraNodes.leftEye ); // This will set the matrices
37.    sceneManager->Draw(); // Draw left eye's view
38.    
39.    // Set the gl draw area to the right side of the window
40.    glViewport( halfWindowWidth, 0, halfWindowWidth, windowHeight );
41.    sceneManager->SetActiveCamera( cameraNodes.rightEye );
42.    sceneManager->Draw(); // Draw right eye's view
43.
44.    // Reset the gl draw area to the full window
45.    glViewport( 0, 0, windowWidth, windowHeight );
46.
47.    // Target the VR buffer so we can composit the scene to it (Deferred rendering)
48.    // Only need to clear the colour for this target as it has no depth attachment
49.    graphicsDriver->SetRenderTarget( renderTargets.vrTarget, xiGraphicsDriver::CLEAR_COLOUR );
50.
51.    // Screen material is our deferred compositor
52.    graphicsDriver->SetMaterial( materials.screenMaterial );
53.    // Disable depth (2D rendering)
54.    graphicsDriver->SetDepth( xiGraphicsDriver::DEPTH_NONE );
55.
56.    // This will draw using glDrawArrays( GL_POINTS, 0, 1 );
57.    // So a geometry shader is needed
58.    // Our one will draw to the screen
59.    graphicsDriver->DrawMaterial();
60.
61.    // Target the context and clear it's colour
62.    // We never used the context's depth buffer
63.    graphicsDriver->SetRenderTarget( nullptr, xiGraphicsDriver::CLEAR_COLOUR );
64.
65.    // Set to the distortion screen-shader
66.    graphicsDriver->SetMaterial( materials.vrMaterial );
67.} else {
68.    // ... default drawing
69.    graphicsDriver->SetDepth( xiGraphicsDriver::DEPTH_NONE );
70.}
71.
72.// Draw to the context with current material
73.// When using VR this should be the distortion shader
74.// When not using VR this should be the deferred compsitor
75.graphicsDriver->DrawMaterial();
76.
77.// Enable depth r/w for normal 3D scene rendering
78.graphicsDriver->SetDepth( xiGraphicsDriver::DEPTH_READ_WRITE );

Terminating VR

1.materials.vrMaterial->Release(); // Release material
2.
3.// Release camera eyes and head
4.cameraNodes.rightEye->Release();
5.cameraNodes.leftEye->Release();
6.cameraNodes.deferredCamera->Release();
7.
8.textures.vrTexture->Release(); // Release target texture
9.renderTargets.vrTarget->Release(); // Release target

Comments