Aetheria/Assets/Shaders/Compute/Slime/Slime.compute
Reinier Vens 7a9af46820 Gravity Slime
Adjusted the "slime" particles to adjust their parameters according to the current gravitational potential and gradient!
2021-03-28 03:17:55 +01:00

377 lines
No EOL
11 KiB
Text

#include "SlimeVertex.cginc"
#include "UnityCG.cginc"
#define NUM_THREAD_X 64
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel UpdateAgents
#pragma kernel Diffuse
#pragma kernel CreateAgentParticles
#pragma kernel CreateParticleGeometry
// Struct for storing the current state of each simulated slime agent
struct Agent
{
float2 position;
float angle;
};
struct Particle
{
float3 start;
float3 middle;
float3 end;
float intensity;
};
struct SlimeParameter
{
float HeightSlopeLerp;
float HeightThreshold;
float HeightBlend;
float SlopeThreshold;
float SlopeBlend;
float HighValue;
float LowValue;
};
struct SlimeSettings
{
SlimeParameter Deposition;
SlimeParameter Diffusion;
SlimeParameter Decay;
SlimeParameter TurnSpeed;
SlimeParameter Speed;
SlimeParameter Drive;
SlimeParameter SensorDistance;
SlimeParameter SensorSpread;
SlimeParameter TrailDamping;
SlimeParameter Intensity;
};
float evaluate(SlimeParameter param, float height, float slope)
{
return lerp(
lerp(param.LowValue, param.HighValue, smoothstep(param.HeightThreshold - param.HeightBlend, param.HeightThreshold + param.HeightBlend, height)),
lerp(param.LowValue, param.HighValue, smoothstep(param.SlopeThreshold - param.SlopeBlend, param.SlopeThreshold + param.SlopeBlend, slope)), param.HeightSlopeLerp);
}
// Parameter buffer
RWStructuredBuffer<SlimeSettings> parameters;
// Accumulation Buffer
RWTexture2D<float> accumulation;
// Output of the diffusion kernel
RWTexture2D<float> diffusedAccumulation;
// Agent data buffer
RWStructuredBuffer<Agent> agents;
// Use this texture to sample level terrain shape for shenanigans
Texture2D Heightmap;
SamplerState samplerHeightmap;
int randomOffset;
int numAgents;
float deltaTime;
int textureWidth;
float zoneRadius;
// Hash function from H. Schechter & R. Bridson, goo.gl/RXiKaH
inline uint hash(uint s)
{
s ^= 2747636419u;
s *= 2654435769u;
s ^= s >> 16;
s *= 2654435769u;
s ^= s >> 16;
s *= 2654435769u;
return s;
}
inline float random(uint seed)
{
return float(hash(seed)) / 4294967295.0; // 2^32-1
}
inline float2 uvToPos(float2 uv)
{
return (uv - float2(.5, .5)) * (zoneRadius * 2);
}
inline float2 posToUv(float2 pos)
{
return pos / (zoneRadius * 2) + float2(.5, .5);
}
float sense(int2 position, float angle, float directionSign, float spread, float distance)
{
float sensorAngle = angle + directionSign * spread;
float2 sensorDir = float2(cos(sensorAngle), sin(sensorAngle));
int2 sensorCenter = position + sensorDir * distance;
float sum = 0;
for(int x = -1; x <= 1; x++)
{
for(int y = -1; y <= 1; y++)
{
int2 sample = sensorCenter + int2(x, y);
if(sample.x >= 0 && sample.x < textureWidth && sample.y >= 0 && sample.y < textureWidth)
{
sum += accumulation[sample];
}
}
}
return sum;
}
inline float unlerp(float x, float min, float max)
{
return (x - min) / (max - min);
}
// Z-up normal of the heightmap
float3 GetNormal(int2 pos)
{
if(pos.x >= textureWidth || pos.y >= textureWidth) return float3(0, 0, 1);
float e = -Heightmap[pos + int2(1, 0)];
float n = -Heightmap[pos + int2(0, 1)];
float me = -Heightmap[pos];
// Deduce terrain normal
float3 normal = float3(e - me, n - me, 1.0 / textureWidth * zoneRadius * 2);
return normalize(normal);
}
[numthreads(NUM_THREAD_X,1,1)]
void UpdateAgents (uint3 id : SV_DispatchThreadID)
{
if (id.x >= numAgents) return;
Agent agent = agents[id.x];
SlimeSettings settings = parameters[0];
float2 uv = posToUv(agent.position);
int2 texPos = int2(uv * textureWidth);
float height = -Heightmap[texPos];//.SampleLevel(samplerHeightmap, uv, 0);
float3 normal = GetNormal(texPos);
float slope = acos(dot(normal, float3(0,0,1))) / UNITY_HALF_PI;
// Move Agents based on direction and speed
float2 direction = float2(cos(agent.angle), sin(agent.angle));
agent.position += direction * evaluate(settings.Speed, height, slope) * deltaTime;
float r = random(id.x + randomOffset);
// Clamp position within bounds and randomize direction
if(dot(agent.position, agent.position) > zoneRadius * zoneRadius)
{
agent.position = normalize(agent.position) * zoneRadius;
//agent.position = float2(0,0);
agent.angle = r * UNITY_TWO_PI;
}
if(isnan(agent.position.x))
{
agent.position = float2(0,0);
agent.angle = r * UNITY_TWO_PI;
}
float spread = evaluate(settings.SensorSpread, height, slope);
float distance = evaluate(settings.SensorDistance, height, slope);
// Acquire sensory data
float forwardSensor = sense(texPos, agent.angle, 0, spread, distance);
float leftSensor = sense(texPos, agent.angle, -1, spread, distance);
float rightSensor = sense(texPos, agent.angle, 1, spread, distance);
// Determine the range of the sensor inputs
float minSensor = min(min(leftSensor, rightSensor), forwardSensor);
float maxSensor = max(max(max(leftSensor, rightSensor), forwardSensor), .01);
float range = min(maxSensor - minSensor, .01);
// Normalize sensor inputs
//forwardSensor = unlerp(forwardSensor, minSensor, maxSensor);
leftSensor = unlerp(leftSensor, minSensor, maxSensor);
rightSensor = unlerp(rightSensor, minSensor, maxSensor);
// Which direction it wants to turn in
float desire = leftSensor - rightSensor;
// Strength of its directional preference
float motivation = pow(range / maxSensor, 1 / evaluate(settings.Drive, height, slope));
agent.angle += evaluate(settings.TurnSpeed, height, slope) * lerp(r * 2 - 1, desire, motivation) * deltaTime;
agents[id.x] = agent;
accumulation[texPos] += evaluate(settings.Deposition, height, slope) * deltaTime;
}
[numthreads(8,8,1)]
void Diffuse (uint3 id : SV_DispatchThreadID)
{
if(id.x >= textureWidth || id.y >= textureWidth) return;
float previous = accumulation[id.xy];
SlimeSettings settings = parameters[0];
float height = -Heightmap[id.xy];
float3 normal = GetNormal(id.xy);
float slope = acos(dot(normal, float3(0,0,1))) / UNITY_HALF_PI;
float sum = 0;
for(int x = -1; x <= 1; x++)
{
for(int y=-1; y <= 1; y++)
{
int2 sample = int2(id.x+x, id.y+y);
if(sample.x >= 0 && sample.x < textureWidth && sample.y >= 0 && sample.y < textureWidth)
{
sum += accumulation[sample];
}
}
}
float average = sum / 9;
diffusedAccumulation[id.xy] = lerp(previous, average, saturate(evaluate(settings.Diffusion, height, slope) * deltaTime)) * saturate(1-evaluate(settings.Decay, height, slope) * deltaTime);
}
// Buffer containing the leading position of the agent particle
RWStructuredBuffer<Particle> particles;
// Output buffer for particle trail geometry
RWStructuredBuffer<Vertex> vertexBuffer;
// Perlin noise parameters
float noiseAmplitude;
float time;
float heightRange;
float heightOffset;
int particleCount;
#define HASHSCALE 0.1031
float hashp(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(hashp(pi), pf), grad(hashp(pi + 1.0), pf - 1.0), w) * 2.0;
}
[numthreads(NUM_THREAD_X,1,1)]
void CreateAgentParticles (uint3 id : SV_DispatchThreadID)
{
Agent agent = agents[id.x];
float2 uv = posToUv(agent.position);
int2 texPos = int2(uv * textureWidth);
SlimeSettings settings = parameters[0];
float height = -Heightmap[texPos];
float3 normal = GetNormal(texPos);
float slope = acos(dot(normal, float3(0,0,1))) / UNITY_HALF_PI;
float sequence = (float)id.x/particleCount;
//float height = -Heightmap.SampleLevel(samplerHeightmap, posToUv(agent.position), 0);
float particleHeight = height + pow(random(id.x)-.5, 3) * heightRange + heightOffset;
Particle particle = particles[id.x];
float damping = evaluate(settings.TrailDamping, height, pow(slope, .25));
float3 targetPosition = float3(
agent.position.x + perlinNoise1D(id.x*3 + time + sequence) * noiseAmplitude,
particleHeight + perlinNoise1D(id.x*3 + 1 + time + sequence) * noiseAmplitude,
agent.position.y + perlinNoise1D(id.x*3 + 2 + time + sequence) * noiseAmplitude);
if(length(particle.start-targetPosition) > 256)
{
particle.end = targetPosition;
particle.middle = targetPosition;
particle.start = targetPosition;
}
else
{
particle.end = lerp(particle.end, particle.middle, damping/4);
particle.middle = lerp(particle.middle, particle.start, damping/2);
particle.start = lerp(particle.start, targetPosition, damping);
}
particle.intensity = evaluate(settings.Intensity, height, slope);
particles[id.x] = particle;
}
// https://catlikecoding.com/unity/tutorials/curves-and-splines/
inline float3 QuadraticSplinePosition(float3 p0, float3 p1, float3 p2, float t)
{
//return lerp(lerp(p0,p1,t),lerp(p1,p2,t),t);
float oneMinusT = 1 - t;
return
oneMinusT * oneMinusT * p0 +
2 * oneMinusT * t * p1 +
t * t * p2;
}
inline float3 QuadraticSplineDerivative (float3 p0, float3 p1, float3 p2, float t) {
return
2 * (1 - t) * (p1 - p0) +
2 * t * (p2 - p1);
}
int trailPoints;
float trailStartWidth;
float trailEndWidth;
float3 cameraPos;
inline float3 calcToCameraDir(float3 pos)
{
return normalize(cameraPos - pos);
}
[numthreads(NUM_THREAD_X,1,1)]
void CreateParticleGeometry (uint3 id : SV_DispatchThreadID)
{
Vertex v0 = GetDefaultVertex();
Vertex v1 = GetDefaultVertex();
int idx = id.x;
int particleIndex = id.x / trailPoints;
int trailIndex = id.x % trailPoints;
float trailLerp = (float) trailIndex / trailPoints;
Particle particle = particles[particleIndex];
//float3 pos = float3(particle, 0, trailIndex);
//float3 dir = float3(0,0,1);
//float3 pos = lerp(particle.start, particle.end, trailLerp);
//float3 dir = normalize(particle.end-particle.start);
float3 pos = QuadraticSplinePosition(particle.start, particle.middle, particle.end, trailLerp);
float3 dir = QuadraticSplineDerivative(particle.start, particle.middle, particle.end, trailLerp);
float width = lerp(trailStartWidth, trailEndWidth, trailLerp);
float3 toCameraDir = calcToCameraDir(pos);
float3 right = normalize(cross(dir, toCameraDir)) * width * 0.5f;
v0.pos = pos - right;
v0.uv = float2(trailLerp, 0);
v0.intensity = particle.intensity;
v1.pos = pos + right;
v1.uv = float2(trailLerp, 1);
v1.intensity = particle.intensity;
uint vId = idx * 2;
vertexBuffer[vId] = v0;
vertexBuffer[vId+1] = v1;
}