ComputeShader Small Example: Particle Fountain

This is an example of combining ComputeShader with Shader to realize particle fountain.
Calculate the position of fountain particles through ComputeShader, and draw them through Shader.

effect

principle

Generate a specified number of point data with random velocities, stack their initial velocities with gravity acceleration in ComputeShader, roughly calculate their positions according to the survival time, and then use Shader to draw them.

code

1.ComputeShader

 #pragma kernel PenQuan struct Particle { float3 position; Float3 velocity;//Zimiao haunt blog (azimiao. com) float3 startingVelocity; float lifetime; }; RWStructuredBuffer<Particle> particleBuffer; float deltaTime; [numthreads(256, 1, 1)] void PenQuan(uint3 id : SV_DispatchThreadID) { //Roughly calculate its displacement particleBuffer[id.x].position += particleBuffer[id.x].velocity * deltaTime; particleBuffer[id.x].velocity +=  float3(0,-10,0) * deltaTime; particleBuffer[id.x].lifetime += deltaTime;// Zimiao haunting blog (azimiao. com) if (particleBuffer[id.x].lifetime >= 5) { particleBuffer[id.x].lifetime = 0; particleBuffer[id.x].position.x = 0; particleBuffer[id.x].position.y = 0; particleBuffer[id.x].position.z = 0; particleBuffer[id.x].velocity = particleBuffer[id.x].startingVelocity; } }

2.Shader

 Shader "PenQuan" { Properties { _Tint ("Tint", Color) = (0, 0, 0.5, 0.3) _Randomness ("Random", Range(0.0, 1.0)) = 0.5 } SubShader { Pass { Blend SrcAlpha one CGPROGRAM #pragma target 5.0 #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct Particle { float3 position; float3 velocity; float3 startingVelocity; float lifetime; }; // Pixel shader input struct PS_INPUT { float4 position : SV_POSITION; float4 color : COLOR; }; //Shared buffer StructuredBuffer<Particle> particleBuffer; // Properties variables uniform float4 _Tint; uniform float _Randomness; // Vertex shader PS_INPUT vert(uint vertex_id : SV_VertexID,  uint instance_id : SV_InstanceID) { PS_INPUT o = (PS_INPUT)0; // Color float speed = length(particleBuffer[instance_id].velocity); //Modify the color according to the current speed float4 c = _Tint; c.r *= 1.0 - _Randomness; c.g *= 1.0 - _Randomness; c.b *= 1.0 - _Randomness; c.r += fmod(abs(particleBuffer[instance_id].velocity.x), _Randomness); c.g += fmod(abs(particleBuffer[instance_id].velocity.y), _Randomness); c.b += fmod(abs(particleBuffer[instance_id].velocity.z), _Randomness); c.a = 1; o.color = c; // Position o.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position, 1.0f)); return o; } // Pixel shader float4 frag(PS_INPUT i) : COLOR { return i.color; } ENDCG } } Fallback Off }

3.CSharp

 public class PenQuanTest : MonoBehaviour { private struct Particle { public Vector3 position; public Vector3 velocity; public Vector3 startingVelocity; public float lifetime; } public int particleCount = 10000; public Material material; public ComputeShader computeShader; //Structure size, used when initializing buffer private const int SIZE_PARTICLE = 40; private int mComputeShaderKernelID; //Keep references ComputeBuffer particleBuffer; private const int WARP_SIZE = 256; private int mWarpCount; private Particle[] particleArray; void Start() { if (particleCount <= 0) particleCount = 1; mWarpCount = Mathf.CeilToInt((float)particleCount / WARP_SIZE); particleArray = new Particle[particleCount]; //Initialize particle position and random velocity information for (int i = 0;  i < particleCount; ++i) { particleArray[i].position.x = 0; particleArray[i].position.y = 0; particleArray[i].position.z =0; Vector2 v = Random.insideUnitCircle * 1.5f; particleArray[i].velocity.x = v.x; particleArray[i].velocity.y = 10f; particleArray[i].velocity.z = v.y; particleArray[i].startingVelocity = particleArray[i].velocity; particleArray[i].lifetime = Random. Range(-5f, 5f); } particleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE); particleBuffer.SetData(particleArray); mComputeShaderKernelID = computeShader.FindKernel("PenQuan"); computeShader.SetBuffer(mComputeShaderKernelID, "particleBuffer", particleBuffer); material.SetBuffer("particleBuffer", particleBuffer); } void OnDestroy() { if (particleBuffer !=  null) particleBuffer.Release(); } // Update is called once per frame void Update() { computeShader.SetFloat("deltaTime", Time.deltaTime); computeShader.Dispatch(mComputeShaderKernelID, mWarpCount, 1, 1); } void OnRenderObject() { material.SetPass(0); Graphics.DrawProceduralNow(MeshTopology. Points, 1, particleCount); } }

References

In this paper, the following code has been modified to achieve normal operation and effect optimization.

  1. [Code information] [Github] lukakostic ComputeShaderExamples
Zimiao haunting blog (azimiao. com) All rights reserved. Please note the link when reprinting: https://www.azimiao.com/7650.html
Welcome to the Zimiao haunting blog exchange group: three hundred and thirteen million seven hundred and thirty-two thousand

Comment

*

*