Opti + Add Theatre Credit + Post process Una

This commit is contained in:
2026-09-29 17:13:10 +02:00
parent 7d31b8d165
commit f6c265671c
65 changed files with 82122 additions and 534 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3f164a5a2b9742647a4a52bc88071d29
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,78 @@
using UnityEngine;
/// <summary>
/// Old film projector feel for the credits projected on the theatre screen: the projector light,
/// the projected image and the light rays share the same subtle brightness flicker, with rare
/// short dips like a skipping film.
/// </summary>
public class CreditsProjector : MonoBehaviour
{
[SerializeField] private Light _light;
[SerializeField] private CanvasGroup _image;
[SerializeField] private Renderer[] _rays;
[Tooltip("Continuous brightness variation (0 = steady)")]
[SerializeField, Range(0f, 1f)] private float _flickerAmount = .08f;
[SerializeField, Min(0f)] private float _flickerSpeed = 7f;
[Tooltip("Average number of short brightness dips per second")]
[SerializeField, Min(0f)] private float _dipsPerSecond = .3f;
[SerializeField, Range(0f, 1f)] private float _dipAmount = .25f;
[SerializeField, Min(0f)] private float _dipDuration = .06f;
[Tooltip("How much of the flicker reaches the projected image (the light and rays get all of it)")]
[SerializeField, Range(0f, 1f)] private float _imageFlicker = .5f;
private static readonly int ColorId = Shader.PropertyToID("_Color");
private float _baseIntensity;
private float _baseAlpha;
private Color[] _baseRayColors;
private MaterialPropertyBlock _block;
private float _dipTimer;
private float _seed;
private void Awake()
{
_seed = Random.Range(0f, 100f);
_block = new MaterialPropertyBlock();
if (_light) _baseIntensity = _light.intensity;
if (_image) _baseAlpha = _image.alpha;
_baseRayColors = new Color[_rays.Length];
for (int i = 0; i < _rays.Length; i++)
_baseRayColors[i] = _rays[i] && _rays[i].sharedMaterial ? _rays[i].sharedMaterial.GetColor(ColorId) : Color.white;
}
private void Update()
{
float noise = Mathf.PerlinNoise(Time.time * _flickerSpeed, _seed);
float brightness = 1f - _flickerAmount * noise;
if (_dipTimer > 0f)
_dipTimer -= Time.deltaTime;
else if (Random.value < _dipsPerSecond * Time.deltaTime)
_dipTimer = _dipDuration;
if (_dipTimer > 0f)
brightness *= 1f - _dipAmount;
if (_light)
_light.intensity = _baseIntensity * brightness;
if (_image)
_image.alpha = _baseAlpha * Mathf.Lerp(1f, brightness, _imageFlicker);
for (int i = 0; i < _rays.Length; i++)
{
if (!_rays[i])
continue;
_rays[i].GetPropertyBlock(_block);
Color color = _baseRayColors[i];
_block.SetColor(ColorId, new Color(color.r * brightness, color.g * brightness, color.b * brightness, color.a));
_rays[i].SetPropertyBlock(_block);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3817b92fc8cd2a2418e308550ed0adeb
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 994abbd88a0c1bc40af91f7078fcf9a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,132 @@
using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;
/// <summary>
/// Scene-local "altered perception" fullscreen effect (Custom/LiminalPerception) for the main camera.
/// The pass is injected only while this component is enabled, so the shared renderer asset and the
/// other scenes are untouched. Fade it in/out from UnityEvents; while active, its intensity slowly
/// swells and recedes in irregular waves.
/// Photosensitivity: the shader and the swell only use slow, smooth variations (no flashes).
/// </summary>
public class LiminalPerception : MonoBehaviour
{
[SerializeField] private Material _material;
[SerializeField, Range(0f, 1f)] private float _initialWeight = 0f;
[SerializeField, Min(0f)] private float _fadeDuration = 4f;
[Tooltip("Speed of the irregular intensity waves (lower = slower)")]
[SerializeField, Min(0f)] private float _swellSpeed = 0.05f;
private static readonly int WeightId = Shader.PropertyToID("_Weight");
private static readonly int SwellId = Shader.PropertyToID("_Swell");
private LiminalPerceptionPass _pass;
private Material _instance;
private float _weight;
private Coroutine _fade;
private float _seed;
private void OnEnable()
{
if (!_material)
return;
_instance = new Material(_material) { hideFlags = HideFlags.HideAndDontSave };
_pass = new LiminalPerceptionPass(_instance);
_weight = _initialWeight;
_seed = Random.Range(0f, 100f);
RenderPipelineManager.beginCameraRendering += OnBeginCamera;
}
private void OnDisable()
{
RenderPipelineManager.beginCameraRendering -= OnBeginCamera;
if (_instance)
Destroy(_instance);
_instance = null;
_pass = null;
}
public void FadeIn() => FadeTo(1f);
public void FadeOut() => FadeTo(0f);
public void FadeTo(float weight)
{
if (_fade != null)
StopCoroutine(_fade);
_fade = StartCoroutine(Fade(Mathf.Clamp01(weight)));
}
private IEnumerator Fade(float target)
{
float start = _weight;
for (float t = 0f; t < _fadeDuration; t += Time.deltaTime)
{
_weight = Mathf.Lerp(start, target, Mathf.SmoothStep(0f, 1f, t / _fadeDuration));
yield return null;
}
_weight = target;
_fade = null;
}
private void Update()
{
if (!_instance)
return;
// Irregular waves: mostly calm, sometimes a slow swell lasting several seconds
float noise = Mathf.PerlinNoise(Time.time * _swellSpeed, _seed);
float swell = Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.35f, 0.75f, noise));
_instance.SetFloat(WeightId, _weight);
_instance.SetFloat(SwellId, swell);
}
private void OnBeginCamera(ScriptableRenderContext context, Camera camera)
{
if (_weight <= 0.001f || camera.cameraType != CameraType.Game || !camera.CompareTag("MainCamera"))
return;
camera.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(_pass);
}
private class LiminalPerceptionPass : ScriptableRenderPass
{
private readonly Material _material;
public LiminalPerceptionPass(Material material)
{
_material = material;
renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
requiresIntermediateTexture = true;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
UniversalResourceData resources = frameData.Get<UniversalResourceData>();
if (resources.isActiveTargetBackBuffer)
return;
TextureHandle source = resources.activeColorTexture;
TextureDesc desc = renderGraph.GetTextureDesc(source);
desc.name = "_LiminalPerception";
desc.clearBuffer = false;
TextureHandle destination = renderGraph.CreateTexture(desc);
renderGraph.AddBlitPass(new RenderGraphUtils.BlitMaterialParameters(source, destination, _material, 0), "Liminal Perception");
resources.cameraColor = destination;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 85f89d5f3330fe647a701bd5ef79a013
@@ -0,0 +1,47 @@
using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>
/// Smoothly fades a post-process Volume's weight in or out (e.g. from UnityEvents when the player
/// changes area), instead of switching the look abruptly.
/// </summary>
[RequireComponent(typeof(Volume))]
public class VolumeWeightFader : MonoBehaviour
{
[SerializeField, Min(0f)] private float _duration = 1.5f;
private Volume _volume;
private Coroutine _fade;
private void Awake()
{
_volume = GetComponent<Volume>();
}
public void FadeIn() => FadeTo(1f);
public void FadeOut() => FadeTo(0f);
public void FadeTo(float weight)
{
if (_fade != null)
StopCoroutine(_fade);
_fade = StartCoroutine(Fade(Mathf.Clamp01(weight)));
}
private IEnumerator Fade(float target)
{
float start = _volume.weight;
for (float t = 0f; t < _duration; t += Time.deltaTime)
{
_volume.weight = Mathf.Lerp(start, target, Mathf.SmoothStep(0f, 1f, t / _duration));
yield return null;
}
_volume.weight = target;
_fade = null;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2dc622db6fcabf2458ff54530be2ef22
+53 -7
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
/// <summary>Reachable landmarks at the gaps between four repeated server clusters.</summary>
@@ -8,9 +9,17 @@ public sealed class PortalExitMarker : MonoBehaviour
{
[SerializeField] private Material exitMaterial;
[SerializeField] private float rotationY = 45;
[Tooltip("If set, every exit turns its face toward this point (e.g. where the player arrives) instead of using Rotation Y.")]
[SerializeField] private Transform faceTowards;
[SerializeField] private Vector3 dimensions = new Vector3(1.5f, 3.2f, 0.2f);
[Tooltip("Invoked once, in play mode, when the camera walks through one of the exits.")]
[SerializeField] private UnityEvent onPlayerEnter;
[Tooltip("Extra thickness (meters) on each side of an exit counted as walking through it.")]
[SerializeField, Min(0)] private float enterMargin = 0.35f;
private PortalMeadow meadow;
private readonly List<Transform> markers = new List<Transform>();
private readonly List<Vector3> lastLocalEye = new List<Vector3>();
private bool entered;
private PortalServerGrid grid;
private float period;
private Material runtimeMaterial;
@@ -27,6 +36,7 @@ public sealed class PortalExitMarker : MonoBehaviour
private void Update()
{
if (!meadow || !exitMaterial || !grid || !grid.enabled || grid.RepetitionPeriod <= 0) { Clear(); return; }
DetectPlayer();
if (!rebuild && markers.Count > 0 && initialCentre == meadow.Center && lastMaterial == exitMaterial && period == grid.RepetitionPeriod) return;
Clear(); rebuild = false; initialCentre = meadow.Center; lastMaterial = exitMaterial;
period = grid.RepetitionPeriod;
@@ -57,14 +67,49 @@ public sealed class PortalExitMarker : MonoBehaviour
}
private void LateUpdate()
{
if (!meadow || !meadow.Endless || period <= 0) return;
float poolPeriod = period * 3;
foreach (Transform marker in markers)
if (!meadow || period <= 0) return;
if (meadow.Endless)
{
Vector3 p = marker.localPosition;
p.x += Mathf.Floor((meadow.SurfaceCenter.x - p.x + poolPeriod * 0.5f) / poolPeriod) * poolPeriod;
p.z += Mathf.Floor((meadow.SurfaceCenter.z - p.z + poolPeriod * 0.5f) / poolPeriod) * poolPeriod;
marker.localPosition = p;
float poolPeriod = period * 3;
foreach (Transform marker in markers)
{
Vector3 p = marker.localPosition;
p.x += Mathf.Floor((meadow.SurfaceCenter.x - p.x + poolPeriod * 0.5f) / poolPeriod) * poolPeriod;
p.z += Mathf.Floor((meadow.SurfaceCenter.z - p.z + poolPeriod * 0.5f) / poolPeriod) * poolPeriod;
marker.localPosition = p;
}
}
if (faceTowards)
foreach (Transform marker in markers)
{
Vector3 toTarget = faceTowards.position - marker.position;
toTarget.y = 0;
if (toTarget.sqrMagnitude > 1e-4f) marker.rotation = Quaternion.LookRotation(toTarget);
}
}
// The exits are thin pooled cubes: the camera counts as entering when it is within an exit's
// width/height and either inside its (margin-padded) thickness or crossed its plane since last frame.
private void DetectPlayer()
{
if (!Application.isPlaying || entered || markers.Count == 0) return;
Camera cam = Camera.main;
if (!cam) return;
Vector3 eye = cam.transform.position;
while (lastLocalEye.Count < markers.Count) lastLocalEye.Add(new Vector3(float.PositiveInfinity, 0, 0));
for (int i = 0; i < markers.Count; i++)
{
Transform marker = markers[i];
if (!marker) continue;
Vector3 local = marker.InverseTransformPoint(eye);
Vector3 previous = lastLocalEye[i];
lastLocalEye[i] = local;
if (Mathf.Abs(local.x) > 0.5f || Mathf.Abs(local.y) > 0.5f) continue;
float halfThickness = 0.5f + enterMargin / Mathf.Max(0.01f, marker.localScale.z);
bool crossed = Mathf.Abs(previous.x) <= 0.5f && Mathf.Sign(previous.z) != Mathf.Sign(local.z);
if (Mathf.Abs(local.z) > halfThickness && !crossed) continue;
entered = true;
onPlayerEnter?.Invoke();
return;
}
}
private void Sync(ScriptableRenderContext context, Camera camera)
@@ -80,6 +125,7 @@ public sealed class PortalExitMarker : MonoBehaviour
foreach (Transform marker in markers)
if (marker) { marker.gameObject.SetActive(false); Release(marker.gameObject); }
markers.Clear();
lastLocalEye.Clear();
if (runtimeMaterial) Release(runtimeMaterial);
runtimeMaterial = null;
}