// Global keywords defining environment settings shared between shaders // Would rather just #include_with_pragmas // but it's broken in compute due to a bug that has been fixed but not backported yet #pragma multi_compile __ FLOW_GLOBAL #pragma multi_compile __ FLOW_SLOPE #pragma multi_compile __ NOISE_SLOPE //Tell unity that we have defined a kernel #pragma kernel UpdateParticles #include "Assets/Shaders/Volumetric.cginc" //Store Particle data in a struct struct Particle { float3 position; float3 color; float size; }; RWStructuredBuffer particles; // Use this texture to choose the color of the particle Texture2D HueTexture; SamplerState samplerHueTexture; float period; float time; float spacing; float ceilingHeight; float floorHeight; float maximumSize; float minimumSize; float minHeadroom; float maxHeadroom; float heightExponent; uint span; //================ //Bonus: Random number generator //Using xorshift, and thread ID as a seed. If you only use xorshift, //there is too much correlation, so we first hash the seed id in RandomFirst() //See: http://www.reedbeta.com/blog/2013/01/12/quick-and-easy-gpu-random-numbers-in-d3d11/ uint rngState; #define UINT_MAXF 4294967290.0f float RandomFirst(uint a, uint b) { //wang hash the seed first to get rid of correlation. a = (a ^ 61) ^ (a >> 16); a *= 9; a = a ^ (a >> 4); a *= 0x27d4eb2d; a = a ^ (a >> 15); b -= (b<<6); b ^= (a>>17); b -= (b<<9); b ^= (a<<4); b -= (b<<3); b ^= (b<<10); b ^= (b>>15); rngState = a ^ b; return (float)rngState / UINT_MAXF; } float RandomValue() { // Xorshift algorithm from George Marsaglia's paper //You could use RandomFirst() all the time - it'd just be more expensive. rngState ^= (rngState << 13); rngState ^= (rngState >> 17); rngState ^= (rngState << 5); return (float) rngState / UINT_MAXF; } #define HASHSCALE 0.1031 float hash(float p) { float3 p3 = frac(p.xxx * HASHSCALE); p3 += dot(p3, p3.yzx + 19.19); return frac((p3.x + p3.y) * p3.z); } float fade(float t) { return t*t*t*(t*(6.*t-15.)+10.); } float grad(float hash, float p) { int i = int(1e4*hash); return (i & 1) == 0 ? p : -p; } float perlinNoise1D(float p) { float pi = floor(p), pf = p - pi, w = fade(pf); return lerp(grad(hash(pi), pf), grad(hash(pi + 1.0), pf - 1.0), w) * 2.0; } const float4 irrationals = float4(1.41421, 3.14159, 2.71828, 1.61803); float noise(float p) { return (sin(irrationals.x * p) + sin(irrationals.y * p) + sin(irrationals.z * p) + sin(irrationals.w * p))/4; } const uint offset = 65535; const float randomRange = 2048; //Since we only have a long list of particles, our thread group is just a flat block of threads in the x direction //128 threads in a group is a good amount for a couple of reasons [numthreads(128, 1, 1)] void UpdateParticles(uint3 id : SV_DispatchThreadID, int3 groupID : SV_GroupThreadID) { int x = id % span - span / 2; int y = id / span - span / 2; int xt = (int)(_GridTransform.x / spacing); int yt = (int)(_GridTransform.y / spacing); uint xp = x + xt + offset; uint yp = y + yt + offset; const float lifetime = frac(time / period + RandomFirst(xp, yp)); particles[id.x].position = float3( x * spacing + xt * spacing + perlinNoise1D(RandomValue()*1024)*spacing, 0, y * spacing + yt * spacing + perlinNoise1D(RandomValue()*1024)*spacing); const float2 puv = getUV(particles[id.x].position.xz); const float gravHeight = -tex2Dlod(_NebulaSurfaceHeight, float4(puv, 0, 0)).x + floorHeight; const float headroom = clamp(ceilingHeight - gravHeight, minHeadroom, maxHeadroom); particles[id.x].position.y = gravHeight + headroom * pow(RandomValue(), heightExponent) + perlinNoise1D(RandomValue()*1024)*spacing/2; const float3 flowVec = flow(particles[id.x].position); const float3 lighting = tex2Dlod(_NebulaTint, float4(puv.x, puv.y, 0, _DynamicLodHigh)).rgb + tex2Dlod(_NebulaTint, float4(puv.x, puv.y, 0, _DynamicLodLow)).rgb; particles[id.x].position -= flowVec * lifetime * period; //const float fade = smoothstep(0,ceilingHeight, particles[id.x].position.y); //const float d = pow(density(particles[id.x].position) + .01, 0.1); particles[id.x].size = lerp(minimumSize, maximumSize, saturate(RandomValue())) * parabola(lifetime, 2); particles[id.x].color = HueTexture.SampleLevel(samplerHueTexture, float2(RandomValue(), 0.0f), 0.0f).rgb * lighting; }