Editor/SuperShotPostFx.cs

Editor code that defines PostFxSettings (a set of postprocessing options) and a helper SuperShotPostFx that applies those settings to a camera GameObject by creating corresponding Component instances and returns an IDisposable scope that destroys them when disposed.

Native Interop
using System;
using System.Collections.Generic;
using Sandbox;

namespace Editor.SuperShot;

public sealed class PostFxSettings
{
	[Property, Group( "Bloom" ), Title( "Bloom" )]
	public bool BloomEnabled { get; set; } = false;

	[Property, Group( "Bloom" ), Title( "Strength" ), Range( 0f, 3f ), ShowIf( nameof( BloomEnabled ), true )]
	public float BloomStrength { get; set; } = 0.6f;

	[Property, Group( "Bloom" ), Title( "Threshold" ), Range( 0f, 5f ), ShowIf( nameof( BloomEnabled ), true )]
	public float BloomThreshold { get; set; } = 1.2f;

	[Property, Group( "Tonemapping" ), Title( "Tonemapping" )]
	public bool TonemapEnabled { get; set; } = false;

	[Property, Group( "Tonemapping" ), Title( "Exposure" ), Range( -3f, 3f ), ShowIf( nameof( TonemapEnabled ), true )]
	public float Exposure { get; set; } = 0f;

	[Property, Group( "Color Adjustments" ), Title( "Color Adjustments" )]
	public bool ColorEnabled { get; set; } = false;

	[Property, Group( "Color Adjustments" ), Title( "Brightness" ), Range( 0f, 2f ), ShowIf( nameof( ColorEnabled ), true )]
	public float ColorBrightness { get; set; } = 1f;

	[Property, Group( "Color Adjustments" ), Title( "Contrast" ), Range( 0f, 2f ), ShowIf( nameof( ColorEnabled ), true )]
	public float ColorContrast { get; set; } = 1f;

	[Property, Group( "Color Adjustments" ), Title( "Saturation" ), Range( 0f, 2f ), ShowIf( nameof( ColorEnabled ), true )]
	public float ColorSaturation { get; set; } = 1f;

	[Property, Group( "Color Adjustments" ), Title( "Hue Rotate" ), Range( 0f, 360f ), ShowIf( nameof( ColorEnabled ), true )]
	public float ColorHueRotate { get; set; } = 0f;

	[Property, Group( "Depth of Field" ), Title( "Depth of Field" )]
	public bool DofEnabled { get; set; } = false;

	[Property, Group( "Depth of Field" ), Title( "Focal Distance" ), Range( 0f, 2000f ), ShowIf( nameof( DofEnabled ), true )]
	public float DofFocalDistance { get; set; } = 200f;

	[Property, Group( "Depth of Field" ), Title( "Focus Range" ), Range( 0f, 2000f ), ShowIf( nameof( DofEnabled ), true )]
	public float DofFocusRange { get; set; } = 500f;

	[Property, Group( "Depth of Field" ), Title( "Blur Size" ), Range( 0f, 100f ), ShowIf( nameof( DofEnabled ), true )]
	public float DofBlurSize { get; set; } = 30f;

	[Property, Group( "Depth of Field" ), Title( "Blur Foreground" ), ShowIf( nameof( DofEnabled ), true )]
	public bool DofFrontBlur { get; set; } = false;

	[Property, Group( "Sharpen" ), Title( "Sharpen" )]
	public bool SharpenEnabled { get; set; } = false;

	[Property, Group( "Sharpen" ), Title( "Scale" ), Range( 0f, 2f ), ShowIf( nameof( SharpenEnabled ), true )]
	public float SharpenScale { get; set; } = 0.5f;

	[Property, Group( "Chromatic Aberration" ), Title( "Chromatic Aberration" )]
	public bool ChromaEnabled { get; set; } = false;

	[Property, Group( "Chromatic Aberration" ), Title( "Scale" ), Range( 0f, 1f ), ShowIf( nameof( ChromaEnabled ), true )]
	public float ChromaScale { get; set; } = 0.3f;

	[Property, Group( "Vignette (engine)" ), Title( "Vignette" )]
	public bool VignetteEnabled { get; set; } = false;

	[Property, Group( "Vignette (engine)" ), Title( "Intensity" ), Range( 0f, 1f ), ShowIf( nameof( VignetteEnabled ), true )]
	public float VignetteIntensity { get; set; } = 0.6f;

	[Property, Group( "Vignette (engine)" ), Title( "Roundness" ), Range( 0f, 1f ), ShowIf( nameof( VignetteEnabled ), true )]
	public float VignetteRoundness { get; set; } = 0.5f;

	[Property, Group( "Vignette (engine)" ), Title( "Smoothness" ), Range( 0f, 1f ), ShowIf( nameof( VignetteEnabled ), true )]
	public float VignetteSmoothness { get; set; } = 1f;

	[Property, Group( "Vignette (engine)" ), Title( "Color" ), ShowIf( nameof( VignetteEnabled ), true )]
	public Color VignetteColor { get; set; } = Color.Black;

	[Property, Group( "Film Grain (engine)" ), Title( "Film Grain" )]
	public bool GrainEnabled { get; set; } = false;

	[Property, Group( "Film Grain (engine)" ), Title( "Intensity" ), Range( 0f, 1f ), ShowIf( nameof( GrainEnabled ), true )]
	public float GrainIntensity { get; set; } = 0.5f;

	[Property, Group( "Film Grain (engine)" ), Title( "Response" ), Range( 0f, 1f ), ShowIf( nameof( GrainEnabled ), true )]
	public float GrainResponse { get; set; } = 0.5f;

	[Property, Group( "Pixelate" ), Title( "Pixelate" )]
	public bool PixelateEnabled { get; set; } = false;

	[Property, Group( "Pixelate" ), Title( "Scale" ), Range( 0f, 1f ), ShowIf( nameof( PixelateEnabled ), true )]
	public float PixelateScale { get; set; } = 0.3f;

	[Property, Group( "Ambient Occlusion" ), Title( "Ambient Occlusion" )]
	public bool AoEnabled { get; set; } = false;

	[Property, Group( "Ambient Occlusion" ), Title( "Intensity" ), Range( 0f, 1f ), ShowIf( nameof( AoEnabled ), true )]
	public float AoIntensity { get; set; } = 1f;

	[Property, Group( "Ambient Occlusion" ), Title( "Radius" ), Range( 1, 512 ), ShowIf( nameof( AoEnabled ), true )]
	public int AoRadius { get; set; } = 128;

	public bool Any =>
		BloomEnabled || TonemapEnabled || ColorEnabled || DofEnabled || SharpenEnabled ||
		ChromaEnabled || VignetteEnabled || GrainEnabled || PixelateEnabled || AoEnabled;
}

public static class SuperShotPostFx
{
	public static IDisposable Apply( GameObject cameraGo, PostFxSettings fx )
	{
		var created = new List<Component>();
		if ( cameraGo is null || fx is null || !fx.Any )
			return new FxScope( created );

		if ( fx.BloomEnabled )
		{
			var c = cameraGo.Components.Create<Bloom>();
			c.Strength = fx.BloomStrength;
			c.Threshold = fx.BloomThreshold;
			created.Add( c );
		}

		if ( fx.TonemapEnabled )
		{
			var c = cameraGo.Components.Create<Tonemapping>();
			c.ExposureCompensation = fx.Exposure;
			created.Add( c );
		}

		if ( fx.ColorEnabled )
		{
			var c = cameraGo.Components.Create<ColorAdjustments>();
			c.Brightness = fx.ColorBrightness;
			c.Contrast = fx.ColorContrast;
			c.Saturation = fx.ColorSaturation;
			c.HueRotate = fx.ColorHueRotate;
			created.Add( c );
		}

		if ( fx.DofEnabled )
		{
			var c = cameraGo.Components.Create<DepthOfField>();
			c.FocalDistance = fx.DofFocalDistance;
			c.FocusRange = fx.DofFocusRange;
			c.BlurSize = fx.DofBlurSize;
			c.FrontBlur = fx.DofFrontBlur;
			c.BackBlur = true;
			created.Add( c );
		}

		if ( fx.SharpenEnabled )
		{
			var c = cameraGo.Components.Create<Sharpen>();
			c.Scale = fx.SharpenScale;
			created.Add( c );
		}

		if ( fx.ChromaEnabled )
		{
			var c = cameraGo.Components.Create<ChromaticAberration>();
			c.Scale = fx.ChromaScale;
			created.Add( c );
		}

		if ( fx.VignetteEnabled )
		{
			var c = cameraGo.Components.Create<Vignette>();
			c.Intensity = fx.VignetteIntensity;
			c.Roundness = fx.VignetteRoundness;
			c.Smoothness = fx.VignetteSmoothness;
			c.Color = fx.VignetteColor;
			created.Add( c );
		}

		if ( fx.GrainEnabled )
		{
			var c = cameraGo.Components.Create<FilmGrain>();
			c.Intensity = fx.GrainIntensity;
			c.Response = fx.GrainResponse;
			created.Add( c );
		}

		if ( fx.PixelateEnabled )
		{
			var c = cameraGo.Components.Create<Pixelate>();
			c.Scale = fx.PixelateScale;
			created.Add( c );
		}

		if ( fx.AoEnabled )
		{
			var c = cameraGo.Components.Create<AmbientOcclusion>();
			c.Intensity = fx.AoIntensity;
			c.Radius = fx.AoRadius;
			created.Add( c );
		}

		return new FxScope( created );
	}

	sealed class FxScope : IDisposable
	{
		readonly List<Component> _created;
		public FxScope( List<Component> created ) => _created = created;

		public void Dispose()
		{
			foreach ( var c in _created )
			{
				if ( c.IsValid() )
					c.Destroy();
			}
			_created.Clear();
		}
	}
}