#pragma kernel CSParticle | |
// Particle's data | |
struct Particle | |
{ | |
float3 position; | |
float3 velocity; | |
float life; | |
}; | |
// Particle's data, shared with the shader | |
RWStructuredBuffer<Particle> particleBuffer; | |
// Variables set from the CPU | |
float deltaTime; | |
float2 mousePosition; | |
float nrand(float2 uv) | |
{ | |
return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453); | |
} | |
uint rng_state; | |
uint rand_xorshift() | |
{ | |
// Xorshift algorithm from George Marsaglia's paper | |
rng_state ^= (rng_state << 13); | |
rng_state ^= (rng_state >> 17); | |
rng_state ^= (rng_state << 5); | |
return rng_state; | |
} | |
[numthreads(256, 1, 1)] | |
void CSParticle(uint3 id : SV_DispatchThreadID) | |
{ | |
// subtract the life based on deltaTime | |
particleBuffer[id.x].life -= deltaTime; | |
float3 delta = float3(mousePosition.xy, 3) - particleBuffer[id.x].position; | |
float3 dir = normalize(delta); | |
particleBuffer[id.x].velocity += dir; | |
particleBuffer[id.x].position += particleBuffer[id.x].velocity * deltaTime; | |
if (particleBuffer[id.x].life < 0) | |
{ | |
// http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/ | |
rng_state = id.x; | |
float f0 = float(rand_xorshift()) * (1.0 / 4294967296.0) - 0.5; | |
float f1 = float(rand_xorshift()) * (1.0 / 4294967296.0) - 0.5; | |
float f2 = float(rand_xorshift()) * (1.0 / 4294967296.0) - 0.5; | |
float3 normalF3 = normalize(float3(f0, f1, f2)) * 0.8f; | |
normalF3 *= float(rand_xorshift()) * (1.0 / 4294967296.0); | |
particleBuffer[id.x].position = float3(normalF3.x + mousePosition.x, normalF3.y + mousePosition.y, normalF3.z + 3.0); | |
// reset the life of this particle | |
particleBuffer[id.x].life = 4; | |
particleBuffer[id.x].velocity = float3(0, 0,0); | |
} | |
} |
Shader "Custom/Particle" { | |
SubShader { | |
Pass { | |
Tags{ "RenderType" = "Opaque" } | |
LOD 200 | |
Blend SrcAlpha one | |
CGPROGRAM | |
// Physically based Standard lighting model, and enable shadows on all light types | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
// Use shader model 3.0 target, to get nicer looking lighting | |
#pragma target 5.0 | |
struct Particle{ | |
float3 position; | |
float3 velocity; | |
float life; | |
}; | |
struct PS_INPUT{ | |
float4 position : SV_POSITION; | |
float4 color : COLOR; | |
float life : LIFE; | |
}; | |
// particles' data | |
StructuredBuffer<Particle> particleBuffer; | |
PS_INPUT vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID) | |
{ | |
PS_INPUT o = (PS_INPUT)0; | |
// Color | |
float life = particleBuffer[instance_id].life; | |
float lerpVal = life * 0.25f; | |
o.color = fixed4(1.0f - lerpVal+0.1, lerpVal+0.1, 1.0f, lerpVal); | |
// Position | |
o.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position, 1.0f)); | |
return o; | |
} | |
float4 frag(PS_INPUT i) : COLOR | |
{ | |
return i.color; | |
} | |
ENDCG | |
} | |
} | |
FallBack Off | |
} |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class RunCompute : MonoBehaviour { | |
private Vector2 cursorPos; | |
// struct | |
struct Particle | |
{ | |
public Vector3 position; | |
public Vector3 velocity; | |
public float life; | |
} | |
/// <summary> | |
/// Size in octet of the Particle struct. | |
/// since float = 4 bytes... | |
/// 4 floats = 16 bytes | |
/// </summary> | |
//private const int SIZE_PARTICLE = 24; | |
private const int SIZE_PARTICLE = 28; // since property "life" is added... | |
/// <summary> | |
/// Number of Particle created in the system. | |
/// </summary> | |
private int particleCount = 1000000; | |
/// <summary> | |
/// Material used to draw the Particle on screen. | |
/// </summary> | |
public Material material; | |
/// <summary> | |
/// Compute shader used to update the Particles. | |
/// </summary> | |
public ComputeShader computeShader; | |
/// <summary> | |
/// Id of the kernel used. | |
/// </summary> | |
private int mComputeShaderKernelID; | |
/// <summary> | |
/// Buffer holding the Particles. | |
/// </summary> | |
ComputeBuffer particleBuffer; | |
/// <summary> | |
/// Number of particle per warp. | |
/// </summary> | |
private const int WARP_SIZE = 256; // TODO? | |
/// <summary> | |
/// Number of warp needed. | |
/// </summary> | |
private int mWarpCount; // TODO? | |
//public ComputeShader shader; | |
// Use this for initialization | |
void Start () { | |
InitComputeShader(); | |
} | |
void InitComputeShader() | |
{ | |
mWarpCount = Mathf.CeilToInt((float)particleCount / WARP_SIZE); | |
// initialize the particles | |
Particle[] particleArray = new Particle[particleCount]; | |
for (int i = 0; i < particleCount; i++) | |
{ | |
float x = Random.value * 2 - 1.0f; | |
float y = Random.value * 2 - 1.0f; | |
float z = Random.value * 2 - 1.0f; | |
Vector3 xyz = new Vector3(x, y, z); | |
xyz.Normalize(); | |
xyz *= Random.value; | |
xyz *= 0.5f; | |
particleArray[i].position.x = xyz.x; | |
particleArray[i].position.y = xyz.y; | |
particleArray[i].position.z = xyz.z + 3; | |
particleArray[i].velocity.x = 0; | |
particleArray[i].velocity.y = 0; | |
particleArray[i].velocity.z = 0; | |
// Initial life value | |
particleArray[i].life = Random.value * 5.0f + 1.0f; | |
} | |
// create compute buffer | |
particleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE); | |
particleBuffer.SetData(particleArray); | |
// find the id of the kernel | |
mComputeShaderKernelID = computeShader.FindKernel("CSParticle"); | |
// bind the compute buffer to the shader and the compute shader | |
computeShader.SetBuffer(mComputeShaderKernelID, "particleBuffer", particleBuffer); | |
material.SetBuffer("particleBuffer", particleBuffer); | |
} | |
void OnRenderObject() | |
{ | |
material.SetPass(0); | |
Graphics.DrawProcedural(MeshTopology.Points, 1, particleCount); | |
} | |
void OnDestroy() | |
{ | |
if (particleBuffer != null) | |
particleBuffer.Release(); | |
} | |
// Update is called once per frame | |
void Update () { | |
float[] mousePosition2D = { cursorPos.x, cursorPos.y }; | |
// Send datas to the compute shader | |
computeShader.SetFloat("deltaTime", Time.deltaTime); | |
computeShader.SetFloats("mousePosition", mousePosition2D); | |
// Update the Particles | |
computeShader.Dispatch(mComputeShaderKernelID, mWarpCount, 1, 1); | |
} | |
void OnGUI() | |
{ | |
Vector3 p = new Vector3(); | |
Camera c = Camera.main; | |
Event e = Event.current; | |
Vector2 mousePos = new Vector2(); | |
// Get the mouse position from Event. | |
// Note that the y position from Event is inverted. | |
mousePos.x = e.mousePosition.x; | |
mousePos.y = c.pixelHeight - e.mousePosition.y; | |
p = c.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, c.nearClipPlane + 14));// z = 3. | |
cursorPos.x = p.x; | |
cursorPos.y = p.y; | |
/* | |
GUILayout.BeginArea(new Rect(20, 20, 250, 120)); | |
GUILayout.Label("Screen pixels: " + c.pixelWidth + ":" + c.pixelHeight); | |
GUILayout.Label("Mouse position: " + mousePos); | |
GUILayout.Label("World position: " + p.ToString("F3")); | |
GUILayout.EndArea(); | |
*/ | |
} | |
} |
// https://github.com/keijiro/NoiseShader | |
#include "HLSL/SimplexNoise3D.hlsl" | |
#pragma kernel CSParticle | |
// Particle's data | |
struct Particle | |
{ | |
float3 position; | |
float3 velocity; | |
float life; | |
}; | |
// Particle's data, shared with the shader | |
RWStructuredBuffer<Particle> particleBuffer; | |
// Variables set from the CPU | |
float deltaTime; | |
float2 mousePosition; | |
float nrand(float2 uv) | |
{ | |
return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453); | |
} | |
uint rng_state; | |
uint rand_xorshift() | |
{ | |
// Xorshift algorithm from George Marsaglia's paper | |
rng_state ^= (rng_state << 13); | |
rng_state ^= (rng_state >> 17); | |
rng_state ^= (rng_state << 5); | |
return rng_state; | |
} | |
// https://github.com/cabbibo/glsl-curl-noise/blob/master/curl.glsl | |
float3 snoiseVec3(float3 x) { | |
float s = snoise(x); | |
float s1 = snoise(float3(x.y - 19.1, x.z + 33.4, x.x + 47.2)); | |
float s2 = snoise(float3(x.z + 74.2, x.x - 124.5, x.y + 99.4)); | |
float3 c = float3(s, s1, s2); | |
return c; | |
} | |
float3 curlNoise(float3 p) { | |
const float e = .1; | |
float3 dx = float3(e, 0.0, 0.0); | |
float3 dy = float3(0.0, e, 0.0); | |
float3 dz = float3(0.0, 0.0, e); | |
float3 p_x0 = snoiseVec3(p - dx); | |
float3 p_x1 = snoiseVec3(p + dx); | |
float3 p_y0 = snoiseVec3(p - dy); | |
float3 p_y1 = snoiseVec3(p + dy); | |
float3 p_z0 = snoiseVec3(p - dz); | |
float3 p_z1 = snoiseVec3(p + dz); | |
float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y; | |
float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z; | |
float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x; | |
const float divisor = 1.0 / (2.0 * e); | |
return normalize(float3(x, y, z) * divisor); | |
} | |
[numthreads(256, 1, 1)] | |
void CSParticle(uint3 id : SV_DispatchThreadID) | |
{ | |
// subtract the life based on deltaTime | |
particleBuffer[id.x].life -= deltaTime; | |
particleBuffer[id.x].position += curlNoise(particleBuffer[id.x].position) * 0.1; | |
if (particleBuffer[id.x].life < 0) | |
{ | |
// http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/ | |
rng_state = id.x; | |
float f0 = float(rand_xorshift()) * (1.0 / 4294967296.0) - 0.5; | |
float f1 = float(rand_xorshift()) * (1.0 / 4294967296.0) - 0.5; | |
float f2 = float(rand_xorshift()) * (1.0 / 4294967296.0) - 0.5; | |
float3 normalF3 = normalize(float3(f0, f1, f2)) * 0.2f; | |
normalF3 *= float(rand_xorshift()) * (1.0 / 4294967296.0); | |
particleBuffer[id.x].position = float3(normalF3.x + mousePosition.x, normalF3.y + mousePosition.y, normalF3.z + 3.0); | |
// reset the life of this particle | |
particleBuffer[id.x].life = 5; | |
//particleBuffer[id.x].velocity = float3(0, 0, 0); | |
} | |
} |