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 |