Home » Blogs » Foliage in AMD FidelityFX™ Brixelizer GI » Part 1: Foliage in AMD FidelityFX Brixelizer GI

Part 1: Foliage in AMD FidelityFX Brixelizer GI

This article will describe Brixelizer GI’s functionality to better highlight the problem with foliage. It will also provide a naive, low-quality solution to the problem and compare the result to the default Brixelizer GI.

AMD FidelityFX Brixelizer

To understand why foliage is a problem in Brixelizer GI, we must first understand Brixelizer itself. Brixelizer is a sparse distance field builder and part of the AMD FidelityFX SDK. Brixelizer GI leverages this backend to compute global illumination.

The Brixelizer API is relatively simple. The programmer feeds geometry to the library, which produces a distance field acceleration structure that can be traced using the provided shader trace operations.

Under the hood, the acceleration structure is sparse. This sparse distance field distributes local distance fields in a sparse grid. In Brixelizer, we refer to these local distance fields as Bricks. To trace this structure, Brixelizer first traverses an AABB tree of the scene geometry, and when the ray enters a leaf node, it switches to ray marching incident bricks.

The sparse distance field

Distance fields provide an effective way of tracing scenes. However, they are implicit in nature in contrast to triangle-mesh parametric surfaces. This difference makes it difficult to reuse the material bindings of the original triangle surfaces, which poses a significant problem for any GI that uses Brixelizer.

Radiance cache

Brixelizer does not provide an API for accessing a surface’s material properties. This means that Brixelizer GI needs to provide its own solution to sample the outgoing radiance at surface points. The chosen solution implements a secondary cache structure called the radiance cache.

The radiance cache stores the outgoing radiance of surfaces in a sparse grid. This grid leverages the Brick ID provided by Brixelizer to store radiance only on surfaces. Whenever a Brixelizer trace hits a surface, the Brick ID of said surface can then be referenced in the radiance cache to access the outgoing radiance.

Of course, it’s not enough to access the cache structure; we must also fill it out. To do this, Brixelizer GI uses a sort of screen-space voxelization. The developer passes the previously lit frame to Brixelizer GI, this frame is successively split into 4×4 tiles. The algorithm chooses a random fragment from each of these 4×4 tiles in every frame. World-space coordinates of these fragments are then reconstructed using the depth buffer and the view-screen projection. Finally, the color value of each fragment is written to the radiance cache.

A fragment from a 4x4 tile is inserted into the radiance cache

Probes

Brixelizer GI uses screen-space probes to sample the radiance cache. The screen probes are spawned on the world-space coordinates of screen-space fragments. The probes trace the distance field and access the radiance cache to approximate the incoming radiance. The details here are not especially relevant to this blog post. To find out more, refer to the Brixelizer GI GDC presentation and AMD GI-1.0.

Before we move on to the problem of foliage, let’s examine what the probes see in a scene like Sponza.

What the screens-space probes see in Sponza

In the left image, we can see the distance field constructed by Brixelizer. On the right, we see the same distance field, but with the radiance cache consulted at each hit point. This is, in essence, what the probes will see.

The astute reader might notice that some radiance bleeds onto the pillars from the curtains. Before storing fragments in the cache, we apply a slight jitter to the world-space coordinates. This jitter is necessary because the distance field is generally not precise, meaning the surfaces will not coincide with the original triangle meshes.

The distance field is not precise

Foliage

Now we have a grasp of how Brixelizer GI traces the scene. To illustrate the problem with foliage, let us now look at how Brixelizer GI performs in a scene full of alpha-tested geometry.

A foliage-heavy scene tested with Brixelizer GI

In this scene, the grass is over-darkening. To understand why this is happening, let’s look at the scene’s distance field and radiance cache.

The distance field and the radiance cache of the forest scene

As observed, Brixelizer builds the distance field on the entire geometry of the alpha-clipped cards, resulting in an inaccurate distance field representation. The screen-space probes placed on the grass will trace this distance field and erroneously sample the radiance at the transparent parts of the geometry; these parts never have any associated fragments, resulting in the cache stored there being pure darkness. The image below demonstrates a screen-space probe placed on a strand of grass that is unable to escape the enclosing distance field.

A screen-space probe that is unable to sample the surrounding environment due to an erroneous distance field representation

Naive solution

A naive solution to the over-darkening issue is avoiding passing alpha-tested geometry into Brixelizer. This solution removes erroneously built cards from the distance field. Screen probes placed on the grass can now trace through any alpha-tested geometry, thus allowing them to sample the sky and opaque parts of the scene.

A naive solution to foliage

Unfortunately, this also means that the leaves will no longer be able to provide indirect shadowing for the grass. In this situation, we will instead have a case of over-brightening. Additionally, since no bricks are created around the grass, we experience problems with other cache structures in Brixelizer GI, causing flickering and artifacts.

In Part 2 of this blog series, we will modify the Brixelizer GI algorithm to try to provide a solution that fixes the over-darkening and brightening while still avoiding artifacts.

Petter Blomkvist
Petter Blomkvist

Petter is a Master's student who has completed a year-long internship at AMD's European Game Engineering Team. Throughout this time, he conducted extensive research on Software-Based GI and assisted in the shipment of Brixelizer™ and Brixelizer GI™.

Introducing AMD FidelityFX™ Brixelizer

As of FidelityFX SDK version 1.1, Brixelizer and Brixelizer GI are now unleashed to world so in this article we aim to discuss a few practical use cases and provide you with some tips you can apply for getting the most performance out of Brixelizer in your application.

Looking for a good place to get started with exploring GPUOpen?

AMD GPUOpen documentation

Explore our huge collection of detailed tutorials, sample code, presentations, and documentation to find answers to your graphics development questions.

AMD GPUOpen Effects - AMD FidelityFX technologies

Create wonder. No black boxes. Meet the AMD FidelityFX SDK!

AMD GPUOpen Performance Guides

The home of great performance and optimization advice for AMD RDNA™ 2 GPUs, AMD Ryzen™ CPUs, and so much more.

AMD GPUOpen Samples

Browse all our useful samples. Perfect for when you’re needing to get started, want to integrate one of our libraries, and much more.

AMD GPUOpen developer SDKs

Discover what our SDK technologies can offer you. Query hardware or software, manage memory, create rendering applications or machine learning, and much more!

AMD GPUOpen Developer Tools

Analyze, Optimize, Profile, Benchmark. We provide you with the developer tools you need to make sure your game is the best it can be!

Getting started: AMD GPUOpen software

New or fairly new to AMD’s tools, libraries, and effects? This is the best place to get started on GPUOpen!

AMD GPUOpen Getting Started Development and Performance

Looking for tips on getting started with developing and/or optimizing your game, whether on AMD hardware or generally? We’ve got you covered!