Aetheria/Assets/Shaders/Compute/Slime/Slime.compute
Reinier Vens e3e9664a98 Microfauna Redux
Added more aesthetic trails to "slime" effect
Created prefabs for planet and asteroid stations
Added model for adrasteia ship
Integrated audio changes from Defirm
2021-05-25 22:01:59 +01:00

441 lines
No EOL
13 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 UpdateTrailPoints
#pragma kernel CreateTrailGeometry
// #pragma kernel CreateParticleGeometry
// Struct for storing the current state of each simulated slime agent
struct Agent
{
float2 position;
float angle;
};
struct Particle
{
float3 start;
float intensity;
};
struct SlimeParameter
{
float SlopeThreshold;
float SlopeBlend;
float HighValue;
float LowValue;
float Exponent;
};
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 slope)
{
return lerp(param.LowValue, param.HighValue, pow(smoothstep(param.SlopeThreshold - param.SlopeBlend, param.SlopeThreshold + param.SlopeBlend, slope), param.Exponent));
}
// Parameter buffer
RWStructuredBuffer<SlimeSettings> parameters;
// Accumulation Buffer
RWTexture2D<float> accumulation;
// Output of the diffusion kernel
RWTexture2D<float> diffusedAccumulation;
// Agent data buffer
RWStructuredBuffer<Agent> agents;
RWStructuredBuffer<float2> spawnPositions;
// Use this texture to sample level terrain shape for shenanigans
Texture2D Heightmap;
SamplerState samplerHeightmap;
int randomOffset;
int numAgents;
float deltaTime;
int textureSize;
int gravityTextureSize;
float zoneRadius;
int spawnPositionCount;
// 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;
RWStructuredBuffer<float3> previousTrailBuffer;
RWStructuredBuffer<float3> trailBuffer;
int trailPoints;
float trailStartWidth;
float trailEndWidth;
float3 cameraPos;
// 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)
{
const float sensorAngle = angle + directionSign * spread;
const float2 sensorDir = float2(cos(sensorAngle), sin(sensorAngle));
const int2 sensorCenter = position + sensorDir * distance;
float sum = 0;
for(int x = -1; x <= 1; x++)
{
for(int y = -1; y <= 1; y++)
{
const int2 sample = sensorCenter + int2(x, y);
if(sample.x >= 0 && sample.x < textureSize && sample.y >= 0 && sample.y < textureSize)
{
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 >= gravityTextureSize || pos.y >= gravityTextureSize) return float3(0, 0, 1);
const float e = -Heightmap[pos + int2(1, 0)];
const float n = -Heightmap[pos + int2(0, 1)];
const float me = -Heightmap[pos];
// Deduce terrain normal
const float3 normal = float3(e - me, n - me, 1.0 / gravityTextureSize * 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];
const SlimeSettings settings = parameters[0];
const float2 uv = posToUv(agent.position);
const int2 texPos = int2(uv * textureSize);
const int2 gravTexPos = int2(uv * gravityTextureSize);
const float3 normal = GetNormal(gravTexPos);
const float slope = acos(dot(normal, float3(0,0,1))) / UNITY_HALF_PI;
// Move Agents based on direction and speed
const float2 direction = float2(cos(agent.angle), sin(agent.angle));
agent.position += direction * evaluate(settings.Speed, slope) * deltaTime;
const float r = random(id.x + randomOffset);
// Clamp position within bounds and randomize direction
if(dot(agent.position, agent.position) > zoneRadius * zoneRadius || isnan(agent.position.x) || slope > .8)
{
agent.position = spawnPositions[id.x%spawnPositionCount];
agent.angle = r * UNITY_TWO_PI;
}
const float spread = evaluate(settings.SensorSpread, slope);
const float distance = evaluate(settings.SensorDistance, slope);
// Acquire sensory data
const 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
const float minSensor = min(min(leftSensor, rightSensor), forwardSensor);
const float maxSensor = max(max(max(leftSensor, rightSensor), forwardSensor), .01);
const 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
const float desire = leftSensor - rightSensor;
// Strength of its directional preference
const float motivation = pow(range / maxSensor, 1 / max(evaluate(settings.Drive, slope),.001));
agent.angle += evaluate(settings.TurnSpeed, slope) * lerp(r * 2 - 1, desire, motivation) * deltaTime;
agents[id.x] = agent;
accumulation[texPos] += evaluate(settings.Deposition, slope) * deltaTime;
}
[numthreads(8,8,1)]
void Diffuse (uint3 id : SV_DispatchThreadID)
{
if(id.x >= textureSize || id.y >= textureSize) return;
const float previous = accumulation[id.xy];
const SlimeSettings settings = parameters[0];
const float3 normal = GetNormal(id.xy * (gravityTextureSize / textureSize));
const 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++)
{
const int2 sample = int2(id.x+x, id.y+y);
if(sample.x >= 0 && sample.x < textureSize && sample.y >= 0 && sample.y < textureSize)
{
sum += accumulation[sample];
}
}
}
const float average = sum / 9;
diffusedAccumulation[id.xy] = lerp(previous, average, saturate(evaluate(settings.Diffusion, slope) * deltaTime)) * saturate(1-evaluate(settings.Decay, slope) * deltaTime);
}
#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)
{
return (int(1e4*hash) & 1) == 0 ? p : -p;
}
float perlinNoise1D(float p)
{
const float pi = floor(p), pf = p - pi;
return lerp(grad(hashp(pi), pf), grad(hashp(pi + 1.0), pf - 1.0), fade(pf)) * 2.0 - 1;
}
// fractal sum, range -1.0 - 1.0
float fBm(float p, int octaves)
{
float freq = 1, amp = .5;
float sum = 0;
for(int i = 0; i < octaves; i++)
{
if(i<4)
sum += (1-abs(perlinNoise1D(p * freq))) * amp;
else sum += abs(perlinNoise1D(p * freq)) * amp;
freq *= 2;
amp *= .75;
}
return (sum + 0)*noiseAmplitude;
}
[numthreads(NUM_THREAD_X,1,1)]
void CreateAgentParticles (uint3 id : SV_DispatchThreadID)
{
const Agent agent = agents[id.x];
const float2 uv = posToUv(agent.position);
const int2 gravTexPos = int2(uv * gravityTextureSize);
const float3 normal = GetNormal(gravTexPos);
const float height = -Heightmap[gravTexPos];
const float slope = acos(dot(normal, float3(0,0,1))) / UNITY_HALF_PI;
const SlimeSettings settings = parameters[0];
const float sequence = (float)id.x/particleCount;
//float height = -Heightmap.SampleLevel(samplerHeightmap, posToUv(agent.position), 0);
const float particleHeight = height + pow(random(id.x)-.5, 3) * heightRange + heightOffset;
Particle particle = particles[id.x];
const float damping = evaluate(settings.TrailDamping, slope);
const float3 targetPosition = float3(
agent.position.x + fBm(id.x*3 + time + sequence, 4),
particleHeight + fBm(id.x*3 + 1 + time + sequence, 4),
agent.position.y + fBm(id.x*3 + 2 + time + sequence, 4));
if(length(particle.start-targetPosition) > 32)
{
particle.start = targetPosition;
}
else
{
particle.start = lerp(particle.start, targetPosition, damping);
}
particle.intensity = evaluate(settings.Intensity, slope);
particles[id.x] = particle;
}
[numthreads(NUM_THREAD_X,1,1)]
void UpdateTrailPoints (uint3 id : SV_DispatchThreadID)
{
// give every trail position its own particle
const int idx = id.x;
const int particleIndex = idx / trailPoints;
const int trailIndex = idx % trailPoints;
const Particle particle = particles[particleIndex];
if(trailIndex==trailPoints-1)
{
trailBuffer[idx] = particle.start;
}
else trailBuffer[idx] = previousTrailBuffer[idx+1];
}
// 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);
const 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);
}
inline float3 calcToCameraDir(float3 pos)
{
return normalize(cameraPos - pos);
}
[numthreads(NUM_THREAD_X,1,1)]
void CreateTrailGeometry (uint3 id : SV_DispatchThreadID)
{
Vertex v0 = GetDefaultVertex();
Vertex v1 = GetDefaultVertex();
// give every trail position its own particle
const int idx = id.x;
const int particleIndex = id.x / trailPoints;
const int trailIndex = id.x % trailPoints;
float trailLerp = (float) trailIndex / trailPoints;
const Particle particle = particles[particleIndex];
float3 dirPrev = float3(0,0,0);
float3 dirNext = float3(0,0,0);
float3 pos = trailBuffer[idx];
if ( trailIndex!=0 )
dirPrev = normalize(pos - trailBuffer[idx-1]);
if( trailIndex < trailPoints-1 )
dirNext = normalize(trailBuffer[idx+1] - pos);
const float3 dir = normalize(dirPrev + dirNext);
const float width = lerp(trailStartWidth, trailEndWidth, trailLerp);
const float3 toCameraDir = calcToCameraDir(pos);
const float3 right = normalize(cross(dir, toCameraDir)) * width * 0.5f / particle.intensity;
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;
const uint vId = idx * 2;
vertexBuffer[vId] = v0;
vertexBuffer[vId+1] = v1;
}
// [numthreads(NUM_THREAD_X,1,1)]
// void CreateParticleGeometry (uint3 id : SV_DispatchThreadID)
// {
// Vertex v0 = GetDefaultVertex();
// Vertex v1 = GetDefaultVertex();
//
// // give every trail position its own particle
// const int idx = id.x;
// const int particleIndex = id.x / trailPoints;
// const int trailIndex = id.x % trailPoints;
// float trailLerp = (float) trailIndex / trailPoints;
//
// const Particle particle = particles[particleIndex];
//
// const float3 pos = QuadraticSplinePosition(particle.start, particle.middle, particle.end, trailLerp);
// const float3 dir = QuadraticSplineDerivative(particle.start, particle.middle, particle.end, trailLerp);
//
// const float width = lerp(trailStartWidth, trailEndWidth, trailLerp);
//
// const float3 toCameraDir = calcToCameraDir(pos);
// const float3 right = normalize(cross(dir, toCameraDir)) * width * 0.5f / particle.intensity;
//
// 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;
//
// const uint vId = idx * 2;
// vertexBuffer[vId] = v0;
// vertexBuffer[vId+1] = v1;
// }