⚠️ Beta State

PyBevy is in an early and experimental stage. The API is incomplete, subject to breaking changes without notice, and you should expect bugs. Many features are still under development.

Post-Processing Effects

Add bloom, tonemapping, and color grading to your scenes.

Introduction

Post-processing effects are applied to the camera's rendered image to enhance visual quality. pybevy supports several effects as components that you attach to your camera entity:

  • Bloom: Makes bright surfaces glow.
  • Tonemapping: Converts HDR to displayable colors.
  • ColorGrading: Adjusts exposure, saturation, contrast, and more.
from pybevy.prelude import *
from pybevy.camera import Bloom, Tonemapping, ColorGrading, ColorGradingGlobal, ColorGradingSection

Setting Up a Scene with Emissive Materials

Bloom works best with emissive materials — surfaces that emit light. We'll create a dark scene with glowing objects.

def setup(
    commands: Commands,
    meshes: ResMut[Assets[Mesh]],
    materials: ResMut[Assets[StandardMaterial]],
):
    # Camera with post-processing effects
    commands.spawn(
        Camera3d(),
        Transform.from_xyz(0.0, 3.0, 10.0).looking_at(Vec3.ZERO, Vec3.Y),
        # Bloom makes bright things glow
        Bloom(intensity=0.3),
        # Tonemapping converts HDR colors to displayable range
        Tonemapping.TONY_MC_MAPFACE,
        # Color grading for cinematic look
        ColorGrading(
            global_=ColorGradingGlobal(
                exposure=0.5,
                post_saturation=1.2,
            ),
            shadows=ColorGradingSection(
                saturation=0.8,
            ),
        ),
    )
 
    # Dark ground
    commands.spawn(
        Mesh3d(meshes.add(Plane3d(Vec3.Y, Vec2(20.0, 20.0)))),
        MeshMaterial3d(materials.add(StandardMaterial(
            base_color=Color.srgb(0.05, 0.05, 0.05),
        ))),
    )
 
    # Glowing spheres with emissive materials
    colors = [
        Color.srgb(1.0, 0.2, 0.2),  # Red
        Color.srgb(0.2, 1.0, 0.2),  # Green
        Color.srgb(0.2, 0.2, 1.0),  # Blue
        Color.srgb(1.0, 0.8, 0.0),  # Yellow
        Color.srgb(1.0, 0.2, 1.0),  # Magenta
    ]
 
    sphere = meshes.add(Sphere(0.5))
    for i, color in enumerate(colors):
        x = (i - 2) * 2.5
        commands.spawn(
            Mesh3d(sphere),
            MeshMaterial3d(materials.add(StandardMaterial(
                base_color=color,
                # Emissive makes the surface glow — the key to visible bloom
                emissive=color.to_linear() * 5.0,
            ))),
            Transform.from_xyz(x, 0.5, 0.0),
        )
 
    # A dim light so non-emissive surfaces are still slightly visible
    commands.spawn(
        PointLight(intensity=2000.0, range=30.0),
        Transform.from_xyz(0.0, 5.0, 5.0),
    )

Adjusting Effects at Runtime

You can query and modify post-processing components like any other component.

def adjust_bloom(
    keyboard: Res[ButtonInput],
    query: Query[Mut[Bloom], With[Camera3d]],
):
    for bloom in query:
        if keyboard.just_pressed(KeyCode.ArrowUp):
            bloom.intensity = min(bloom.intensity + 0.05, 1.0)
            print(f"Bloom intensity: {bloom.intensity:.2f}")
        if keyboard.just_pressed(KeyCode.ArrowDown):
            bloom.intensity = max(bloom.intensity - 0.05, 0.0)
            print(f"Bloom intensity: {bloom.intensity:.2f}")

Tonemapping Algorithms

Different tonemapping algorithms produce different visual styles:

  • TONY_MC_MAPFACE: Good default, preserves hue.
  • ACES_FITTED: Cinematic, used in film industry.
  • AGX: Balanced, good for games.
  • REINHARD: Classic algorithm, can wash out colors.
  • NONE: No tonemapping (raw HDR values clamped).

Running the App

@entrypoint
def main(app: App) -> App:
    return (
        app
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, adjust_bloom)
    )
 
if __name__ == "__main__":
    main().run()