Files
Test/Assets/ColbyO/VNTG/Scripts/CRT/CRTRendererPass.cs
T
2026-11-01 23:39:55 +02:00

282 lines
13 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
//-----------------------------------------------------------------------
// Author: Colby-O
// File: CRTRendererPass.cs
//-----------------------------------------------------------------------
namespace ColbyO.VNTG.CRT
{
internal sealed class CRTRendererPass : ScriptableRenderPass
{
private const string kPassName = "CRT Effect Pass";
#if UNITY_6000_4_OR_NEWER
private Dictionary<EntityId, RTHandle> _historyBuffers = new();
#else
private Dictionary<int, RTHandle> _historyBuffers = new();
#endif
#if UNITY_6000_4_OR_NEWER
private Dictionary<EntityId, CameraRefreshState> _cameraLastTimes = new();
#else
private Dictionary<int, CameraRefreshState> _cameraLastTimes = new();
#endif
private Material _material;
public CRTRendererPass(Material material)
{
_material = material;
requiresIntermediateTexture = true;
}
public void Setup(Material material)
{
_material = material;
}
private void UpdateMaterialWithSettings(Material mat, CRTSettings settings)
{
mat.SetFloat("_RefreshRate", settings.EnableScreenSettings.value ? settings.RefreshRate.value : 50);
mat.SetFloat("_DecayRate", settings.EnableScreenSettings.value ? settings.DecayRate.value : 1f);
mat.SetVector("_ScreenResolution", settings.EnableScreenSettings.value ? settings.ScreenResolution.value : new Vector2(Screen.width, Screen.height));
mat.SetInt("_EnableInterlacedRendering", settings.EnableScreenSettings.value ? (settings.EnableInterlacedRendering.value ? 1 : 0) : 0);
mat.SetInt("_EnableScreenBend", settings.EnableScreenBend.value ? 1 : 0);
mat.SetFloat("_ScreenBend", settings.ScreenBend.value);
mat.SetFloat("_ScreenRoundness", settings.EnableVignette.value ? settings.ScreenRoundness.value : 0f);
mat.SetFloat("_VignetteOpacity", settings.EnableVignette.value ? settings.VignetteOpacity.value : 1f);
mat.SetVector("_ScanLineOpacity", settings.EnableScanlines.value ? new Vector2(settings.ScanLineVerticalOpacity.value, settings.ScanLineHorizontalOpacity.value) : new Vector2(0f, 0f));
mat.SetVector("_ScanLineSpeed", settings.EnableScanlines.value ? new Vector2(settings.ScanLineVerticalSpeed.value, settings.ScanLineHorizontalSpeed.value) : new Vector2(0f, 0f));
mat.SetFloat("_ScanLineStrength", settings.EnableScanlines.value ? settings.ScanLineStrength.value : 0f);
mat.SetFloat("_NoiseSpeed", settings.EnableNoise.value ? settings.NoiseSpeed.value : 0f);
mat.SetFloat("_NoiseScale", settings.EnableNoise.value ? settings.NoiseScale.value : 0f);
mat.SetVector("_NoiseRGBOffset", settings.EnableNoise.value ? new Vector2(settings.NoiseRBGOffsetX.value, settings.NoiseRBGOffsetY.value) : new Vector2(0f, 0f));
mat.SetFloat("_NoiseFade", settings.EnableNoise.value ? settings.NoiseFade.value : 0f);
mat.SetFloat("_VHSSmear", settings.EnableVhs.value ? Mathf.Lerp(1f, 0.05f, settings.VhsSmear.value) : 1f);
mat.SetFloat("_UnsharpAmount", settings.EnableUnsharp.value ? settings.UnsharpAmount.value : 0f);
mat.SetFloat("_UnsharpRadius", settings.EnableUnsharp.value ? settings.UnsharpRadius.value : 0f);
mat.SetFloat("_UnsharpThreshold", settings.EnableUnsharp.value ? settings.UnsharpThreshold.value : 0f);
mat.SetFloat("_ClampBlack", settings.EnableClampColor.value ? settings.ClampBlack.value : 0f);
mat.SetFloat("_ClampWhite", settings.EnableClampColor.value ? settings.ClampWhite.value : 1f);
mat.SetColor("_TintShadowsColor", settings.EnableClampColor.value ? settings.ShadowTint.value : Color.black);
mat.SetInt("_EnableTrackerLine", settings.EnableTrackerLine.value ? 1 : 0);
mat.SetFloat("_TrackingSpeed", settings.TrackingSpeed.value);
mat.SetFloat("_TrackingJitter", settings.TrackingJitter.value);
mat.SetInt("_EnableSignalInterference", settings.EnableSignalInterference.value ? 1 : 0);
mat.SetFloat("_InterferenceFrequency", settings.InterferenceFrequency.value);
mat.SetFloat("_InterferenceAmplitude", settings.InterferenceAmplitude.value);
mat.SetFloat("_ChromaticOffset", settings.EnableChromaticAberration.value ? settings.ChromaticOffset.value : 0f);
mat.SetFloat("_ChromaticSpeed", settings.EnableChromaticAberration.value ? settings.ChromaticOffsetSpeed.value : 0f);
mat.SetFloat("_Brightness", settings.EnableColorAdjustment.value ? settings.Brightness.value : 0.5f);
mat.SetFloat("_Contrast", settings.EnableColorAdjustment.value ? settings.Contrast.value : 0.5f);
mat.SetFloat("_Saturation", settings.EnableColorAdjustment.value ? settings.Saturation.value : 0.5f);
mat.SetFloat("_Gamma", settings.EnableColorAdjustment.value ? settings.Gamma.value : 0.5f);
mat.SetFloat("_Hue", settings.EnableColorAdjustment.value ? settings.Hue.value : 0f);
mat.SetFloat("_RedShift", settings.EnableColorAdjustment.value ? settings.RedShift.value : 0f);
mat.SetFloat("_GreenShift", settings.EnableColorAdjustment.value ? settings.GreenShift.value : 0f);
mat.SetFloat("_BlueShift", settings.EnableColorAdjustment.value ? settings.BlueShift.value : 0f);
mat.SetInt("_IsMonochrome", settings.EnableColorAdjustment.value ? (settings.IsMonochrome.value ? 1 : 0) : 0);
mat.SetInt("_SubPixelMode", settings.EnableSubPixels.value ? (int)settings.SubPixelMode.value : 0);
mat.SetFloat("_SubPixelDesnity", settings.SubPixelDensity.value);
mat.SetFloat("_GlitchChance", settings.EnableGlitch.value ? settings.GlitchChance.value : 0f);
mat.SetFloat("_GlitchLength", settings.GlitchLength.value);
}
#if UNITY_6000_4_OR_NEWER
private (bool shouldRefresh, int interlaceOffset) ShouldRefresh(EntityId camID, CRTSettings settings)
#else
private (bool shouldRefresh, int interlaceOffset) ShouldRefresh(int camID, CRTSettings settings)
#endif
{
if (!_cameraLastTimes.TryGetValue(camID, out CameraRefreshState state))
{
state = new CameraRefreshState { accumulatedTime = 0f, interlaceOffset = 0 };
}
if (!settings.EnableScreenSettings.value || settings.UseMaxFPS.value)
{
state.interlaceOffset = (state.interlaceOffset + 1) % 2;
state.accumulatedTime = 0f;
_cameraLastTimes[camID] = state;
return (true, state.interlaceOffset);
}
float refreshRate = settings.RefreshRate.value;
float targetInterval = refreshRate > 0f ? (1.0f / refreshRate) : 0.016f;
state.accumulatedTime += Time.deltaTime;
bool shouldRefresh = false;
if (state.accumulatedTime >= targetInterval)
{
shouldRefresh = true;
state.interlaceOffset = (state.interlaceOffset + 1) % 2;
state.accumulatedTime %= targetInterval;
}
_cameraLastTimes[camID] = state;
return (shouldRefresh, state.interlaceOffset);
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
VolumeStack stack = VolumeManager.instance.stack;
CRTSettings settings = stack.GetComponent<CRTSettings>();
if (settings == null || !settings.IsActive()) return;
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
if (resourceData.isActiveTargetBackBuffer)
{
Debug.LogError("Skipping render pass. CRT render requries an intermediate ColorTexture.");
return;
}
TextureHandle src = resourceData.activeColorTexture;
#if UNITY_6000_4_OR_NEWER
EntityId camID = cameraData.camera.GetEntityId();
#else
int camID = cameraData.camera.GetInstanceID();
#endif
(bool shouldRefresh, int interlaceOffset) = ShouldRefresh(camID, settings);
float forceRefresh = _historyBuffers.ContainsKey(camID) ? 0.0f : 1.0f;
RTHandle historyRT = GetHistoryBuffer(camID, cameraData, cameraData.cameraTargetDescriptor);
TextureHandle historyHandle = renderGraph.ImportTexture(historyRT);
TextureDesc dstDesc = renderGraph.GetTextureDesc(src);
dstDesc.name = "CRT_Output";
dstDesc.clearBuffer = false;
TextureHandle dst = renderGraph.CreateTexture(dstDesc);
using (IRasterRenderGraphBuilder builder = renderGraph.AddRasterRenderPass(kPassName, out PassData passData))
{
passData.src = src;
passData.history = historyHandle;
passData.material = _material;
passData.settings = settings;
passData.forceRefresh = forceRefresh;
passData.shouldRefresh = shouldRefresh ? 1.0f : 0.0f;
passData.interlaceOffset = interlaceOffset;
builder.UseTexture(passData.src, AccessFlags.Read);
builder.UseTexture(passData.history, AccessFlags.Read);
builder.SetRenderAttachment(dst, 0, AccessFlags.Write);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
data.material.SetTexture("_PrevFrameTex", data.history);
data.material.SetFloat("_ForceRefresh", data.forceRefresh);
data.material.SetFloat("_ShouldRefresh", data.shouldRefresh);
data.material.SetInt("_InterlaceOffset", data.interlaceOffset);
UpdateMaterialWithSettings(data.material, data.settings);
Blitter.BlitTexture(context.cmd, data.src, new Vector4(1, 1, 0, 0), data.material, 0);
});
}
renderGraph.AddBlitPass(dst, historyHandle, Vector2.one, Vector2.zero, passName: "Update History");
resourceData.cameraColor = dst;
}
#if UNITY_6000_4_OR_NEWER
private RTHandle GetHistoryBuffer(EntityId id, UniversalCameraData cameraData, RenderTextureDescriptor desc)
#else
private RTHandle GetHistoryBuffer(int id, UniversalCameraData cameraData, RenderTextureDescriptor desc)
#endif
{
if (!_historyBuffers.TryGetValue(id, out RTHandle historyRT) ||
historyRT == null ||
historyRT.rt.width != cameraData.cameraTargetDescriptor.width ||
historyRT.rt.height != cameraData.cameraTargetDescriptor.height)
{
historyRT?.Release();
desc.depthBufferBits = 0;
desc.msaaSamples = 1;
historyRT = RTHandles.Alloc(
desc.width,
desc.height,
colorFormat: desc.graphicsFormat,
depthBufferBits: DepthBits.None,
name: $"_CRT_History_{id}"
);
_historyBuffers[id] = historyRT;
}
return _historyBuffers[id];
}
#if UNITY_6000_4_OR_NEWER
public void Cleanup(EntityId camID)
#else
public void Cleanup(int camID)
#endif
{
if (_historyBuffers.ContainsKey(camID))
{
_historyBuffers[camID]?.Release();
_historyBuffers.Remove(camID);
}
if (_cameraLastTimes.ContainsKey(camID))
{
_cameraLastTimes.Remove(camID);
}
}
public void Cleanup()
{
foreach (RTHandle rt in _historyBuffers.Values)
{
rt?.Release();
}
_historyBuffers.Clear();
}
private struct CameraRefreshState
{
public float accumulatedTime;
public int interlaceOffset;
}
private class PassData
{
public TextureHandle src;
public TextureHandle history;
public Material material;
public CRTSettings settings;
public float forceRefresh;
public float shouldRefresh;
public int interlaceOffset;
}
}
}