A post-processing component that draws letterbox black bars. It exposes presets and manual controls for aspect ratio, computes bar thicknesses, updates an AspectRatio property, and sets shader attributes before blitting a postprocess material.
using Sandbox;
using System;
using Sandbox.Rendering;
/// <summary>
/// Crop the screen using black bars like you are watching a DVD on an old tv.
/// </summary>
[Title( "Letterboxing" )]
[Category( "Post Processing" )]
[Icon( "theaters" )]
public sealed class CCSLetterbox : BasePostProcess<CCSLetterbox>
{
[Property]
public Color LetterboxColor { get; set; } = Color.Black;
private static readonly float[] AspectRatios = { 0.00f, 2.39f, 1.85f, 1.33f, 1.50f, 1.00f, 0.5625f, 0.80f, 2.76f, 3.50f, 4.00f };
private AspectPreset _aspectSelection;
[Property, Title("Aspect Ratio Preset")]
public AspectPreset AspectSelection
{
get => _aspectSelection;
set => _aspectSelection = value;
}
public enum AspectPreset
{
None,
[Description( "2.39:1 Widescreen film" )]
Scope,
[Description( "1.85:1 Theatrical film" )]
Flat,
[Description( "4:3 CLASSIC" )]
TV,
[Description( "3:2 35mm Photo" )]
FilmPhoto,
[Description( "1:1 Perfection" )]
Square,
[Description( "9:16 Vertical Video" )]
Vertical,
[Description( "4:5 Slightly less ugly" )]
InstaVertical,
[Description( "2.76:1 The Cinema is yours" )]
UltraPanavision70,
[Description( "3.5:1 aka 32:9, dual monitor" )]
SuperUltraWide,
[Description( "4:1 Napoléon [1927]" )]
Polyvision
}
private bool _manualSettings;
[Property, Title("Manual Settings"), ToggleGroup("ManualSettings", Label = "Manual Settings")]
public bool ManualSettings
{
get => _manualSettings;
set => _manualSettings = value;
}
[Property, Title("Aspect Ratio"), ReadOnly, Group("ManualSettings")]
public float AspectRatio { get; private set; } = 1.777f;
[Property, Title("Horizontal Bar Thickness"), Range(0, 100.0f, 0, true), Group("ManualSettings")]
public float vPercent { get; set; } = 33.3f;
[Property, Title("Vertical Bar Thickness"), Range(0, 100.0f, 0, true), Group("ManualSettings")]
public float hPercent { get; set; } = 0.0f;
private void OnAspectPresetChanged()
{
if (AspectSelection != AspectPreset.None)
{
_manualSettings = false;
}
}
private void OnManualSettingsChanged()
{
if (_manualSettings)
{
AspectSelection = AspectPreset.None;
}
}
protected override void OnUpdate()
{
var cc = Components.Get<CameraComponent>(true);
if (cc == null || cc.ScreenRect.Width <= 0 || cc.ScreenRect.Height <= 0)
return;
// Если выбран пресет, вычисляем проценты и обновляем свойства
if (AspectSelection != AspectPreset.None)
{
float cameraAspect = cc.ScreenRect.Width / cc.ScreenRect.Height;
float desiredAspect = AspectRatios[(int)AspectSelection];
if (desiredAspect > cameraAspect)
{
vPercent = 100.0f * (1.0f - (cameraAspect / desiredAspect));
hPercent = 0.0f;
}
else if (desiredAspect < cameraAspect)
{
hPercent = 100.0f * (1.0f - (desiredAspect / cameraAspect));
vPercent = 0.0f;
}
else
{
vPercent = 0.0f;
hPercent = 0.0f;
}
}
// Обновляем AspectRatio на основе текущих значений vPercent и hPercent
float effectiveWidth = cc.ScreenRect.Width * (1.0f - hPercent / 100.0f);
float effectiveHeight = cc.ScreenRect.Height * (1.0f - vPercent / 100.0f);
if (effectiveHeight > 0.0001f)
{
AspectRatio = effectiveWidth / effectiveHeight;
}
else
{
// Если высота слишком мала, используем оригинальный aspect ratio камеры
AspectRatio = cc.ScreenRect.Width / cc.ScreenRect.Height;
}
}
public override void Render()
{
// Используем GetWeighted для поддержки PostProcess Volume
float v = GetWeighted(x => x.vPercent);
float h = GetWeighted(x => x.hPercent);
Color color = GetWeighted(x => x.LetterboxColor);
// Если эффект не нужен, выходим
if (v.AlmostEqual(0.0f) && h.AlmostEqual(0.0f))
return;
// Передаем значения в шейдер
Attributes.Set("vPercent", v);
Attributes.Set("hPercent", h);
// Конвертируем Color в Vector4 для шейдера (float4)
Attributes.Set("LetterboxColor", new Vector4(color.r, color.g, color.b, color.a));
var shader = Material.FromShader("postprocess/ccs_letterbox.shader");
var blit = BlitMode.WithBackbuffer(shader, Stage.AfterPostProcess, 21000, true);
Blit(blit, "CCSLetterbox");
}
}