A post-process effect class that emulates CRT display artifacts (mask presets, scanlines, flicker, softness). It reads editable properties, computes weighted numeric values for blending, sets shader attributes, loads a postprocess shader, and blits the effect with chosen priority.
using Sandbox;
using System;
using Sandbox.Rendering;
/// <summary>
/// Emulate the look of a CRT screen
/// </summary>
/// <summary>
/// Emulate the look of a CRT screen
/// </summary>
[Title( "CRT Emulation" )]
[Category( "Post Processing" )]
[Icon( "live_tv" )]
public sealed class CCSCRT : BasePostProcess<CCSCRT>
{
/// <summary>
/// Pattern to use for the CRT Mask.
/// </summary>
[Property, Title("CRT Mask Preset")]
public CRTPreset CRTPresetSelection { get; set; } = CRTPreset.SubPixelApertureGrille;
public enum CRTPreset
{
[Description( "Vertical Pattern" )]
SubPixelApertureGrille,
[Description( "Alternating pattern, looks like shadow mask." )]
SubPixelShadowMask,
[Description( "Fake slot-mask pattern" )]
SubPixelSlotMask,
[Description( "RGB mask with black lines" )]
FullPixelGrille,
[Description( "RGB alternating pattern" )]
FullPixelShadowMask,
None
}
/// <summary>
/// Blur the image slightly to create a "bloomy" look (works best with white enhance).
/// </summary>
[Property, Title("Softness"), Range(0.0f, 10.0f, 0, true)]
public float bLevel { get; set; } = 1.0f;
/// <summary>
/// FLASHING LIGHTS WARNING! Enables Screen Flicker (defaults are sane).
/// </summary>
[Property, ToggleGroup("Flicker", Label = "Screen Flicker")]
public bool Flicker { get; set; }
/// <summary>
/// How much to flash, 1 = fully black(crazy). 0.02 = semi realistic.
/// </summary>
[Property, Title("Flicker Opacity"), Group("Flicker"), Range(0.0f, 1.0f, 0, true)]
public float fOpacity { get; set; } = 0.02f;
/// <summary>
/// How often the screen flickers. Gets weird at high values.
/// </summary>
[Property, Title("Flicker Rate"), Group("Flicker"), Range(0.0f, 100.0f, 0, true)]
public float fRate { get; set; } = 60.0f;
/// <summary>
/// Enable CRT Scan line emulation.
/// </summary>
[Property, ToggleGroup("ScanLines", Label = "Scan Lines")]
public bool ScanLines { get; set; }
/// <summary>
/// Not the actual number of lines on the screen, but a value which scales the number of lines.
/// </summary>
[Property, Title("Line Count"), Group("ScanLines"), Range(0.0f, 1000.0f, 1, true)]
public float slCount { get; set; } = 480.0f;
/// <summary>
/// How dark the scanlines are.
/// </summary>
[Property, Title("Line Opacity"), Group("ScanLines"), Range(0.0f, 1.0f, 0, true)]
public float slOpacity { get; set; } = 0.1f;
/// <summary>
/// Rate at which to scroll the scanlines up or down the screen. high speed + opacity = FLASHING LIGHTS!
/// </summary>
[Property, Title("Scroll Rate"), Group("ScanLines"), Range(-100.0f, 100.0f, 0, true)]
public float slScroll { get; set; } = 1.2f;
/// <summary>
/// brings back some brighter colors, but some of the crt effect is lost.
/// </summary>
[Property, Title("Enhance Whites")]
public bool rWhite { get; set; }
/// <summary>
/// Ensures the effect is applied AFTER others in the stack.
/// </summary>
[Property, Title("Low Priority")]
public bool fPriority { get; set; }
public override void Render()
{
// ИСПРАВЛЕНО: Для bool и enum берем значения НАПРЯМУЮ, GetWeighted вызывает NullReferenceException!
CRTPreset currentPreset = CRTPresetSelection;
// Если выбран None, выходим
if (currentPreset == CRTPreset.None)
return;
// Числовые значения получаем через GetWeighted (для поддержки PostProcess Volume блендинга)
float bLevelW = GetWeighted(x => x.bLevel);
float fOpacityW = GetWeighted(x => x.fOpacity);
float fRateW = GetWeighted(x => x.fRate);
float slCountW = GetWeighted(x => x.slCount);
float slOpacityW = GetWeighted(x => x.slOpacity);
float slScrollW = GetWeighted(x => x.slScroll);
// Булевые значения берем напрямую
bool flickerW = Flicker;
bool scanLinesW = ScanLines;
bool rWhiteW = rWhite;
bool priorityW = fPriority;
// Передаем значения в шейдер
// В s&box bool в шейдер часто передается как int (0 или 1) для надежности
Attributes.Set("ScanLines", scanLinesW ? 1 : 0);
Attributes.Set("slCount", slCountW);
Attributes.Set("slOpacity", slOpacityW);
Attributes.Set("slScroll", slScrollW);
Attributes.Set("Flicker", flickerW ? 1 : 0);
Attributes.Set("fOpacity", fOpacityW);
Attributes.Set("fRate", fRateW);
Attributes.Set("rWhite", rWhiteW ? 1 : 0);
Attributes.Set("bLevel", bLevelW);
Attributes.Set("style", (int)currentPreset);
// Загружаем шейдер с проверкой на null
var shader = Material.FromShader("postprocess/ccs_crt.shader");
if (shader == null)
{
Log.Error("CCSCRT: Shader 'postprocess/ccs_crt.shader' not found!");
return;
}
// Выбираем приоритет
int priority = priorityW ? 200 : 2000;
var blit = BlitMode.WithBackbuffer(shader, Stage.AfterPostProcess, priority, true);
Blit(blit, "CCSCRT");
}
}