107 lines
5.0 KiB
C#
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);
|
|
}
|
|
}
|