diff --git a/Assets/Scenes/ARPG.unity b/Assets/Scenes/ARPG.unity index 9591afb11..738110fbd 100644 --- a/Assets/Scenes/ARPG.unity +++ b/Assets/Scenes/ARPG.unity @@ -16461,7 +16461,6 @@ MonoBehaviour: NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2} Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2} ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3} - GravityCamera: {fileID: 1537342003} --- !u!114 &1009503736 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scenes/Main Menu.unity b/Assets/Scenes/Main Menu.unity index c67fdb7cc..4fafe8336 100644 --- a/Assets/Scenes/Main Menu.unity +++ b/Assets/Scenes/Main Menu.unity @@ -892,7 +892,6 @@ MonoBehaviour: NebulaTint: {fileID: 8400000, guid: d196505464047f04a97f28689041209b, type: 2} Heightmap: {fileID: 8400000, guid: 2fc21a9d3e9647948b56e384cd9378b3, type: 2} ParticleColors: {fileID: 2800000, guid: 56db63da398b2d04394ddbccedce943c, type: 3} - GravityCamera: {fileID: 2103364771} --- !u!114 &750375536 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Editor/FlowFieldSceneVerify.cs b/Assets/Scripts/Editor/FlowFieldSceneVerify.cs index 80d9a4e31..771cd9396 100644 --- a/Assets/Scripts/Editor/FlowFieldSceneVerify.cs +++ b/Assets/Scripts/Editor/FlowFieldSceneVerify.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Reflection; @@ -140,6 +140,8 @@ namespace Aetheria.EditorTools Call(volume, "Update"); if (Differing(flowing, Frame()) == 0) Fail("scene-stardust-run", "the particle buffer did not move while the zone ran"); if (Shader.GetGlobalFloat("_NebulaNoiseTime") == noiseTime) Fail("scene-noise-clock", "_NebulaNoiseTime did not move while the zone ran"); + + CheckStardustUncullable(dust, bufferField); } finally { @@ -147,6 +149,88 @@ namespace Aetheria.EditorTools } } + // The dust is a command buffer on the target camera, which no culling sees: with every particle 10000 below the + // camera (far past any box a hand could inflate round it) a render still shows dust, and shows none when the + // same particles have no size. + static void CheckStardustUncullable(Stardust dust, FieldInfo bufferField) + { + var camera = dust.TargetCamera; + if (camera == null) { Fail("scene-stardust-uncullable", "Stardust has no TargetCamera"); return; } + var attached = camera.GetCommandBuffers(CameraEvent.BeforeForwardAlpha).Count(buffer => buffer.name == "Stardust"); + if (attached != 1) Fail("scene-stardust-uncullable", $"{attached} Stardust command buffers on the target camera, expected 1"); + + var particles = (ComputeBuffer) bufferField.GetValue(dust); + var side = Mathf.RoundToInt(Mathf.Sqrt(particles.count)); + float[] Layout(float size, Vector3 centre, float below) + { + var data = new float[particles.count * 7]; + for (var i = 0; i < particles.count; i++) + { + data[i * 7 + 0] = centre.x + ((i % side) - side / 2f) * 4f; + data[i * 7 + 1] = centre.y - below; + data[i * 7 + 2] = centre.z + ((i / side) - side / 2f) * 4f; + data[i * 7 + 3] = data[i * 7 + 4] = data[i * 7 + 5] = 1f; + data[i * 7 + 6] = size; + } + return data; + } + + var transform = camera.transform; + var (position, rotation) = (transform.position, transform.rotation); + var (far, flags, background, mask, target) = (camera.farClipPlane, camera.clearFlags, camera.backgroundColor, camera.cullingMask, camera.targetTexture); + var texture = RenderTexture.GetTemporary(64, 64, 24, RenderTextureFormat.ARGB32); + var readback = new Texture2D(64, 64, TextureFormat.RGBA32, false); + var previous = RenderTexture.active; + // Image effects on the camera would write their own frame over the dust; only the dust draw is under test. + var effects = camera.GetComponents().Where(behaviour => behaviour.enabled).ToArray(); + Color32[] Render(float size, Vector3 centre, float below) + { + Call(dust, "Update"); // on a bounded draw this queues the draw for the camera; here it dispatches the compute + particles.SetData(Layout(size, centre, below)); + camera.Render(); + RenderTexture.active = texture; + readback.ReadPixels(new Rect(0, 0, 64, 64), 0, 0); + readback.Apply(); + return readback.GetPixels32(); + } + try + { + foreach (var effect in effects) effect.enabled = false; + camera.clearFlags = CameraClearFlags.SolidColor; + camera.backgroundColor = Color.black; + camera.cullingMask = 1; + camera.targetTexture = texture; + // control: the camera where the scene put it, dust 100 below, inside any box a hand-set extent would give + transform.SetPositionAndRotation(position, Quaternion.Euler(90f, 0f, 0f)); + camera.farClipPlane = 1000f; + var nearWith = Render(5f, position, 100f); var nearWithout = Render(0f, position, 100f); + var nearDiff = 0; for (var i = 0; i < nearWith.Length; i++) if (!nearWith[i].Equals(nearWithout[i])) nearDiff++; + Debug.Log($"{Tag} stardust near-control differing pixels={nearDiff}"); + if (nearDiff == 0) Fail("scene-stardust-uncullable", "control: dust 100 below the camera drew no pixels"); + transform.SetPositionAndRotation(new Vector3(0f, -40000f, 0f), Quaternion.Euler(90f, 0f, 0f)); + camera.farClipPlane = 30000f; + var withDust = Render(400f, transform.position, 10000f); // the material scales a quad by distance^.33, so at this range it needs this size to cover pixels + var without = Render(0f, transform.position, 10000f); + var differing = 0; + for (var i = 0; i < withDust.Length; i++) + if (!withDust[i].Equals(without[i])) differing++; + if (differing == 0) Fail("scene-stardust-uncullable", "the dust 10000 below the camera drew no pixels"); + } + finally + { + RenderTexture.active = previous; + foreach (var effect in effects) effect.enabled = true; + camera.targetTexture = target; + camera.cullingMask = mask; + camera.backgroundColor = background; + camera.clearFlags = flags; + camera.farClipPlane = far; + transform.SetPositionAndRotation(position, rotation); + RenderTexture.ReleaseTemporary(texture); + UnityEngine.Object.DestroyImmediate(readback); + } + } + // CloudShader's pass 0 compiles with the flow keywords (it must #include_with_pragmas the shared include). static void CheckCloudKeywords() { diff --git a/Assets/Shaders/Compute/Stardust/Stardust.cs b/Assets/Shaders/Compute/Stardust/Stardust.cs index a6636fd2e..8b8b45c22 100644 --- a/Assets/Shaders/Compute/Stardust/Stardust.cs +++ b/Assets/Shaders/Compute/Stardust/Stardust.cs @@ -34,7 +34,6 @@ public class Stardust : MonoBehaviour public Texture Heightmap; public Texture2D ParticleColors; //public Transform TargetTransform; - public Camera GravityCamera; private const int GROUP_SIZE = 128; private int _updateParticlesKernel; @@ -57,6 +56,11 @@ public class Stardust : MonoBehaviour private ComputeBuffer _quadPoints; private const int QUAD_STRIDE = 12; + // The dust is drawn by a command buffer on TargetCamera: a camera replays its buffers at their event and never culls them, + // so the draw needs no bounds and no extent can fall short of how deep the field is. + private CommandBuffer _draw; + private const CameraEvent DrawEvent = CameraEvent.BeforeForwardAlpha; + #endregion #region setup @@ -91,6 +95,33 @@ public class Stardust : MonoBehaviour new Vector3(-.5f, -.5f), new Vector3(-.5f, .5f) }); + + //The buffers never change after this, so the material is bound once + ParticleMaterial.SetBuffer("particles", _particlesBuffer); + ParticleMaterial.SetBuffer("quadPoints", _quadPoints); + + //Start may run again (the scene harness calls it by hand): drop the previous draw before building the next + DetachDraw(); + _draw?.Release(); + _draw = new CommandBuffer { name = "Stardust" }; + _draw.DrawProcedural(Matrix4x4.identity, ParticleMaterial, 0, MeshTopology.Triangles, 6, Span * Span); + AttachDraw(); + } + + void OnEnable() => AttachDraw(); + + void OnDisable() => DetachDraw(); + + void AttachDraw() + { + if (_draw != null && TargetCamera != null) + TargetCamera.AddCommandBuffer(DrawEvent, _draw); + } + + void DetachDraw() + { + if (_draw != null && TargetCamera != null) + TargetCamera.RemoveCommandBuffer(DrawEvent, _draw); } #endregion @@ -104,7 +135,6 @@ public class Stardust : MonoBehaviour // Particle lifetimes cycle on the flow clock (zone time), so the dust stops with the sim like everything the flow moves. ParticleCalculation.SetFloat("phase", FlowField.Cycle(VolumeSampling.FlowClock, Period)); - var pos = GravityCamera.transform.position; ParticleCalculation.SetFloat("period", Period); ParticleCalculation.SetFloat("spacing", Spacing); ParticleCalculation.SetFloat("ceilingHeight", Ceiling); @@ -121,38 +151,17 @@ public class Stardust : MonoBehaviour //Dispatch, launch threads on GPU int numberOfGroups = Mathf.CeilToInt((float)Span * Span / GROUP_SIZE); ParticleCalculation.Dispatch(_updateParticlesKernel, numberOfGroups, 1, 1); - - ParticleMaterial.SetBuffer("particles", _particlesBuffer); - ParticleMaterial.SetBuffer("quadPoints", _quadPoints); - Graphics.DrawProcedural(ParticleMaterial, new Bounds(pos, new Vector3(Spacing * Span, 2048, Spacing * Span)), MeshTopology.Triangles, 6, Span * Span, TargetCamera, null, ShadowCastingMode.Off, false, 0); } #endregion - #region rendering - // void OnRenderObject() - // { - // if (Camera.current == TargetCamera || Camera.current.name == "SceneCamera") - // { - // //Bind resources to material - // ParticleMaterial.SetBuffer("particles", _particlesBuffer); - // ParticleMaterial.SetBuffer("quadPoints", _quadPoints); - // // ParticleMaterial.SetFloat("velocityStretch", ParticleVelocityStretch); - // // ParticleMaterial.SetVector("velocity", PlayerVelocity); - // - // //Set the pass - // ParticleMaterial.SetPass(0); - // - // //Draw - // Graphics.DrawProceduralNow(MeshTopology.Triangles, 6, Span * Span); - // } - // } - #endregion - #region cleanup void OnDestroy() { // m_emitterProxies.Release(); // _temperature.Release(); + DetachDraw(); + _draw?.Release(); + _draw = null; if(_particlesBuffer!=null) { _particlesBuffer.Release();