opti
This commit is contained in:
@@ -25,7 +25,8 @@ public sealed class PortalMeadow : MonoBehaviour
|
||||
[SerializeField] private Vector2 bladeHeight = new Vector2(0.25f, 0.65f);
|
||||
[SerializeField, Range(0.01f, 0.15f)] private float bladeWidth = 0.055f;
|
||||
[SerializeField] private int seed = 1978;
|
||||
[Tooltip("Extra bounds in every direction to accommodate camera-relative shader bending.")]
|
||||
[Tooltip("Maximum extra bounds in every direction to accommodate camera-relative shader bending. " +
|
||||
"In Play mode the padding is reduced to the real bend of the visible disk, so renderers can be culled.")]
|
||||
[SerializeField, Min(1)] private float boundsPadding = 128;
|
||||
[Tooltip("Rendering layers of every generated PortalB renderer, so lights can be restricted to this world.")]
|
||||
[SerializeField] private RenderingLayerMask renderingLayers = 1u;
|
||||
@@ -41,6 +42,23 @@ public sealed class PortalMeadow : MonoBehaviour
|
||||
private static readonly int FrameFixedId = Shader.PropertyToID("_CurvedWorldFrameFixed");
|
||||
private float frameBlend;
|
||||
|
||||
// Culling with the real bent bounds (the padded renderer bounds are too large for Unity to cull anything):
|
||||
// before the crossing PortalB is only visible through the portal openings, then through the camera frustum.
|
||||
private Portal[] portals;
|
||||
private readonly List<Plane[]> portalFrusta = new List<Plane[]>();
|
||||
private readonly Plane[] cameraFrustum = new Plane[6];
|
||||
private int portalFrustaCount;
|
||||
private bool throughPortals;
|
||||
private int frustaFrame = -1;
|
||||
private Camera frustaCamera;
|
||||
private bool culling;
|
||||
private bool tilesCulled;
|
||||
private float cullCurvature, cullStart, cullHorizontal;
|
||||
private Vector2 cullAxis;
|
||||
private Vector3 cullBendDirection, cullFramePosition;
|
||||
private Quaternion cullFrameRotation;
|
||||
private readonly List<Bounds> tileLocalBounds = new List<Bounds>();
|
||||
|
||||
private readonly List<Mesh> meshes = new List<Mesh>();
|
||||
private GameObject generated;
|
||||
private bool rebuild;
|
||||
@@ -59,7 +77,7 @@ public sealed class PortalMeadow : MonoBehaviour
|
||||
public Vector2 Size => size;
|
||||
public Vector3 Center => center + (centerOnPlayerAtStart ? initialCenterOffset : Vector3.zero);
|
||||
public Material MeadowMaterial => material;
|
||||
public float BoundsPadding => boundsPadding;
|
||||
public float BoundsPadding => Application.isPlaying ? Mathf.Min(boundsPadding, MaxBendDisplacement() + 2) : boundsPadding;
|
||||
public uint RenderingLayers => renderingLayers;
|
||||
public float Radius => Mathf.Min(size.x, size.y) * 0.5f;
|
||||
public bool Endless => endless && Application.isPlaying;
|
||||
@@ -95,8 +113,129 @@ public sealed class PortalMeadow : MonoBehaviour
|
||||
|
||||
public void SetFollowPlayer(bool follow) { followPlayer = follow; }
|
||||
|
||||
/// <summary>Largest shader displacement inside the visible disk (the bend origin stays near its centre in Play mode).</summary>
|
||||
private float MaxBendDisplacement()
|
||||
{
|
||||
if (!material || !material.HasProperty("_Curvature")) return boundsPadding;
|
||||
// The disk plus the wrapped servers just past its edge.
|
||||
float reach = Radius + 8;
|
||||
float distance = Mathf.Max(0, reach - material.GetFloat("_StartDistance"));
|
||||
float horizontal = material.HasProperty("_HorizontalInfluence") ? material.GetFloat("_HorizontalInfluence") : 0;
|
||||
return (distance * distance + reach * reach * horizontal) * Mathf.Abs(material.GetFloat("_Curvature"));
|
||||
}
|
||||
|
||||
/// <summary>True when these (unbent) world bounds cannot be seen by the camera once bent by the shader:
|
||||
/// outside the portal openings before the crossing, outside the camera frustum afterwards.</summary>
|
||||
public bool IsCulled(Bounds worldBounds, Camera camera)
|
||||
{
|
||||
if (!UpdateCulling(camera)) return false;
|
||||
Bounds bent = BendBounds(worldBounds);
|
||||
if (!throughPortals) return !GeometryUtility.TestPlanesAABB(cameraFrustum, bent);
|
||||
for (int i = 0; i < portalFrustaCount; i++)
|
||||
if (GeometryUtility.TestPlanesAABB(portalFrusta[i], bent)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Whether this bent-bounds culling applies to this camera this frame (the main camera in Play mode).</summary>
|
||||
public bool Culls(Camera camera) { return UpdateCulling(camera); }
|
||||
|
||||
private bool UpdateCulling(Camera camera)
|
||||
{
|
||||
if (frustaFrame == Time.frameCount && frustaCamera == camera) return culling;
|
||||
frustaFrame = Time.frameCount;
|
||||
frustaCamera = camera;
|
||||
portalFrustaCount = 0;
|
||||
culling = Application.isPlaying && material && material.HasProperty("_Curvature") && camera && camera == Camera.main;
|
||||
if (!culling) return false;
|
||||
|
||||
// Same bend frame as the shaders: the anchor before the crossing, blending to the camera afterwards.
|
||||
Transform view = camera.transform;
|
||||
float blend = bendAnchor ? frameBlend : 1;
|
||||
cullFramePosition = bendAnchor ? Vector3.Lerp(bendAnchor.position, view.position, blend) : view.position;
|
||||
cullFrameRotation = bendAnchor ? Quaternion.Slerp(bendAnchor.rotation, view.rotation, blend) : view.rotation;
|
||||
cullCurvature = Mathf.Abs(material.GetFloat("_Curvature"));
|
||||
cullStart = material.GetFloat("_StartDistance");
|
||||
cullHorizontal = material.HasProperty("_HorizontalInfluence") ? material.GetFloat("_HorizontalInfluence") : 0;
|
||||
float angle = material.GetFloat("_BendAngle") * Mathf.Deg2Rad;
|
||||
cullAxis = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
|
||||
cullBendDirection = cullFrameRotation * new Vector3(Mathf.Sin(angle), -Mathf.Cos(angle), 0);
|
||||
|
||||
throughPortals = !Following && bendAnchor;
|
||||
if (!throughPortals)
|
||||
{
|
||||
GeometryUtility.CalculateFrustumPlanes(camera, cameraFrustum);
|
||||
return true;
|
||||
}
|
||||
if (portals == null) portals = FindObjectsByType<Portal>(FindObjectsSortMode.None);
|
||||
Vector3 eye = camera.transform.position;
|
||||
foreach (Portal portal in portals)
|
||||
{
|
||||
if (!portal || !portal.isActiveAndEnabled) continue;
|
||||
Transform t = portal.transform;
|
||||
// A portal opens onto PortalB along its forward axis, only for a camera in front of it.
|
||||
if (Vector3.Dot(t.position - eye, t.forward) <= 0) continue;
|
||||
if (portalFrustaCount == portalFrusta.Count) portalFrusta.Add(new Plane[5]);
|
||||
Plane[] planes = portalFrusta[portalFrustaCount++];
|
||||
Vector3 right = t.right * (t.localScale.x * 0.5f), up = t.up * (t.localScale.y * 0.5f);
|
||||
Vector3 inside = t.position + (t.position - eye).normalized;
|
||||
Vector3 a = t.position - right - up, b = t.position - right + up, c = t.position + right + up, d = t.position + right - up;
|
||||
planes[0] = SidePlane(eye, a, b, inside);
|
||||
planes[1] = SidePlane(eye, b, c, inside);
|
||||
planes[2] = SidePlane(eye, c, d, inside);
|
||||
planes[3] = SidePlane(eye, d, a, inside);
|
||||
planes[4] = new Plane(t.forward, t.position);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Plane SidePlane(Vector3 eye, Vector3 a, Vector3 b, Vector3 inside)
|
||||
{
|
||||
var plane = new Plane(eye, a, b);
|
||||
if (!plane.GetSide(inside)) plane.Flip();
|
||||
return plane;
|
||||
}
|
||||
|
||||
/// <summary>World bounds grown by the bend (convex, so its maximum is on a corner) and the wind.</summary>
|
||||
private Bounds BendBounds(Bounds bounds)
|
||||
{
|
||||
bounds.Expand(1);
|
||||
Quaternion toFrame = Quaternion.Inverse(cullFrameRotation);
|
||||
Vector3 origin = cullFramePosition;
|
||||
float maxBend = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3 corner = bounds.center + Vector3.Scale(bounds.extents,
|
||||
new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1));
|
||||
Vector3 local = toFrame * (corner - origin);
|
||||
float distance = Mathf.Max(0, local.z - cullStart);
|
||||
float lateral = local.x * cullAxis.x + local.y * cullAxis.y;
|
||||
maxBend = Mathf.Max(maxBend, (distance * distance + lateral * lateral * cullHorizontal) * cullCurvature);
|
||||
}
|
||||
Vector3 offset = cullBendDirection * maxBend;
|
||||
bounds.Encapsulate(bounds.min + offset);
|
||||
bounds.Encapsulate(bounds.max + offset);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private void CullTiles(Camera camera)
|
||||
{
|
||||
if (camera != Camera.main) return;
|
||||
bool cull = Culls(camera);
|
||||
if (!cull && !tilesCulled) return;
|
||||
tilesCulled = cull;
|
||||
for (int i = 0; i < tileRenderers.Count; i++)
|
||||
{
|
||||
if (!tileRenderers[i].enabled) continue;
|
||||
Transform tile = tiles[i];
|
||||
Bounds local = tileLocalBounds[i];
|
||||
var world = new Bounds(tile.TransformPoint(local.center), Vector3.Scale(local.size, tile.lossyScale));
|
||||
tileRenderers[i].forceRenderingOff = cull && IsCulled(world, camera);
|
||||
}
|
||||
}
|
||||
|
||||
private void BeforeCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
CullTiles(camera);
|
||||
if (!bendAnchor || frameBlend >= 1) { Shader.SetGlobalFloat(FrameFixedId, 0); return; }
|
||||
Transform view = camera.transform;
|
||||
Vector3 position = Vector3.Lerp(bendAnchor.position, view.position, frameBlend);
|
||||
@@ -208,6 +347,9 @@ public sealed class PortalMeadow : MonoBehaviour
|
||||
meshes.Clear();
|
||||
tiles.Clear();
|
||||
tileRenderers.Clear();
|
||||
tileLocalBounds.Clear();
|
||||
tilesCulled = false;
|
||||
portals = null;
|
||||
if (renderedMaterial) Release(renderedMaterial);
|
||||
renderedMaterial = null;
|
||||
}
|
||||
@@ -297,7 +439,8 @@ public sealed class PortalMeadow : MonoBehaviour
|
||||
mesh.SetColors(colors); mesh.SetTriangles(triangles, 0);
|
||||
mesh.RecalculateBounds();
|
||||
Bounds bounds = mesh.bounds;
|
||||
bounds.Expand(boundsPadding * 2);
|
||||
tileLocalBounds.Add(bounds);
|
||||
bounds.Expand(BoundsPadding * 2);
|
||||
mesh.bounds = bounds;
|
||||
meshes.Add(mesh);
|
||||
var tile = new GameObject($"Meadow {tx},{tz}") { hideFlags = HideFlags.DontSave, layer = gameObject.layer };
|
||||
|
||||
@@ -34,6 +34,9 @@ public sealed class PortalServerGrid : MonoBehaviour
|
||||
private readonly List<Transform> servers = new List<Transform>();
|
||||
private readonly List<Collider> colliders = new List<Collider>();
|
||||
private bool collidersEnabled = true;
|
||||
private readonly List<Renderer> serverRenderers = new List<Renderer>();
|
||||
private readonly List<Bounds> serverLocalBounds = new List<Bounds>();
|
||||
private bool serversCulled;
|
||||
private float wrapExtent;
|
||||
// Distance between the centres of two repeated server clusters.
|
||||
public float RepetitionPeriod => wrapExtent * 2;
|
||||
@@ -48,7 +51,31 @@ public sealed class PortalServerGrid : MonoBehaviour
|
||||
private void BeforeCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
// Match the terrain's current material, including changes made after Update.
|
||||
if (meadow) SyncBend();
|
||||
if (!meadow) return;
|
||||
SyncBend();
|
||||
CullServers(camera);
|
||||
}
|
||||
private void CullServers(Camera camera)
|
||||
{
|
||||
if (camera != Camera.main) return;
|
||||
bool cull = meadow.Culls(camera);
|
||||
if (!cull && !serversCulled) return;
|
||||
serversCulled = cull;
|
||||
for (int i = 0; i < serverRenderers.Count; i++)
|
||||
{
|
||||
Renderer renderer = serverRenderers[i];
|
||||
if (!renderer) continue;
|
||||
renderer.forceRenderingOff = cull && meadow.IsCulled(WorldBounds(renderer, serverLocalBounds[i]), camera);
|
||||
}
|
||||
}
|
||||
private static Bounds WorldBounds(Renderer renderer, Bounds local)
|
||||
{
|
||||
Matrix4x4 matrix = renderer.localToWorldMatrix;
|
||||
var bounds = new Bounds(matrix.MultiplyPoint3x4(local.center), Vector3.zero);
|
||||
for (int i = 0; i < 8; i++)
|
||||
bounds.Encapsulate(matrix.MultiplyPoint3x4(local.center + Vector3.Scale(local.extents,
|
||||
new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1))));
|
||||
return bounds;
|
||||
}
|
||||
private void OnValidate()
|
||||
{
|
||||
@@ -115,6 +142,9 @@ public sealed class PortalServerGrid : MonoBehaviour
|
||||
curvedMaterials.Clear();
|
||||
servers.Clear();
|
||||
colliders.Clear();
|
||||
serverRenderers.Clear();
|
||||
serverLocalBounds.Clear();
|
||||
serversCulled = false;
|
||||
}
|
||||
private GameObject CreateServer()
|
||||
{
|
||||
@@ -224,6 +254,8 @@ public sealed class PortalServerGrid : MonoBehaviour
|
||||
renderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
renderer.allowOcclusionWhenDynamic = false;
|
||||
Bounds expanded = renderer.localBounds;
|
||||
serverRenderers.Add(renderer);
|
||||
serverLocalBounds.Add(expanded);
|
||||
Vector3 scale = renderer.transform.lossyScale;
|
||||
float minScale = Mathf.Max(0.001f, Mathf.Min(Mathf.Abs(scale.x), Mathf.Abs(scale.y), Mathf.Abs(scale.z)));
|
||||
expanded.Expand(meadow.BoundsPadding * 2 / minScale);
|
||||
|
||||
Reference in New Issue
Block a user