Files
Ipseity-Demo/Assets/_Content/Scripts/PortalFogRings.cs
T
2026-09-28 14:58:25 +02:00

107 lines
5.0 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
/// <summary>Concentric curtains using Omnissiah's procedural fog and the meadow's bend.</summary>
[ExecuteAlways, DisallowMultipleComponent, RequireComponent(typeof(PortalMeadow)), DefaultExecutionOrder(215)]
public sealed class PortalFogRings : MonoBehaviour
{
[SerializeField] private Material fogMaterial;
[SerializeField, Range(1, 8)] private int layers = 4;
[SerializeField, Range(0.1f, 0.9f)] private float innerRadius = 0.35f;
[SerializeField, Range(0.2f, 1.2f)] private float outerRadius = 0.98f;
[SerializeField, Min(1)] private float height = 8;
[SerializeField] private float baseOffset = -1;
private PortalMeadow meadow;
private GameObject generated;
private Mesh mesh;
private readonly List<Transform> rings = new List<Transform>();
private readonly List<Material> materials = new List<Material>();
private Material lastMaterial;
private bool rebuild;
private void OnEnable()
{
meadow = GetComponent<PortalMeadow>(); rebuild = true;
RenderPipelineManager.beginCameraRendering += Sync;
}
private void OnValidate() { rebuild = true; }
private void LateUpdate()
{
if (!meadow || !fogMaterial) { Clear(); return; }
if (rebuild || !generated || lastMaterial != fogMaterial) Build();
generated.transform.localPosition = meadow.SurfaceCenter + Vector3.up * baseOffset;
for (int i = 0; i < rings.Count; i++)
{
float t = rings.Count == 1 ? 1 : (float)i / (rings.Count - 1);
float radius = meadow.Radius * Mathf.Lerp(innerRadius, Mathf.Max(innerRadius, outerRadius), t);
rings[i].localScale = new Vector3(radius, Mathf.Max(1, height) * Mathf.Lerp(0.65f, 1, t), radius);
var renderer = rings[i].GetComponent<MeshRenderer>();
Bounds bounds = mesh.bounds;
bounds.Expand(meadow.BoundsPadding * 2 / Mathf.Max(0.01f, Mathf.Min(radius, rings[i].localScale.y)));
renderer.localBounds = bounds;
}
}
private void Build()
{
Clear(); rebuild = false; lastMaterial = fogMaterial;
generated = new GameObject("Atmospheric Fog Rings (generated)") { hideFlags = HideFlags.DontSave };
generated.transform.SetParent(transform, false);
const int segments = 128, rows = 8;
var vertices = new Vector3[(segments + 1) * (rows + 1)];
var triangles = new int[segments * rows * 6];
int n = 0;
for (int y = 0; y <= rows; y++)
for (int x = 0; x <= segments; x++)
{
float angle = x * Mathf.PI * 2 / segments;
vertices[y * (segments + 1) + x] = new Vector3(Mathf.Sin(angle), (float)y / rows, Mathf.Cos(angle));
if (y == rows || x == segments) continue;
int a = y * (segments + 1) + x, b = a + segments + 1;
triangles[n++] = a; triangles[n++] = b; triangles[n++] = a + 1;
triangles[n++] = a + 1; triangles[n++] = b; triangles[n++] = b + 1;
}
mesh = new Mesh { name = "Fog cylinder curtain", hideFlags = HideFlags.DontSave };
mesh.vertices = vertices; mesh.triangles = triangles; mesh.RecalculateBounds();
for (int i = 0; i < Mathf.Clamp(layers, 1, 8); i++)
{
var ring = new GameObject("Fog layer " + (i + 1)) { hideFlags = HideFlags.DontSave };
ring.transform.SetParent(generated.transform, false);
ring.AddComponent<MeshFilter>().sharedMesh = mesh;
var mat = new Material(fogMaterial) { name = "PortalB Fog (runtime)", hideFlags = HideFlags.DontSave };
materials.Add(mat);
var renderer = ring.AddComponent<MeshRenderer>();
renderer.sharedMaterial = mat;
renderer.shadowCastingMode = ShadowCastingMode.Off;
renderer.receiveShadows = false;
renderer.allowOcclusionWhenDynamic = false;
rings.Add(ring.transform);
}
}
private void Sync(ScriptableRenderContext context, Camera camera)
{
if (!meadow || !meadow.MeadowMaterial || !fogMaterial) return;
for (int i = 0; i < materials.Count; i++)
{
var mat = materials[i];
mat.CopyPropertiesFromMaterial(fogMaterial);
// From outside to inside, after the sky dome. The player is inside all curtains.
mat.renderQueue = 3000 + materials.Count - 1 - i;
meadow.ApplyBend(mat);
meadow.ApplyPortalMask(mat);
}
}
private void OnDisable() { RenderPipelineManager.beginCameraRendering -= Sync; Clear(); }
private void Clear()
{
if (generated) { generated.SetActive(false); Release(generated); }
foreach (Material mat in materials) if (mat) Release(mat);
if (mesh) Release(mesh);
generated = null; mesh = null; materials.Clear(); rings.Clear();
}
private static void Release(Object obj)
{
if (Application.isPlaying) Destroy(obj); else DestroyImmediate(obj);
}
}