OMRAdaptivePixelationPostProcess.cs
using Sandbox;
using Sandbox.Rendering;
using System;
/// <summary>
/// Runtime world-only depth-adaptive, edge-aware pixelation pass.
///
/// The existing OMR PixelateScale is used as the reference block size so a
/// profile multiplier of 1.0 is directly comparable with the legacy/native-
/// style world pixelation. Up to six distance anchors can vary that block size
/// while depth/normal protection prevents coarse samples crossing silhouettes.
/// </summary>
[Title( "OMR Adaptive Pixelation Post Process" )]
[Category( "One More Round/Internal" )]
public sealed class OMRAdaptivePixelationPostProcess : BasePostProcess<OMRAdaptivePixelationPostProcess>
{
private const string ShaderPath = "shaders/omr_adaptive_pixelation.shader";
private const int RenderOrder = 800;
public float Blend { get; set; } = 1f;
public float BasePixelateScale { get; set; } = 0.1f;
public int PointCount { get; set; } = 3;
public int InterpolationMode { get; set; } = 2;
public float Point1Distance { get; set; } = 0f;
public float Point1Multiplier { get; set; } = 2f;
public float Point2Distance { get; set; } = 560f;
public float Point2Multiplier { get; set; } = 1f;
public float Point3Distance { get; set; } = 1000f;
public float Point3Multiplier { get; set; } = 0.5f;
public float Point4Distance { get; set; } = 1400f;
public float Point4Multiplier { get; set; } = 0.4f;
public float Point5Distance { get; set; } = 2000f;
public float Point5Multiplier { get; set; } = 0.3f;
public float Point6Distance { get; set; } = 3000f;
public float Point6Multiplier { get; set; } = 0.25f;
public float DepthThreshold { get; set; } = 0.035f;
public bool UseNormalProtection { get; set; } = true;
public float NormalThreshold { get; set; } = 0.16f;
public float ProtectionStrength { get; set; } = 1f;
public float FallbackScale { get; set; } = 0.5f;
public bool PixelateSky { get; set; } = false;
public int DebugMode { get; set; } = 0;
private Material _material;
private bool _materialFailureLogged;
public override void Render()
{
bool debugActive = DebugMode != 0;
if ( Blend <= 0.0001f && !debugActive )
return;
if ( _material is null || !_material.IsValid )
{
_material = Material.FromShader( ShaderPath );
if ( _material is null || !_material.IsValid )
{
if ( !_materialFailureLogged )
{
_materialFailureLogged = true;
Log.Warning( $"[OMR VISUAL] Could not create adaptive-pixelation material from '{ShaderPath}'." );
}
return;
}
_materialFailureLogged = false;
}
int pointCount = Math.Clamp( PointCount, 2, 6 );
int interpolationMode = Math.Clamp( InterpolationMode, 0, 2 );
float d1 = MathF.Max( 0f, Point1Distance );
float d2 = MathF.Max( d1 + 1f, Point2Distance );
float d3 = MathF.Max( d2 + 1f, Point3Distance );
float d4 = MathF.Max( d3 + 1f, Point4Distance );
float d5 = MathF.Max( d4 + 1f, Point5Distance );
float d6 = MathF.Max( d5 + 1f, Point6Distance );
Attributes.Set( "OMRAdaptiveBlend", MathX.Clamp( Blend, 0f, 1f ) );
Attributes.Set( "OMRAdaptiveBasePixelateScale", MathX.Clamp( BasePixelateScale, 0f, 1f ) );
Attributes.Set( "OMRAdaptivePointCount", (float)pointCount );
Attributes.Set( "OMRAdaptiveInterpolationMode", (float)interpolationMode );
Attributes.Set( "OMRAdaptivePoint1Distance", d1 );
Attributes.Set( "OMRAdaptivePoint1Multiplier", MathX.Clamp( Point1Multiplier, 0.1f, 4f ) );
Attributes.Set( "OMRAdaptivePoint2Distance", d2 );
Attributes.Set( "OMRAdaptivePoint2Multiplier", MathX.Clamp( Point2Multiplier, 0.1f, 4f ) );
Attributes.Set( "OMRAdaptivePoint3Distance", d3 );
Attributes.Set( "OMRAdaptivePoint3Multiplier", MathX.Clamp( Point3Multiplier, 0.1f, 4f ) );
Attributes.Set( "OMRAdaptivePoint4Distance", d4 );
Attributes.Set( "OMRAdaptivePoint4Multiplier", MathX.Clamp( Point4Multiplier, 0.1f, 4f ) );
Attributes.Set( "OMRAdaptivePoint5Distance", d5 );
Attributes.Set( "OMRAdaptivePoint5Multiplier", MathX.Clamp( Point5Multiplier, 0.1f, 4f ) );
Attributes.Set( "OMRAdaptivePoint6Distance", d6 );
Attributes.Set( "OMRAdaptivePoint6Multiplier", MathX.Clamp( Point6Multiplier, 0.1f, 4f ) );
Attributes.Set( "OMRAdaptiveDepthThreshold", MathX.Clamp( DepthThreshold, 0.001f, 0.5f ) );
Attributes.Set( "OMRAdaptiveUseNormals", UseNormalProtection ? 1f : 0f );
Attributes.Set( "OMRAdaptiveNormalThreshold", MathX.Clamp( NormalThreshold, 0.001f, 1f ) );
Attributes.Set( "OMRAdaptiveProtectionStrength", MathX.Clamp( ProtectionStrength, 0f, 1f ) );
Attributes.Set( "OMRAdaptiveFallbackScale", MathX.Clamp( FallbackScale, 0.1f, 1f ) );
Attributes.Set( "OMRAdaptivePixelateSky", PixelateSky ? 1f : 0f );
Attributes.Set( "OMRAdaptiveDebugMode", (float)DebugMode );
// Order 800 intentionally runs immediately before OMR Edge Cues (900).
// Pixelation simplifies world color first while Edge Cues still reads
// the untouched scene depth/G-buffer to reinforce spatial separation.
var blit = BlitMode.WithBackbuffer(
_material,
Stage.BeforePostProcess,
RenderOrder,
false
);
Blit( blit, "OMR Adaptive Pixelation" );
}
}