You can create your own post-processing components by using both C# and shaders.

Your post-processing component needs to be added to the same GameObject as the Camera and the other built-in effects.

C# Component

The most basic C# component for any post processing effect uses Command Lists. You can have the Command List hook into any available render stage.

In this example you can draw a fullscreen quad using your shader with CommandList.Blit and Material.FromShader.

You're not limited to only drawing a fullscreen quad either, and can use anything in the CommandList class.

namespace Sandbox;

[Title( "My Post Processing" )]
[Category( "Post Processing" )]
[Icon( "grain" )]
public sealed class MyPostProcessing : PostProcess, Component.ExecuteInEditor
{
	[Property] public Color Color { get; set; };

	protected override Stage RenderStage => Stage.AfterTransparent;
	protected override int RenderOrder => 100;
 
 	protected override void UpdateCommandList()
	{
		// Pass the Color property to the shader
		CommandList.Attributes.Set( "mycolor", Color );

		// Grab the FrameBuffer to later pass to the shader
		CommandList.GrabFrameTexture( "ColorBuffer" );

		// Blit a quad across the entire screen with our custom shader
		CommandList.Blit( Material.FromShader( "shaders/mypostprocess.shader" ));
	}
}

Shader

Here's a very basic shader that will output the passed color from our C# attribute. The framebuffer is also passed here which can be sampled. More on shaders.

MODES
{
    VrForward();
}

FEATURES
{
}

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

struct PixelInput
{
    float2 vTexCoord : TEXCOORD0;

	#if ( PROGRAM == VFX_PROGRAM_VS )
		float4 vPositionPs		: SV_Position;
	#endif

	#if ( ( PROGRAM == VFX_PROGRAM_PS ) )
		float4 vPositionSs		: SV_Position;
	#endif
};

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

PS
{
    RenderState( DepthWriteEnable, false );
    RenderState( DepthEnable, false );

    // Passed framebuffer if you want to sample it
    Texture2D g_tColorBuffer < Attribute( "ColorBuffer" ); SrgbRead( true ); >;
    float3 vMyColor < Attribute("mycolor"); >;

    float4 MainPs( PixelInput i ) : SV_Target0
    {
        return float4( vMyColor, 1 );
    }
}





Created 8 May 2024
Updated 9 Jun 2025