Aetheria/Assets/Shaders/Compute/Stardust/Stardust.cs
Meta Vens ebce853aba Stardust draws at BeforeImageEffectsOpaque, before the nebula composite; scene verifier proves dust under fog is attenuated
The nebula is an [ImageEffectOpaque] OnRenderImage; a command buffer at BeforeForwardAlpha runs after its composite, so the
dust landed on top of the fog (B49). BeforeImageEffectsOpaque runs before it on the deferred camera. FlowFieldSceneVerify
now requires one Stardust buffer at the new event and none at BeforeForwardAlpha, and scene-stardust-under-fog compares the
dust's contribution with the cloud renderer off and on in an HDR target, with a fog control.

All commits build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:11:04 +02:00

174 lines
5.7 KiB
C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Rendering;
public class Stardust : MonoBehaviour
{
#region variables
//public bool debugLog = false;
//public float proxyPersistTime = 2;
public GameSettings Settings;
public Camera TargetCamera;
public ComputeShader ParticleCalculation;
public Material ParticleMaterial;
public int Span = 512;
public float Period = .25f;
public float MinimumSize = .1f;
public float MaximumSize = 2;
public float Spacing = 4.0f;
public float Ceiling = -250.0f;
public float Floor = -25.0f;
public float MinHeadroom = 25;
public float MaxHeadroom = 100;
public float HeightExponent = 4;
public RenderTexture NebulaSurfaceHeight;
public RenderTexture NebulaPatchHeight;
public RenderTexture NebulaPatch;
public RenderTexture NebulaTint;
public Texture Heightmap;
public Texture2D ParticleColors;
//public Transform TargetTransform;
private const int GROUP_SIZE = 128;
private int _updateParticlesKernel;
#endregion
#region Structs
//Notice that this struct has to match the one in the compute shader exactly.
struct Particle
{
public Vector3 Position;
public Vector3 Color; //Color is a float3 in the compute shader, we need a Vector3 to match that layout, not a Color!
public float Size;
};
#endregion
#region buffers
private ComputeBuffer _particlesBuffer;
private const int PARTICLE_STRIDE = 28;
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;
// Before the [ImageEffectOpaque] nebula, so its march fogs the dust; a deferred camera skips the forward-opaque events when nothing forward-opaque is drawn (B46, B49).
private const CameraEvent DrawEvent = CameraEvent.BeforeImageEffectsOpaque;
#endregion
#region setup
// Use this for initialization
void Start()
{
//Find compute kernel
_updateParticlesKernel = ParticleCalculation.FindKernel("UpdateParticles");
//Create particle buffer
_particlesBuffer = new ComputeBuffer(Span * Span, PARTICLE_STRIDE);
Particle[] particles = new Particle[Span * Span];
for (int i = 0; i < Span * Span; ++i)
{
particles[i].Position = Random.insideUnitSphere * 100;
particles[i].Color = Vector3.one; //white
particles[i].Size = Random.value;
}
_particlesBuffer.SetData(particles);
//Create quad buffer
_quadPoints = new ComputeBuffer(6, QUAD_STRIDE);
_quadPoints.SetData(new[] {
new Vector3(-.5f, .5f),
new Vector3(.5f, .5f),
new Vector3(.5f, -.5f),
new Vector3(.5f, -.5f),
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
#region Compute update
// Update is called once per frame
void Update()
{
//Bind resources to compute shader
ParticleCalculation.SetBuffer(_updateParticlesKernel, "particles", _particlesBuffer);
// 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));
ParticleCalculation.SetFloat("period", Period);
ParticleCalculation.SetFloat("spacing", Spacing);
ParticleCalculation.SetFloat("ceilingHeight", Ceiling);
ParticleCalculation.SetFloat("floorHeight", Floor);
ParticleCalculation.SetFloat("heightExponent", HeightExponent);
ParticleCalculation.SetFloat("maximumSize", MaximumSize);
ParticleCalculation.SetFloat("minimumSize", MinimumSize);
ParticleCalculation.SetFloat("minHeadroom", MinHeadroom);
ParticleCalculation.SetFloat("maxHeadroom", MaxHeadroom);
ParticleCalculation.SetInt("span", Span);
ParticleCalculation.SetTexture(_updateParticlesKernel, "HueTexture", ParticleColors);
//Dispatch, launch threads on GPU
int numberOfGroups = Mathf.CeilToInt((float)Span * Span / GROUP_SIZE);
ParticleCalculation.Dispatch(_updateParticlesKernel, numberOfGroups, 1, 1);
}
#endregion
#region cleanup
void OnDestroy()
{
// m_emitterProxies.Release();
// _temperature.Release();
DetachDraw();
_draw?.Release();
_draw = null;
if(_particlesBuffer!=null)
{
_particlesBuffer.Release();
_quadPoints.Release();
}
// m_livingProxyIndices.Release();
}
#endregion
}