A post-processing effect component for s&box that applies analog TV static, tone curve adjustments and a box blur in three shader passes. It exposes properties for opacity, blur radius and tone curve controls, loads three materials, sets shader attributes and issues Blit calls.
using Sandbox;
using System;
using Sandbox;
using Sandbox.Rendering;
/// <summary>
/// Generate random noise (not very good).
/// </summary>
[Title( "TV Static" )]
[Category( "Post Processing" )]
[Icon( "broken_image" )]
public sealed class CCSAnalogNoise : BasePostProcess<CCSAnalogNoise>
{
/// <summary>
/// noise-image blend, please note: the fine tune controls effect the entire image
/// </summary>
[Property, Title("Opacity"), Range(0.00f, 1.0f, 0, true)]
public float fOpacity { get; set; } = 0.75f;
/// <summary>
/// Makes the noise less stretched out.
/// </summary>
[Property, Title("Don't Stretch"), Group("Fine Tune")]
public bool bAspect { get; set; }
/// <summary>
/// How much to blur the image + noise
/// </summary>
[Property, Title("Blur Radius"), Range(0.0f, 20.0f, 1, true), Group("Fine Tune")]
public float bRadius { get; set; } = 4.0f;
/// <summary>
/// Darkest parts of the image. cool value: -0.25
/// </summary>
[Property, Title("Blacks"), Group("Fine Tune"), Range(-0.25f, 1.0f, 0)]
public float Blacks { get; set; } = 0.0f;
/// <summary>
/// Rolloff from midtones to shadow. cool value: 0.0
/// </summary>
[Property, Title("Shadows"), Group("Fine Tune"), Range(0.0f, 1.0f, 0)]
public float Shadows { get; set; } = 0.25f;
/// <summary>
/// Parts of the image around 50%. cool value: 0.05
/// </summary>
[Property, Title("Midtones"), Group("Fine Tune"), Range(0.0f, 1.0f, 0)]
public float Midtones { get; set; } = 0.5f;
/// <summary>
/// Rolloff from midtones to whites. cool value: 0.365
/// </summary>
[Property, Title("Highlights"), Group("Fine Tune"), Range(0.0f, 1.0f, 0)]
public float Highlights { get; set; } = 0.75f;
/// <summary>
/// Brightest parts of the image. cool value: 1.35
/// </summary>
[Property, Title("Whites"), Group("Fine Tune"), Range(0.0f, 1.5f, 0)]
public float Whites { get; set; } = 1.0f;
// Приватные свойства не сериализуются, но используются в шейдере
private float bMulti { get; set; } = 8.0f;
private bool bCircle { get; set; } = false;
private bool bLoop { get; set; } = true;
public override void Render()
{
// Числовые значения через GetWeighted — поддерживает блендинг в PostProcess Volume
float fOpacityW = GetWeighted(x => x.fOpacity);
float bRadiusW = GetWeighted(x => x.bRadius);
float BlacksW = GetWeighted(x => x.Blacks);
float ShadowsW = GetWeighted(x => x.Shadows);
float MidtonesW = GetWeighted(x => x.Midtones);
float HighlightsW = GetWeighted(x => x.Highlights);
float WhitesW = GetWeighted(x => x.Whites);
// Bool берем напрямую — GetWeighted с bool вызывает NullReferenceException
bool bAspectW = bAspect;
bool bCircleW = bCircle;
bool bLoopW = bLoop;
// Загружаем материалы с проверкой на null
var analogMat = Material.FromShader("postprocess/ccs_analog.shader");
var tonecurvesMat = Material.FromShader("postprocess/ccs_tonecurves.shader");
var boxblurMat = Material.FromShader("postprocess/ccs_boxfilter.shader");
if (analogMat == null || tonecurvesMat == null || boxblurMat == null)
{
Log.Warning("CCSAnalogNoise: One or more materials not found!");
return;
}
// ПРОХОД 1: Analog noise
Attributes.Set("fOpacity", fOpacityW);
var blit1 = BlitMode.WithBackbuffer(analogMat, Stage.AfterPostProcess, 8000, true);
Blit(blit1, "CCSAnalogNoise_Pass1");
// ПРОХОД 2: Tone curves
Attributes.Set("Blacks", BlacksW);
Attributes.Set("Shadows", ShadowsW);
Attributes.Set("Midtones", MidtonesW);
Attributes.Set("Highlights", HighlightsW);
Attributes.Set("Whites", WhitesW);
var blit2 = BlitMode.WithBackbuffer(tonecurvesMat, Stage.AfterPostProcess, 8000, true);
Blit(blit2, "CCSAnalogNoise_Pass2");
// ПРОХОД 3: Box blur
Attributes.Set("bRadius", bRadiusW);
Attributes.Set("bMulti", bMulti);
// Bool передаем как int (0/1) для надежной работы с HLSL
Attributes.Set("bAspect", bAspectW ? 1 : 0);
Attributes.Set("bCircle", bCircleW ? 1 : 0);
Attributes.Set("bLoop", bLoopW ? 1 : 0);
var blit3 = BlitMode.WithBackbuffer(boxblurMat, Stage.AfterPostProcess, 8000, true);
Blit(blit3, "CCSAnalogNoise_Pass3");
}
}