using System.Collections; using System.Collections.Generic; using ComputeShaderUtility; using UnityEngine; using UnityEngine.Rendering; public struct Droplet { public Vector3 position; public Vector3 velocity; public uint enabled; public uint airborne; } public class BloodComputeShader : MonoBehaviour { public int numParticles = 1000; public ComputeShader bloodCompute; public Mesh mesh; public Material instancedMaterial; public float size; public float scoreMult = 1.0f; public int activeParticles = 0; public long score = 0; public float squeakVolume = 0.0f; public AudioSource squeakPlayer; public float splatterVolume = 0.0f; public AudioSource splatterPlayer; ComputeBuffer particleBuffer; ComputeBuffer positionBuffer; ComputeBuffer numParticlesConsumedBuffer; ComputeBuffer argsBuffer; ComputeBuffer freeParticleBuffer; const int InitDustKernel = 0; const int UpdateDustKernel = 1; AsyncGPUReadbackRequest readbackRequest; AsyncGPUReadbackRequest freeBloodReadRequest; Mop mop1; Mop mop2; public float CleanRadius = 2f; [SerializeField] uint bufferLookPointer = 0; // Start is called before the first frame update void Start() { var mops = FindObjectsByType(FindObjectsSortMode.None); mop1 = mops[0]; mop2 = mops[1]; ComputeHelper.CreateStructuredBuffer(ref particleBuffer, numParticles); ComputeHelper.CreateStructuredBuffer(ref positionBuffer, numParticles); Droplet[] particles = new Droplet[numParticles]; for (int i = 0; i < numParticles; i++) { particles[i] = new Droplet() { position = Vector3.zero, velocity = Vector3.zero, enabled = 0, airborne = 0 }; } particleBuffer.SetData(particles); bloodCompute.SetBuffer(UpdateDustKernel, "particles", particleBuffer); bloodCompute.SetBuffer(UpdateDustKernel, "positions", positionBuffer); // Init dust particle positions bloodCompute.SetBuffer(InitDustKernel, "particles", particleBuffer); bloodCompute.SetBuffer(InitDustKernel, "positions", positionBuffer); bloodCompute.SetInt("numParticles", numParticles); // Create args buffer uint[] args = new uint[5]; args[0] = (uint)mesh.GetIndexCount(0); args[1] = (uint)numParticles; args[2] = (uint)mesh.GetIndexStart(0); args[3] = (uint)mesh.GetBaseVertex(0); args[4] = 0; // offset argsBuffer = new ComputeBuffer(1, 5 * sizeof(uint), ComputeBufferType.IndirectArguments); argsBuffer.SetData(args); ComputeHelper.CreateStructuredBuffer(ref numParticlesConsumedBuffer, 2); numParticlesConsumedBuffer.SetData(new uint[] { 0, 0 }); bloodCompute.SetBuffer(UpdateDustKernel, "numParticlesConsumed", numParticlesConsumedBuffer); // Initialize with empty data ComputeHelper.CreateStructuredBuffer(ref freeParticleBuffer, numParticles); RequestAsyncReadback(); instancedMaterial.SetBuffer("positionBuffer", positionBuffer); } void RequestAsyncReadback() { readbackRequest = AsyncGPUReadback.Request(numParticlesConsumedBuffer); } void RequestAllBloodStates() { freeBloodReadRequest = AsyncGPUReadback.Request(freeParticleBuffer); } // Update is called once per frame void Update() { bloodCompute.SetFloat("deltaTime", Time.deltaTime); bloodCompute.SetInt("numParticles", numParticles); bloodCompute.SetFloat("size", size); bloodCompute.SetFloat("gravity", 9.8f); bloodCompute.SetVector("mop1Pos", mop1.transform.position); bloodCompute.SetVector("mop2Pos", mop2.transform.position); bloodCompute.SetFloat("CleanRadius", CleanRadius); if (readbackRequest.hasError) { RequestAllBloodStates(); Debug.Log("Async readback error"); return; } bool putBuffer = false; uint[] bufferData = new uint[] {0,0}; if (readbackRequest.done) { bufferData[0] = readbackRequest.GetData()[0]; bufferData[1] = readbackRequest.GetData()[1]; // Blood cleaned if (bufferData[0] > 0) { // Debug.Log("Cleaned " + bufferData[0]); activeParticles -= (int)bufferData[0]; score += (int)(bufferData[0] * scoreMult); squeakVolume += 0.1f; // Reset counter putBuffer = true; bufferData[0] = 0; } // Blood hitting the floor if (bufferData[1] > 0) { splatterVolume += bufferData[1]/25.0f; // Debug.Log("splat x" + bufferData[1]); putBuffer = true; bufferData[1] = 0; } RequestAsyncReadback(); } if (putBuffer) { numParticlesConsumedBuffer.SetData(bufferData); bloodCompute.SetBuffer(UpdateDustKernel, "numParticlesConsumed", numParticlesConsumedBuffer); } ComputeHelper.Dispatch(bloodCompute, numParticles, 1, 1, UpdateDustKernel); Graphics.DrawMeshInstancedIndirect(mesh, 0, instancedMaterial, new Bounds(Vector3.zero, Vector3.one * 1000), argsBuffer); splatterVolume *= 0.9f; splatterPlayer.volume = splatterVolume; squeakPlayer.volume = squeakVolume; squeakVolume *= 0.8f; if (splatterVolume < 0.001) splatterVolume = 0; if (squeakVolume < 0.001) squeakVolume = 0; } void FixedUpdate() { } public void createBlood(Vector3 wher, int muchies, float powah) { StartCoroutine(penisBlood(wher, muchies, powah)); } IEnumerator penisBlood(Vector3 loc, int amount, float power) { RequestAllBloodStates(); // Wait until we get the state of all the particles from the gpu while (!freeBloodReadRequest.done) { yield return new WaitForEndOfFrame(); } // Find N particles which are disabled uint i = bufferLookPointer; int found = 0; Droplet[] particles = new Droplet[numParticles]; var as_Particles = freeBloodReadRequest.GetData().ToArray(); int length = as_Particles.Length; // particleBuffer.GetData(particles); uint[] particleIndeces = new uint[amount]; // oof while (i < numParticles) { if (as_Particles[i % (length - 1)].enabled == 0) { // Found unused particle particleIndeces[found] = i; found++; if (found >= amount) break; } i++; } bufferLookPointer = (uint)((bufferLookPointer + amount) % numParticles); // Debug.Log(string.Join(", ", particleIndeces)); // send data to gpu freeParticleBuffer.SetData(particleIndeces); bloodCompute.SetBuffer(InitDustKernel, "freeParticles", freeParticleBuffer); // Test for race conditions // yield return new WaitForSeconds(1.0f); bloodCompute.SetFloat("particleVel", power); bloodCompute.SetVector("particleInitPos", loc); bloodCompute.SetInt("particlesToInitialize", found); ComputeHelper.Dispatch(bloodCompute, amount, 1, 1, InitDustKernel); activeParticles += found; yield return null; } void OnDestroy() { ComputeHelper.Release(particleBuffer, positionBuffer, argsBuffer, numParticlesConsumedBuffer, freeParticleBuffer); } public void createBloodTest(int amount) { createBlood(Vector3.zero, amount, 10.0f); } }