Opti + Add Theatre Credit + Post process Una
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user