Skip to content

Render graph

Romain Milbert edited this page Mar 4, 2024 · 15 revisions

A render graph organizes render passes. This allows to perform deferred rendering, applying post effects over the scene.

A RenderSystem always has its own RenderGraph, which must be used directly.

#include <RaZ/Render/RenderGraph.hpp>

Raz::RenderGraph& renderGraph = renderSystem.getRenderGraph();

Textures must be created through Raz::Texture2D::create(). They will be shared among all passes that use them.

// A depth buffer always has a floating-point data type, no need to specify it
const auto depthBuffer = Raz::Texture2D::create(window.getWidth(),
                                                window.getHeight(),
                                                Raz::ImageColorspace::DEPTH);

const auto colorBuffer = Raz::Texture2D::create(window.getWidth(),
                                                window.getHeight(),
                                                Raz::ImageColorspace::RGB,
                                                Raz::ImageDataType::FLOAT);

const auto normalBuffer = Raz::Texture2D::create(window.getWidth(),
                                                 window.getHeight(),
                                                 Raz::ImageColorspace::RGB,
                                                 Raz::ImageDataType::BYTE);

Next, we need to get the geometry pass from the render graph, and tell it we will write to the three textures.

⚠️ Important note: write color textures must be given the index the shaders write them to. For example, in the default shaders, the fragment color is bound to the index 0 while the normal is at index 1, which are the indices you need to give them.

Raz::RenderPass& geometryPass = renderGraph.getGeometryPass();

geometryPass.setWriteDepthTexture(depthBuffer);
geometryPass.addWriteColorTexture(colorBuffer, 0);
geometryPass.addWriteColorTexture(normalBuffer, 1);

We then add a following render pass, which will run the given shader. This pass will read from the three previously declared textures; the given strings are the shader's uniforms' names.

Raz::RenderPass& nextPass = renderGraph.addNode(Raz::FragmentShader("ssao.frag"));

nextPass.addReadTexture(depthBuffer, "uniSceneBuffers.depth");
nextPass.addReadTexture(colorBuffer, "uniSceneBuffers.color");
nextPass.addReadTexture(normalBuffer, "uniSceneBuffers.normal");

We also associate a write buffer to our pass, so that it outputs a buffer we can reuse afterwards.

const auto resultBuffer = Raz::Texture2D::create(window.getWidth(),
                                                 window.getHeight(),
                                                 Raz::ImageColorspace::RGBA,
                                                 Raz::ImageDataType::FLOAT);
nextPass.addWriteTexture(resultBuffer);

Passes are dependent on each other: the above will be executed right after the geometry one. We then set up the dependency accordingly:

geometryPass.addChildren(nextPass);
// Strictly equivalent to:
nextPass.addParents(geometryPass);

A final pass is created, which will apply a blur on the scene; it will read from the previously written texture.

Raz::RenderPass& blurPass = renderGraph.addNode(Raz::FragmentShader("blur.frag"));
blurPass.addReadTexture(resultBuffer, "uniBuffer");

Each pass contains its own ShaderProgram, which can be used to send uniforms. We here set the blur kernel to 2:

blurPass.getProgram().sendUniform("uniKernelSize", 2);

Finally, the blur pass will follow the previous one:

nextPass.addChildren(blurPass);
// Strictly equivalent to:
blurPass.addParents(nextPass);

The setup is now complete, this last pass' output will be displayed!

Clone this wiki locally