CCSColorKey.cs

A post-processing effect component that implements a color key / overlay. It exposes properties for key color, image texture, blending, transform and mode options, passes them to a shader material, and performs a blit using the engine postprocess API.

Native Interop
using Sandbox;
using Sandbox.Rendering;

/// <summary>
/// Key out a color from the image or apply an image overlay on top of the screen.
/// </summary>
[Title( "Color Keyer" )]
[Category( "Post Processing" )]
[Icon( "colorize" )]
public sealed class CCSColorKey : BasePostProcess<CCSColorKey>
{
    /// <summary>
    /// Color to key out.
    /// </summary>
    [Property, Title("Key Color")]
    public Color keyColor { get; set; } = new Color(0.0f, 0.83f, 0.0f, 1.0f);

    /// <summary>
    /// Texture to use for the image to be keyed / overlaid.
    /// </summary>
    [Property]
    public Texture image_texture { get; set; }

    // === Blending ===

    /// <summary>
    /// Background blend tolerance.
    /// </summary>
    // ИСПРАВЛЕНО: Убраны лишние аргументы (0, true) и [Header]
    [Property, Title("Background Blend"), Range(0.0f, 1.0f)]
    public float bBlend { get; set; } = 0.203f;

    /// <summary>
    /// Foreground blend tolerance.
    /// </summary>
    [Property, Title("Foreground Blend"), Range(0.0f, 1.0f)]
    public float fBlend { get; set; } = 0.203f;

    /// <summary>
    /// Adjusts the opacity of the area which is keyed out.
    /// </summary>
    [Property, Title("Mask Opacity"), Range(0.0f, 1.0f)]
    public float bOpacity { get; set; } = 1.0f;

    /// <summary>
    /// Adjusts the opacity of the main image.
    /// </summary>
    [Property, Title("Foreground Opacity"), Range(0.0f, 1.0f)]
    public float fOpacity { get; set; } = 1.0f;

    // === Image Transform ===

    /// <summary>
    /// Horizontal scale of the image.
    /// </summary>
    [Property, Title("Horizontal Scale"), Range(0.01f, 20.0f)]
    public float xScale { get; set; } = 1.0f;

    /// <summary>
    /// Vertical scale of the image.
    /// </summary>
    [Property, Title("Vertical Scale"), Range(0.01f, 20.0f)]
    public float yScale { get; set; } = 1.0f;

    /// <summary>
    /// Horizontal image position offset from center.
    /// </summary>
    [Property, Title("Horizontal Position"), Range(-1.0f, 1.0f)]
    public float xPos { get; set; } = 0.0f;

    /// <summary>
    /// Vertical image position offset from center.
    /// </summary>
    [Property, Title("Vertical Position"), Range(-1.0f, 1.0f)]
    public float yPos { get; set; } = 0.0f;

    // === Extras ===

    /// <summary>
    /// Swap foreground and background layers.
    /// </summary>
    [Property, Title("Overlay Mode")]
    public bool kSwap { get; set; }

    /// <summary>
    /// Allow the image texture to repeat.
    /// </summary>
    [Property, Title("Tile Image")]
    public bool iTile { get; set; } = true;

    /// <summary>
    /// Don't color key at all, just overlay.
    /// </summary>
    [Property, Title("Disable Keying")]
    public bool dKey { get; set; }

    /// <summary>
    /// Blend mode for compositing. Based on Photoshop/Clip Studio formulas.
    /// </summary>
    [Property, Title("Blend Mode")]
    public BlendMode BlendModeSelection { get; set; } = BlendMode.Normal;

    public enum BlendMode
    {
        [Description("Normal Opacity blending")]
        Normal,
        [Description("Light areas become Brighter (Linear Dodge)")]
        Additive,
        [Description("Subtract blend layer from base")]
        Subtract,
        [Description("Multiply colors together")]
        Multiply,
        [Description("Divide base by blend")]
        Divide,
        [Description("Screen blend - lighter result")]
        Screen,
        [Description("Color Dodge - brightens base using blend color")]
        ColorDodge
    }

    public override void Render()
    {
        // Float значения через GetWeighted для поддержки PostProcess Volume
        float bBlendW = GetWeighted(x => x.bBlend);
        float fBlendW = GetWeighted(x => x.fBlend);
        float bOpacityW = GetWeighted(x => x.bOpacity);
        float fOpacityW = GetWeighted(x => x.fOpacity);
        float xScaleW = GetWeighted(x => x.xScale);
        float yScaleW = GetWeighted(x => x.yScale);
        float xPosW = GetWeighted(x => x.xPos);
        float yPosW = GetWeighted(x => x.yPos);

        // Bool, Color, Texture, Enum берем напрямую
        Color keyColorW = keyColor;
        Texture imageTextureW = image_texture;
        bool kSwapW = kSwap;
        bool iTileW = iTile;
        bool dKeyW = dKey;
        BlendMode blendModeW = BlendModeSelection;

        // Передаем атрибуты в шейдер
        Attributes.Set("keyColor", new Vector4(keyColorW.r, keyColorW.g, keyColorW.b, keyColorW.a));
        Attributes.Set("image_texture", imageTextureW);
        Attributes.Set("bBlend", bBlendW);
        Attributes.Set("fBlend", fBlendW);
        Attributes.Set("bOpacity", bOpacityW);
        Attributes.Set("fOpacity", fOpacityW);
        Attributes.Set("xScale", xScaleW);
        Attributes.Set("yScale", yScaleW);
        Attributes.Set("xPos", xPosW);
        Attributes.Set("yPos", yPosW);

        // Bool → int (0/1) для надежной работы с HLSL
        Attributes.Set("kSwap", kSwapW ? 1 : 0);
        Attributes.Set("iTile", iTileW ? 1 : 0);
        Attributes.Set("dKey", dKeyW ? 1 : 0);
        Attributes.Set("BlendMode", (int)blendModeW);

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

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