ScreenSpaceGodRays.cs
using Sandbox;
using System;

[Title( "Screen Space God Rays" )]
[Category( "Post Processing" )]
[Icon( "light_mode" )]
public sealed class ScreenSpaceGodRays : PostProcess, Component.ExecuteInEditor
{
	public enum Quality
	{
		/// <summary>
		/// 128 base samples (only for screenshots!)
		/// </summary>
		[Icon( "brightness_5" )]
		Ultra = 0,

		/// <summary>
		/// 32 base samples
		/// </summary>
		[Icon( "brightness_4" )]
		High = 1,

		/// <summary>
		/// 24 base samples
		/// </summary>
		[Icon( "brightness_3" )]
		Medium = 2,

		/// <summary>
		/// 16 base samples
		/// </summary>
		[Icon( "brightness_2" )]
		Low = 3,

		/// <summary>
		/// 8 base samples
		/// </summary>
		[Icon( "brightness_1" )]
		UltraLow
	}

	/// <summary>
	/// Determines how many samples we use when blurring.
	/// The number of samples is also affected by the <see cref="Density"/> value, to keep things looking smooth.
	/// </summary>
	[Property, Group( "General" )] public Quality QualityLevel { get; set; } = Quality.Medium;

	/// <summary>
	/// How much should we multiply the output by?
	/// </summary>
	[Property, Group( "General" ), Range( 0, 1 )] public float Intensity { get; set; } = 0.5f;

	/// <summary>
	/// How much exposure should we apply to the initial image?
	/// </summary>
	[Property, Group( "Filtering" ), Range( 0, 1 )] public float Exposure { get; set; } = 1f;

	/// <summary>
	/// What brightness cutoff should we use?
	/// </summary>
	[Property, Group( "Filtering" ), Range( 0, 4 )] public float Threshold { get; set; } = 1.6f;

	/// <summary>
	/// How smooth should the brightness cutoff be?
	/// </summary>
	[Property, Group( "Filtering" ), Range( 0, 4 )] public float Smoothness { get; set; } = 1.5f;

	/// <summary>
	/// How long should the rays be?
	/// </summary>
	[Property, Group( "Rays" ), Range( 0, 1 )] public float Density { get; set; } = 0.6f;

	/// <summary>
	/// How much energy should the rays preserve over distance?
	/// </summary>
	[Property, Group( "Rays" ), Range( 0, 1 )] public float Decay { get; set; } = 0.96f;

	/// <summary>
	/// How much weight should the rays have?
	/// </summary>
	[Property, Group( "Rays" ), Range( 0, 1 )] public float Weight { get; set; } = 1.0f;

	IDisposable renderHook;

	protected override void OnEnabled()
	{
		renderHook = Camera.AddHookBeforeOverlay( "Screen Space God Rays", 2000, RenderEffect );
	}

	protected override void OnDisabled()
	{
		renderHook?.Dispose();
		renderHook = null;
	}

	RenderAttributes attributes = new RenderAttributes();

	private int GetSampleCount()
	{
		var baseSampleCount = QualityLevel switch
		{
			Quality.Ultra => 128,
			Quality.High => 24,
			Quality.Medium => 16,
			Quality.Low => 8,
			Quality.UltraLow => 4,

			_ => 16
		};

		return baseSampleCount * Density.Remap( 0.0f, 1.0f, 1, 8 ).CeilToInt();
	}

	public void RenderEffect( SceneCamera camera )
	{
		if ( !camera.EnablePostProcessing )
			return;

		var sun = Scene.GetComponentInChildren<DirectionalLight>();

		if ( !sun.IsValid() )
			return;

		//
		// Find main light position on screen
		//
		var lightForward = -sun.WorldRotation.Forward;
		var farPosition = camera.Position + lightForward * camera.ZFar;

		camera.ToScreen( farPosition, out var screenPosition );

		var lightPosition = screenPosition / camera.Size;
		attributes.Set( "lightposition", lightPosition );

		//
		// Make it look semi-decent if the light is off-screen
		//
		var strength = 1.0f - lightPosition.GetBoundaryProximityFactor();
		strength = MathF.Pow( strength, 0.5f );
		attributes.Set( "lightintensity", Intensity * strength );

		if ( strength <= 0.0f )
			return;

		//
		// User props
		//
		attributes.Set( "threshold", Threshold );
		attributes.Set( "smoothness", Smoothness );
		attributes.Set( "exposure", Exposure );
		attributes.Set( "density", Density );
		attributes.Set( "weight", Weight );
		attributes.Set( "decay", Decay );

		//
		// Num samples
		//
		attributes.Set( "samplecount", GetSampleCount() );

		Graphics.GrabFrameTexture( "ColorBuffer", attributes );
		Graphics.Blit( Material.FromShader( "shaders/godrays.shader" ), attributes );
	}
}