CCSAnalogNoise.cs

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.

Native Interop
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");
    }
}