# How to Configure and Use Deferred Rendering with PBR in EquilibriumEngine

> Configure and use deferred rendering with PBR in EquilibriumEngine. Attach the DeferredRenderer component for automatic G-Buffer creation, geometry and light passes, and PBR compositing.

- Repository: [Alexander/equilibriumengine](https://github.com/clibequilibrium/equilibriumengine)
- Tags: how-to-guide
- Published: 2026-02-27

---

**To enable deferred rendering with PBR in EquilibriumEngine, attach the `DeferredRenderer` component to the main engine entity; the system automatically creates a G-Buffer, runs geometry and light passes, and composites transparent objects using physically-based shading.**

EquilibriumEngine supports two rendering paths: a forward renderer (the default) and a deferred renderer that integrates tightly with the physically-based rendering (PBR) system. This guide explains how to activate the deferred pipeline, configure PBR materials, and understand the underlying architecture implemented in the `clibequilibrium/equilibriumengine` repository.

## Architecture Overview

The deferred rendering architecture consists of three primary layers that work together to separate geometry processing from lighting calculations.

**Core Components and Systems**

- **`DeferredRenderer` component** ([`equilibrium/components/renderer/renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/renderer/renderer_components.h)) – Stores G-Buffer textures, shader programs, and light-pass resources.
- **[`deferred_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/deferred_renderer_system.c)** ([`equilibrium/systems/rendering/deferred_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/deferred_renderer_system.c)) – Creates the G-Buffer, manages view IDs, and orchestrates the geometry, light, and transparent passes.
- **[`pbr_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/pbr_system.c)** ([`equilibrium/systems/rendering/pbr_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/pbr_system.c)) – Prepares per-material uniforms, binds PBR textures, and generates the pre-computed multiple-scattering albedo LUT.

**Data Flow**

1. **Initialization** – `InitializeDeferredRenderer` verifies GPU support via `renderer_supported(true)` and aborts if the hardware cannot handle the deferred path.
2. **G-Buffer Creation** – The system creates a G-Buffer with five attachments defined in `GBufferAttachment`: diffuse-roughness, normal, F0-metallic, emissive-occlusion, and depth.
3. **PBR Setup** – The `PBRShader` component holds uniform handles for material properties and manages the albedo LUT (`generate_albedo_lut`), bound as `PBR_ALBEDO_LUT`.
4. **Frame Execution** – `DeferredRendererBeginFrame` clears buffers and sets view-projections. `DrawOpaqueMeshes` submits geometry using the deferred geometry program. The depth texture is blitted to `light_depth_texture` before `DrawPointLights` processes illumination. Finally, `DrawTransparentMeshes` runs a forward pass for blending.

## Enabling Deferred Rendering

The engine selects the rendering path based on the presence of a `DeferredRenderer` component on the main engine entity. To switch from forward to deferred rendering, add the component immediately after initializing BGFX:

```c
/* Attach the DeferredRenderer component to the engine entity */
entity_t engine_entity = {world->entities[0], world};
DeferredRenderer *deferred = entity_get_or_add_component(engine_entity, DeferredRenderer);
(void)deferred; // the system will initialise it on the next frame

```

If you prefer forward rendering, simply omit this component. The system uses flecs observers, so `InitializeDeferredRenderer` runs automatically when the `Bgfx` component is set.

## Configuring PBR Materials

Materials are defined in [`renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/renderer_components.h) via the `Material` struct. The deferred path requires valid texture handles or relies on automatic fallback to a 1×1 white default texture.

```c
Material mat = {
    .base_color_factor   = {1.0f, 1.0f, 1.0f, 1.0f},
    .metallic_factor     = 0.0f,
    .roughness_factor    = 0.5f,
    .normal_scale        = 1.0f,
    .occlusion_strength  = 1.0f,
    .blend               = false,           // opaque
    .double_sided        = false,
    .base_color_texture  = my_albedo_texture,
    .metallic_roughness_texture = my_mr_texture,
    .normal_texture      = my_normal_texture,
    .occlusion_texture   = my_ao_texture,
    .emissive_texture    = my_emissive_texture,
};
ecs_set(world, mesh_entity, Material, {mat});

```

The `bind_material` function in [`pbr_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/pbr_system.c) handles the binding logic:

- Uploads factor vectors as uniforms (`u_baseColorFactor`, `u_metallicRoughnessNormalOcclusionFactor`, `u_emissiveFactorVec`)
- Sets texture samplers (`s_texBaseColor`, `s_texMetallicRoughness`, `s_texNormal`, `s_texOcclusion`, `s_texEmissive`)
- Packs a bit-mask into `u_hasTextures` so shaders skip sampling missing textures

## Rendering Pipeline Execution

The main loop in `launcher/launcher.cc` calls `ecs_progress(world, dt)`. The deferred system registers four flecs callbacks that execute in sequence:

1. **`OnBeginRender`** → `DeferredRendererBeginFrame` – Sets up views and clears geometry/light buffers.
2. **`OnBeginRender`** → `DrawOpaqueMeshes` – Geometry pass using `deferred_renderer->geometry_program`.
3. **`OnRender`** → `DrawPointLights` – Light pass using the fullscreen lighting program and point-light bounding boxes via `deferred_renderer->light_index_vec_uniform`.
4. **`OnRender`** → `DrawTransparentMeshes` – Forward transparency pass into the accumulation buffer.

No additional user code is required beyond adding the `DeferredRenderer` component.

## Practical Implementation Example

Below is a minimal, reproducible snippet that starts an engine instance with deferred PBR rendering enabled:

```c
#include "equilibrium/equilibrium.h"
#include "components/renderer/renderer_components.h"

int main(void) {
    world_t *world = ecs_init_w_args(0, NULL);
    ecs_set_name(world, "EquilibriumEngine");

    /* Load core systems (SDL, BGFX, etc.) */
    ecs_import(world, "Equilibrium");

    /* Create the root entity that represents the application */
    entity_t app = {ecs_new_id(world), world};

    /* Attach window & BGFX components – normally done by the Launcher */
    ecs_set(world, app.entity, AppWindow, {800, 600, "Deferred PBR Demo"});
    ecs_set(world, app.entity, Bgfx, {});

    /* *** Enable deferred rendering *** */
    entity_get_or_add_component(app, DeferredRenderer);   // <-- key line

    /* Load a glTF scene (Sponza) – utils will fill Mesh, Material, Transform, etc. */
    load_gltf_scene(world, "launcher/sandbox/models/Sponza/glTF/Sponza.bin");

    /* Main loop */
    while (app_is_running(world)) {
        ecs_progress(world, 0.016f);   // 60 Hz tick
    }

    ecs_fini(world);
    return 0;
}

```

The `load_gltf_scene` utility in [`utils/cgltf_utils.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/utils/cgltf_utils.c) creates entities with `Mesh`, `Material`, and `Transform` components, automatically populating the PBR material fields required by the deferred pipeline.

## Advanced Configuration

### Tuning the G-Buffer Layout

The G-Buffer layout is defined by `GBufferAttachment` in [`renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/renderer_components.h). The format array `gBufferAttachmentFormats` maps each attachment to a BGFX texture format. To increase precision (e.g., HDR normals), modify the array and update the corresponding sampler names in `InitializeDeferredRenderer`. Remember to synchronize changes with `fs_deferred_geometry.sc` in `launcher/sandbox/shaders/shading/`.

### Adding Custom Light Types

The current implementation supports point lights (`LIGHTS_POINTLIGHTS`). To extend it:

1. Add a new uniform block in the `LightShader` struct (e.g., `spot_light_program`).
2. Create a new program with appropriate vertex/fragment shaders.
3. Add a system similar to `DrawPointLights` that submits geometry for the new light type.
4. Register the system in `DeferredRendererSystemImport` following the point light pattern.

## Debugging Common Issues

| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| Black screen, no geometry | G-Buffer not created (GPU unsupported) | Verify `renderer_supported(true)` returns true; check BGFX renderer type. |
| Missing textures on materials | `set_texture_or_default` fell back to default white texture | Ensure texture handles are valid (`bgfx_is_texture_valid`). |
| Light contributions missing | `light_depth_texture` not blitted before light pass | Confirm `bgfx_blit` call (lines 286-288 in [`deferred_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/deferred_renderer_system.c)) executes without error. |
| Transparent objects appear over everything | Transparent pass bound to wrong view | Ensure `DrawTransparentMeshes` uses `vTransparent` and the same accumulation buffer. |

All debug output routes through `ecs_trace`/`ecs_err`, configured in [`bgfx_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/bgfx_system.c).

## Summary

- **Enable deferred rendering** by adding the `DeferredRenderer` component to the main engine entity after BGFX initialization.
- **PBR materials** use the `Material` struct; the `bind_material` function in [`pbr_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/pbr_system.c) automatically handles texture samplers and uniform uploads.
- **Pipeline stages** include G-Buffer creation, opaque geometry pass, depth blit, light accumulation, and forward transparency pass.
- **Runtime toggling** is possible by adding or removing the `DeferredRenderer` component dynamically.
- **Customization** requires editing `GBufferAttachment` formats and corresponding shader code in the sandbox shaders directory.

## Frequently Asked Questions

### How do I switch between forward and deferred rendering at runtime?

Remove or add the `DeferredRenderer` component on the engine entity. When removed, the system automatically falls back to the forward renderer. Use `entity_get_or_add_component` to enable deferred mode and `ecs_remove` to disable it.

### Can I override the default albedo LUT for artistic control?

Yes. After the `PBRShader` component is created, replace the `albedo_lut_texture` handle with a custom texture created via `create_texture_2d`, then call `bind_albedo_lut(shader, false)` to bind it for subsequent render passes.

### What texture formats does the G-Buffer use by default?

The default formats defined in `gBufferAttachmentFormats` are `BGRA8` for diffuse-roughness, F0-metallic, and emissive-occlusion; `RG16F` for normals; and a hardware-specific depth format. These are declared in [`renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/renderer_components.h) and consumed by the geometry shader (`fs_deferred_geometry.sc`).

### Why are my transparent objects rendering incorrectly?

The transparent pass must use the same accumulation buffer and the `vTransparent` view ID. Verify that `DrawTransparentMeshes` executes after the light pass and that depth testing is configured correctly for forward rendering within the deferred pipeline.