Skip to content

Instantly share code, notes, and snippets.

@amygoodchild
Created January 31, 2020 17:53
Show Gist options
  • Save amygoodchild/d0a86b3b936183fe93c3f44f2ae55ce4 to your computer and use it in GitHub Desktop.
Save amygoodchild/d0a86b3b936183fe93c3f44f2ae55ce4 to your computer and use it in GitHub Desktop.
// Reset
#pragma kernel ResetTextureKernel
#pragma kernel ResetAgentsKernel
// Step
#pragma kernel MoveAgentsKernel
#pragma kernel WriteTrailsKernel
#pragma kernel DiffuseTextureKernel
// Render
#pragma kernel AgentsDebugKernel
#pragma kernel RenderKernel
Texture2D<float4> readTex;
SamplerState sampler_readTex;
RWTexture2D<float4> writeTex;
RWTexture2D<float4> outTex;
RWTexture2D<float4> debugTex;
struct Agent
{
float2 position;
float2 direction;
};
RWStructuredBuffer<Agent> agentsBuffer;
uint rez;
uint stepn;
float time;
float trailDecayFactor;
/*
*
*
*
* UTIL
*
*
*
*/
// via "Art of Code" on youtube
float2 RandomA(float2 p)
{
float3 a = frac(p.xyx * float3(123.34, 234.34, 345.65));
a+= dot(a, a + 34.45);
return frac(float2(a.x * a.y, a.y * a.z));
}
float RandomB(float2 st) {
return frac(sin(dot(st.xy,
float2(12.9898, 78.233)))*
43758.5453123);
}
float2 RandomDirection(float2 p)
{
return(normalize(2.0 * (RandomB(p) - 0.5)));
}
/*
*
*
*
* RESET
*
*
*
*/
[numthreads(1, 1, 1)]
void ResetTextureKernel(uint3 id : SV_DispatchThreadID)
{
writeTex[id.xy] = 0;
}
[numthreads(64, 1, 1)]
void ResetAgentsKernel(uint3 id : SV_DispatchThreadID)
{
Agent a;
//a.position = RandomA(id.x * .0001 + time * .001) * rez;
//a.direction = RandomDirection(id.xx * .01 + sin(time));
//float2 st = float2(randomB(float2((float)id.x*time + .5, id.x)) - 0.5, randomB(float2((float)id.x*time, id.x * 11)) - 0.5)
//a.position = RandomB(st) * rez;
//a.direction = RandomDirection(st);
a.position = float2(RandomB(float2((float)id.x*time* .0001 + .5* .0001, id.x* .0001)), RandomB(float2((float)id.x*time* .0001, id.x * 11* .0001))) * rez;
a.direction = float2((RandomB(float2((float)id.x*time* .0001 + .7* .0001, id.x* .0001)) - 0.5), (RandomB(float2((float)id.x*time* .0001, id.x * 3* .0001)) - 0.5));
agentsBuffer[id.x] = a;
}
/*
*
*
*
* STEP
*
*
*
*/
[numthreads(1, 1, 1)]
void WriteTrailsKernel(uint3 id : SV_DispatchThreadID)
{
Agent a = agentsBuffer[id.x];
writeTex[round(a.position)] = clamp(writeTex[round(a.position)] + .1, 0, 1);
}
/*
float2 SimpleTurns(uint3 id, Agent a) {
//We will use round whenever reading from or writing to the grid tex
float4 f = readTex[round(a.position + a.direction * 2)];
float2 d = a.direction;
if (f.x > 0){
d = RandomDirection(id.xx * 0.1 + sin(time));
}
return d;
}
*/
float2 NeighborhoodTurns(uint3 id, Agent a){
float2 vectors[50];
float maxTrail = 0;
int range = 1;
int i = 0;
for (int x = -range; x <= range; x++){
for (int y = -range; y<= range; y++){
if (!(x==0 && y==0)){
float2 direction = float2(x, y);
// Hacky but we are just exploring
if (dot(normalize(direction), a.direction) > 0){
uint2 coord = round(a.position + direction);
float level = readTex.SampleLevel(sampler_readTex, coord / (float)rez, 0).r;
if (level == maxTrail) {
vectors[i] = normalize(float2(x,y));
i++;
}
else if (level >= maxTrail){
maxTrail = level;
i=0;
vectors[i] = normalize(float2(x, y));
i++;
}
if (stepn % 2 == 1){
// Mark blue the neighbourhood being read
debugTex[coord] = float4(0, 0, 1, 0);
}
}
}
}
}
float2 d = a.direction;
if(maxTrail >= .1){
int index = 0;
d = vectors[index];
}
d = normalize(d);
// Mark red the next direction
if (stepn % 2 == 1){
debugTex[round(a.position + d)] += float4 (1, 0, 0, 0);
}
return d;
}
[numthreads(1, 1, 1)]
void MoveAgentsKernel(uint3 id : SV_DispatchThreadID)
{
Agent a = agentsBuffer[id.x];
// Choose next direction
a.direction = NeighborhoodTurns(id, a);
// Move Forward
a.position = a.position + a.direction;
// Boundaries: Wrap
if (a.position.x < 0) {
a.position.x = rez - 1;
}
if (a.position.y < 0) {
a.position.y = rez - 1;
}
a.position %= float2(rez, rez);
if (stepn % 2 == 0) {
agentsBuffer[id.x] = a;
}
}
[numthreads(1, 1, 1)]
void DiffuseTextureKernel(uint3 id: SV_DispatchThreadID)
{
float4 oc = readTex[id.xy];
float avg = 0;
for (int x = -1; x <= 1; x++){
for (int y = -1; y <= 1; y++){
float2 coord = (id.xy + int2(x, y)) / (float)rez;
avg += readTex.SampleLevel(sampler_readTex, coord, 0).r;
}
}
avg /= 9.0;
oc = avg * trailDecayFactor;
oc = clamp(oc, 0, 1);
writeTex[id.xy]=oc;
}
/*
*
*
*
* RENDER
*
*
*
*/
[numthreads(1, 1, 1)]
void RenderKernel(uint3 id : SV_DispatchThreadID)
{
outTex[id.xy] = readTex[id.xy];
outTex[id.xy] += debugTex[id.xy];
debugTex[id.xy] = 0;
}
[numthreads(1, 1, 1)]
void AgentsDebugKernel(uint3 id : SV_DispatchThreadID)
{
Agent a = agentsBuffer[id.x];
outTex[round(a.position)] += float4(0, .1, 0, 0);
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EasyButtons;
[ExecuteAlways]
public class TrailAgent : MonoBehaviour
{
[Header("Trail Agent Params")]
[Range(1, 50000)]
public int agentsCount = 1;
private ComputeBuffer agentsBuffer;
[Range(.7f,1)]
public float trailDecayFactor = .9f;
[Header("Setup")]
[Range(8, 2048)]
public int rez = 8;
[Range(0,50)]
public int stepsPerFrame = 0;
[Range(1, 50)]
public int stepMod = 1;
public Material outMat;
public ComputeShader cs;
private RenderTexture readTex;
private RenderTexture writeTex;
private RenderTexture outTex;
private RenderTexture debugTex;
private int agentsDebugKernel;
private int moveAgentsKernel;
private int writeTrailsKernel;
private int renderKernel;
private int diffuseTextureKernel;
protected List<ComputeBuffer> buffers;
protected List<RenderTexture> textures;
protected int stepn = -1;
/*
*
*
* RESET
*
*
*/
void Start()
{
Reset();
}
[Button]
public void Reset()
{
Release();
agentsDebugKernel = cs.FindKernel("AgentsDebugKernel");
moveAgentsKernel = cs.FindKernel("MoveAgentsKernel");
renderKernel = cs.FindKernel("RenderKernel");
writeTrailsKernel = cs.FindKernel("WriteTrailsKernel");
diffuseTextureKernel = cs.FindKernel("DiffuseTextureKernel");
readTex = CreateTexture(rez, FilterMode.Point);
writeTex = CreateTexture(rez, FilterMode.Point);
outTex = CreateTexture(rez, FilterMode.Point);
debugTex = CreateTexture(rez, FilterMode.Point);
agentsBuffer = new ComputeBuffer(agentsCount, sizeof(float) * 4);
buffers.Add(agentsBuffer);
GPUResetKernel();
Render();
}
private void GPUResetKernel()
{
int kernel;
cs.SetInt("rez", rez);
cs.SetFloat("time", Time.time % 1);
kernel = cs.FindKernel("ResetTextureKernel");
cs.SetTexture(kernel, "writeTex", writeTex);
cs.Dispatch(kernel, rez, rez, 1);
cs.SetTexture(kernel, "writeTex", readTex);
cs.Dispatch(kernel, rez, rez, 1);
kernel = cs.FindKernel("ResetAgentsKernel");
cs.SetBuffer(kernel, "agentsBuffer", agentsBuffer);
cs.Dispatch(kernel, agentsCount, 1, 1);
}
/*
*
*
* STEP
*
*
*/
void Update()
{
if (Time.frameCount % stepMod == 0)
{
for (int i = 0; i < stepsPerFrame; i++)
{
Step();
}
}
}
[Button]
public void Step()
{
stepn +=1;
cs.SetFloat("time", Time.time % 1);
cs.SetInt("stepn", stepn);
GPUMoveAgentsKernel();
if (stepn % 2 == 1)
{
GPUDiffuseTextureKernel();
GPUWriteTrailsKernel();
SwapTex();
}
Render();
}
private void GPUDiffuseTextureKernel()
{
cs.SetTexture(diffuseTextureKernel, "readTex", readTex);
cs.SetTexture(diffuseTextureKernel, "writeTex", writeTex);
cs.SetFloat("trailDecayFactor", trailDecayFactor);
cs.Dispatch(diffuseTextureKernel, rez, rez, 1);
}
private void GPUMoveAgentsKernel()
{
cs.SetBuffer(moveAgentsKernel, "agentsBuffer", agentsBuffer);
cs.SetTexture(moveAgentsKernel, "readTex", readTex);
cs.SetTexture(moveAgentsKernel, "debugTex", debugTex);
cs.Dispatch(moveAgentsKernel, agentsCount, 1, 1);
}
private void GPUWriteTrailsKernel()
{
cs.SetBuffer(writeTrailsKernel, "agentsBuffer", agentsBuffer);
cs.SetTexture(writeTrailsKernel, "writeTex", writeTex);
cs.Dispatch(writeTrailsKernel, agentsCount, 1, 1);
}
private void SwapTex()
{
RenderTexture tmp = readTex;
readTex = writeTex;
writeTex = tmp;
}
/*
*
*
* RENDER
*
*
*/
private void Render()
{
GPURenderKernel();
GPUAgentsDebugKernel();
outMat.SetTexture("_UnlitColorMap", outTex);
if (!Application.isPlaying)
{
UnityEditor.SceneView.RepaintAll();
}
}
private void GPURenderKernel()
{
cs.SetTexture(renderKernel, "readTex", readTex);
cs.SetTexture(renderKernel, "outTex", outTex);
cs.SetTexture(renderKernel, "debugTex", debugTex);
cs.Dispatch(renderKernel, rez, rez, 1);
}
private void GPUAgentsDebugKernel()
{
cs.SetBuffer(agentsDebugKernel, "agentsBuffer", agentsBuffer);
cs.SetTexture(agentsDebugKernel, "outTex", outTex);
cs.Dispatch(agentsDebugKernel, agentsCount, 1, 1);
}
/*
*
*
* UTIL
*
*
*/
public void Release()
{
if (buffers != null)
{
foreach (ComputeBuffer buffer in buffers)
{
if (buffer != null)
{
buffer.Release();
}
}
}
buffers = new List<ComputeBuffer>();
if (textures != null)
{
foreach (RenderTexture tex in textures)
{
if (tex != null)
{
tex.Release();
}
}
}
textures = new List<RenderTexture>();
}
private void onDestroy()
{
Release();
}
private void onEnable()
{
Release();
}
private void onDisable()
{
Release();
}
protected RenderTexture CreateTexture(int r, FilterMode filterMode)
{
RenderTexture texture = new RenderTexture(r, r, 1, RenderTextureFormat.ARGBFloat);
texture.name = "out";
texture.enableRandomWrite = true;
texture.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
texture.volumeDepth = 1;
texture.filterMode = filterMode;
texture.wrapMode = TextureWrapMode.Repeat;
texture.autoGenerateMips = false;
texture.useMipMap = false;
texture.Create();
textures.Add(texture);
return texture;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment