OMRVisualFoundation.Bloom.cs
using Sandbox;
using System;
/// <summary>
/// Phase 8F3F3: restrained native HDR bloom for the gameplay world.
///
/// This deliberately uses Sandbox.Bloom instead of reproducing the engine's
/// multi-resolution bloom chain in an OMR shader. The native implementation
/// thresholds the HDR scene, builds a six-level half-resolution pyramid and
/// composites the accumulated glow before later post-processing.
///
/// Bloom stays on the authored gameplay/world camera. In OMR's two-camera
/// presentation stack that means the world receives bloom while the offscreen
/// first-person viewmodel remains clean. The later adaptive pixelation/edge
/// passes can then stylize the already-bloomed world instead of Bloom blurring
/// their pixel structure afterward.
/// </summary>
public sealed partial class OMRVisualFoundation
{
[Property, Group( "World Look/Bloom" ), Title( "Enable Bloom" )]
[Description( "Native HDR bloom for bright world lighting and emissive/high-intensity regions. The local viewmodel remains outside this pass." )]
public bool EnableBloom { get; set; } = false;
[Property, Group( "World Look/Bloom" ), Title( "Mode" )]
public SceneCamera.BloomAccessor.BloomMode BloomMode { get; set; } = SceneCamera.BloomAccessor.BloomMode.Additive;
[Property, Group( "World Look/Bloom" ), Range( 0f, 10f ), Title( "Strength" )]
[Description( "Overall bloom contribution. OMR starts deliberately low; high values quickly wash out competitive contrast." )]
public float BloomStrength { get; set; } = 0.18f;
[Property, Group( "World Look/Bloom" ), Range( 0f, 2f ), Title( "Threshold" )]
[Description( "HDR brightness required before a pixel contributes to bloom. Higher values restrict glow to the brightest lights/highlights." )]
public float BloomThreshold { get; set; } = 1.10f;
[Property, Group( "World Look/Bloom" ), Title( "Tint" )]
[Description( "Multiplies the accumulated bloom color. White preserves the authored light colors." )]
public Color BloomTint { get; set; } = Color.White;
[Button, Group( "World Look/Bloom" ), Title( "Subtle Standard Preset" )]
public void ApplyBloomSubtlePreset()
{
EnableBloom = true;
BloomMode = SceneCamera.BloomAccessor.BloomMode.Additive;
BloomStrength = 0.18f;
BloomThreshold = 1.10f;
BloomTint = Color.White;
RebuildBloom();
}
[Button, Group( "World Look/Bloom" ), Title( "Disable Bloom" )]
public void DisableBloom()
{
EnableBloom = false;
RebuildBloom();
}
[Button, Group( "World Look/Bloom" ), Title( "Setup Bloom Preview" )]
[Description( "Adds/reuses Sandbox.Bloom on the gameplay camera for editor preview and runtime world bloom." )]
public void SetupBloomPreview()
{
CameraComponent camera = GetComponent<CameraComponent>( true );
if ( camera is null || !camera.IsValid )
{
Log.Warning(
"[OMR VISUAL] Bloom preview setup failed: OMRVisualFoundation must share a GameObject with the gameplay CameraComponent."
);
return;
}
ResolveBloomComponent();
if ( _bloom is null || !_bloom.IsValid )
{
_bloom = GameObject.AddComponent<Bloom>( false );
_bloomCreatedByFoundation = false;
}
ApplyBloomSettings();
Log.Info(
$"[OMR VISUAL] Native Bloom ready on '{GameObject.Name}' | " +
$"Enabled:{_bloom.Enabled} | Mode:{_bloom.Mode} | " +
$"Strength:{_bloom.Strength:0.###} | Threshold:{_bloom.Threshold:0.###}"
);
}
private Bloom _bloom;
private bool _bloomCreatedByFoundation;
private bool _bloomReadyLogged;
private float EffectiveBloomStrength =>
EnableWorldLook && EnableBloom
? MathX.Clamp( BloomStrength, 0f, 10f ) * MathX.Clamp( BlendWeight, 0f, 1f )
: 0f;
private void ResetBloomSettings()
{
EnableBloom = false;
BloomMode = SceneCamera.BloomAccessor.BloomMode.Additive;
BloomStrength = 0.18f;
BloomThreshold = 1.10f;
BloomTint = Color.White;
}
private void ResolveBloomComponent()
{
if ( _bloom is not null && _bloom.IsValid )
return;
_bloom = GetComponent<Bloom>( true );
_bloomCreatedByFoundation = false;
}
private void RebuildBloom()
{
CameraComponent camera = GetComponent<CameraComponent>( true );
if ( camera is null || !camera.IsValid )
return;
ResolveBloomComponent();
// Do not silently author a component while ExecuteInEditor is ticking.
// The patch already includes one, and Setup Bloom Preview is available
// for scenes that do not.
if ( !Game.IsPlaying )
{
if ( _bloom is not null && _bloom.IsValid )
ApplyBloomSettings();
return;
}
if (
EnableBloom &&
(_bloom is null || !_bloom.IsValid)
)
{
_bloom = GameObject.AddComponent<Bloom>( false );
_bloomCreatedByFoundation = true;
}
if ( _bloom is null || !_bloom.IsValid )
return;
ApplyBloomSettings();
if ( _bloom.Enabled && LogSetup && !_bloomReadyLogged )
{
_bloomReadyLogged = true;
Log.Info(
$"[OMR VISUAL] Native world Bloom active on '{GameObject.Name}' | " +
$"Mode:{_bloom.Mode} | Strength:{_bloom.Strength:0.###} | " +
$"Threshold:{_bloom.Threshold:0.###} | " +
$"CameraPostProcessing:{camera.EnablePostProcessing}"
);
}
if ( !_bloom.Enabled )
_bloomReadyLogged = false;
}
private void ApplyBloomSettings()
{
if ( _bloom is null || !_bloom.IsValid )
return;
_bloom.Mode = BloomMode;
_bloom.Strength = EffectiveBloomStrength;
_bloom.Threshold = MathX.Clamp( BloomThreshold, 0f, 2f );
_bloom.Tint = BloomTint;
_bloom.Enabled = EffectiveBloomStrength > 0.0001f;
}
private void ReleaseBloom()
{
if ( _bloom is not null && _bloom.IsValid )
{
_bloom.Enabled = false;
if ( _bloomCreatedByFoundation )
_bloom.Destroy();
}
_bloom = null;
_bloomCreatedByFoundation = false;
_bloomReadyLogged = false;
}
}