CCSToneCurves.cs

A post-processing component for S&box that exposes five-point tone curve controls (Blacks, Shadows, Midtones, Highlights, Whites) and a LumaOnly toggle, and applies them to a shader material during Render(). It reads weighted property values for blending, sets them as shader attributes, loads a shader material, and issues a Blit call to apply the effect.

Native Interop
using Sandbox;
using System;


using Sandbox;
using Sandbox.Rendering;

/// <summary>
/// Basic 5 point curves adjustment
/// </summary>
[Title( "Tone Curves" )]
[Category( "Post Processing" )]
[Icon( "shape_line" )]
public sealed class CCSToneCurve : BasePostProcess<CCSToneCurve>
{
    /// <summary>
    /// Darkest parts of the image.
    /// </summary>
    [Property, Title("Blacks"), Range(-0.25f, 1.0f, 0)]
    public float Blacks { get; set; } = 0.0f;

    /// <summary>
    /// Rolloff from midtones to shadow.
    /// </summary>
    [Property, Title("Shadows"), Range(0.0f, 1.0f, 0)]
    public float Shadows { get; set; } = 0.25f;

    /// <summary>
    /// Parts of the image around 50%.
    /// </summary>
    [Property, Title("Midtones"), Range(0.0f, 1.0f, 0)]
    public float Midtones { get; set; } = 0.5f;

    /// <summary>
    /// Rolloff from midtones to whites.
    /// </summary>
    [Property, Title("Highlights"), Range(0.0f, 1.0f, 0)]
    public float Highlights { get; set; } = 0.75f;

    /// <summary>
    /// Brightest parts of the image.
    /// </summary>
    [Property, Title("Whites"), Range(0.0f, 1.5f, 0)]
    public float Whites { get; set; } = 1.0f;

    /// <summary>
    /// Reduces saturation increase, you probably want this on.
    /// </summary>
    [Property, Title("Luma Only")]
    public bool LumaOnly { get; set; }

    //  [Property, Title("Work in Linear")]  
    //  public bool wLinear { get; set; }

    public override void Render()
    {
        // Числовые значения через GetWeighted — поддерживает блендинг в PostProcess Volume
        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 lumaOnlyW = LumaOnly;
        //  bool wLinearW = wLinear;

        // Передаем значения в шейдер
        Attributes.Set("Blacks", blacksW);
        Attributes.Set("Shadows", shadowsW);
        Attributes.Set("Midtones", midtonesW);
        Attributes.Set("Highlights", highlightsW);
        Attributes.Set("Whites", whitesW);

        // Bool передаем как int (0/1) для надежной работы с HLSL
        Attributes.Set("LumaOnly", lumaOnlyW ? 1 : 0);
        //  Attributes.Set("wLinear", wLinearW ? 1 : 0);

        // Загружаем материал с проверкой на null
        var material = Material.FromShader("postprocess/ccs_tonecurves.shader");
        if (material == null)
        {
            Log.Warning("CCSToneCurve: Material 'materials/postprocess/ccs_tonecurves.vmat' not found!");
            return;
        }

        // Применяем эффект через современный Blit API
        // Приоритет 8000 перенесен из оригинального кода
        var blit = BlitMode.WithBackbuffer(material, Stage.AfterPostProcess, 8000, true);
        Blit(blit, "CCSToneCurve");
    }
}