Skip to main content
Post-processing effects run after the scene is rendered and alter the final image before it is displayed. You can apply effects directly to a Camera component or to a PostProcessVolume that activates when the camera enters it.

Camera settings

The Camera component exposes two post-processing settings: EnablePostProcessing — disable this to skip all post-processing for the camera entirely. PostProcessAnchor — by default, PostProcessVolume triggers are evaluated using the camera’s world position. Assign a different GameObject here to use that object’s position instead. This is useful for top-down games where you want effects to respond to the player’s position rather than the camera’s.

PostProcessVolume

PostProcessVolume is a component that activates a set of post-processing effect components whenever the relevant anchor is inside it.
1

Add the component

Select a GameObject in the Hierarchy and add a PostProcessVolume component to it.
2

Add effect components

Add post-processing effect components (such as FilmGrain or Tonemapping) to the same GameObject or to a child GameObject.
3

Set the volume shape

Choose Box, Sphere, or Infinite as the volume shape.

Volume shapes

ShapeUse case
BoxLocalised region with controllable blend edges
SphereRadial region
InfiniteApplies everywhere; useful for effects you toggle via BlendWeight at runtime

Blending

Effects blend in based on how far inside the volume the anchor is, controlled by the BlendDistance property. Use BlendWeight on the component to fade an infinite volume in or out from code.

Editor preview

When a PostProcessVolume is selected, the editor shows a preview of its effects. Uncheck Editor Preview on the component to stop this.

Built-in effects

Tonemapping

Tonemapping remaps HDR colours to the display range. Add a Tonemapping component to your camera’s GameObject. Properties:
PropertyDescription
ModeThe tonemapping operator
Auto Exposure EnabledToggles automatic exposure adaptation
Minimum ExposureLower bound for auto exposure
Maximum ExposureUpper bound for auto exposure
RateHow quickly exposure adapts to brightness changes
Available modes:
Source 2’s default tonemapper. Preserves detail in dark areas well, but loses punchiness in brighter areas.
Filmic with very high contrast and punchy colours. A popular choice for cinematic looks.
Low contrast. Good for most environments.
Unbiased — only applies exposure. No colour remapping.
The same tonemapper used by Blender. Similar saturation and detail to ACES, but lifts darker areas slightly more. Works well for outdoor scenes.
You can run other post-processing effects before tonemapping (in HDR space) or after it (in LDR space) by using the Stage parameter when blitting in a custom effect.

Film grain

The FilmGrain component adds simulated film-style grain to the camera output. It is purely visual and does not affect gameplay or lighting.
PropertyDescription
IntensityOverall visibility of the grain. 0 = off, 1 = heavy.
ResponseHow much the grain reacts to brightness. Higher values reduce grain in darker areas.

Creating a custom post-processing effect

Derive from BasePostProcess<T> to create a component that can blend across multiple PostProcessVolume instances.
[Title( "MyBrightnessEffect" )]
[Category( "Post Processing" )]
public sealed class MyBrightnessEffect : BasePostProcess<MyBrightnessEffect>
{
    [Property, Range( -1, 1 )]
    public float Brightness { get; set; } = 0.0f;

    public override void Render()
    {
        float brightness = GetWeighted( x => x.Brightness );
        if ( brightness.AlmostEqual( 0.0f ) ) return;

        Attributes.Set( "brightness", 1 + brightness );

        var shader = Material.FromShader( "shaders/postprocess/brightness.shader" );
        var blit = BlitMode.WithBackbuffer( shader, Stage.AfterPostProcess, 200, false );
        Blit( blit, "Brightness" );
    }
}
GetWeighted — returns a value blended across all active volumes the camera is currently inside, weighted by how far in the camera is. Blit — creates a CommandList that executes at the specified render stage and order. Pass true as the fourth argument to WithBackbuffer if your shader needs the back buffer, which will be passed to the shader as ColorBuffer.

Corresponding shader

COMMON
{
    #include "postprocess/shared.hlsl"
}

struct VertexInput
{
    float3 pos : POSITION < Semantic( PosXyz ); >;
    float2 uv  : TEXCOORD0 < Semantic( LowPrecisionUv ); >;
};

struct PixelInput
{
    float2 uv  : TEXCOORD0;
    float4 pos : SV_Position;
};

VS
{
    PixelInput MainVs( VertexInput i )
    {
        PixelInput o;
        o.pos = float4(i.pos.xy, 0.0f, 1.0f);
        o.uv  = i.uv;
        return o;
    }
}

PS
{
    #include "postprocess/common.hlsl"
    #include "postprocess/functions.hlsl"
    #include "procedural.hlsl"

    Texture2D colorBuffer < Attribute( "ColorBuffer" ); SrgbRead( true ); >;
    float brightness < Attribute("brightness"); >;

    float4 MainPs( PixelInput i ) : SV_Target0
    {
        float2 uv    = CalculateViewportUv( i.uv.xy );
        float4 color = colorBuffer.SampleLevel( g_sBilinearMirror, uv, 0 );
        color.rgb *= brightness;
        return color;
    }
}