Rendering

Our game engine is a modern, very fast and cache-optimized application. The rendering system is in charge of all the drawing commands sent to the GPU, being a core system in our application. The main API used for rendering is OpenGL 4.6.

Our rendering system contains a directed-acyclic render graph structure in which the render-passes are enqueued and ordered based on their dependencies. It is currently implemented linearly and it also represents a very useful addition for a possible future integration of latest-gen APIs such as Vulkan, DirectX 12 and Metal 2, being helpful especially for synchronization purposes. However, in our circumstances, the render graph helps us in determining the correct order in which the scene needs to be rendered.


Each render-pass has the possibility of en-queuing multiple subcommands dynamically. Our rendering abstraction layer can be extended to incorporate specific commands for newer graphics APIs.

Each rendering feature is enabled only when needed and thus, we remove the need to use branching in our main rendering loop.

For example, when a feature is toggled in the editor, a command will be sent to the rendering system to toggle that specific feature/pass. An example would be if we try to render a bloom post-processing effect. Usually, one might have to do the following: check if the bloom effect is enabled, and if it does, render it, which is generally slow because the condition is evaluated in each render-loop. On the other hand, in our project, we enqueue a render command to draw the required bloom post-processing effect only when that is enabled by the user. In that way, we enqueue a full pass/sub-pass to be executed in the current render loop, so that we do not have to evaluate the condition for each frame.

As expected, the rendering process became very simple, since the main loop contains only the execution of the render graph's nodes.

Shader Permutations & Hot Reloading
We created a system that allows us to change shaders in real-time, without the need of restarting the engine every time something changes. This dramatically improves the user experience, allowing the users to see the graphics changes in real-time.

Moreover, features from such shaders can be changed at runtime via our shader permutation mechanism. We insert specific keywords into the shaders that require multiple features, which can be enabled in real-time from our engine.


Real-time Rendering Techniques
Our team has dedicated their efforts to ensuring that the game engine runs at a highly-interactive speed, such that the user's possibilities are not restricted.

We have implemented multiple real-time rendering techniques such as: shadow mapping, post-processing effects, illuminance model, as well as different rendering modes.

In terms of shadow mapping and lighting techniques, we have added real-time directional lights, spot lights, and point lights. The world around them is affected by their color, intensity and it is shadowed accordingly.

We have implemented a dynamic post-process stack consisting of the following effects: fog, sharpen, blur, tone mapping, chromatic aberration, pixelize, dilation, bloom. With performance in mind, we have used simple, yet fast, shaders that allow us to create such effects rapidly. For example, the bloom effect uses the dual filter technique introduced by Arm at SIGGRAPH 2015.

Our engine is capable of rendering both in forward and in deferred modes. The latter is sometimes faster when dealing with with multiple lights as the number of calculations per pixel is generally lower than in the forward rendering. The well-architected rendering system allows for a fast change between the rendering modes. This feature is accessible directly from editor.

Render Queues
We try to optimize our rendering methods and have the same quality in both forward and deferred modes. But one of the problems of deferred rendering is transparency. Hence, we decided to use multiple render queues: one for opaque objects and one for the transparent ones.

Here is the scene after the opaque pass:


Here is the scene after the transparent pass:


This allows us to enable blending of transparent materials.