I am trying to generate sand with mouse input, and the behavior of the sand falling down one dot at a time in Compute Shader.but it is not working.
Here is the code. pixeltexture.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PixelTexture : MonoBehaviour
{
[SerializeField]
Material graphBackgroundMat;
[SerializeField]
ComputeShader computeShader;
[SerializeField]
public static int Size = 128;
int updateKernel;
public bool Run = true;
RenderTexture renderTexture;
ComputeBuffer computeBuffer;
struct Particle_t
{
uint id;
Vector4 color;
};
// Start is called before the first frame update
void Start()
{
int initKernel = computeShader.FindKernel("init");
updateKernel = computeShader.FindKernel("update");
renderTexture = new RenderTexture(Size, Size / 2, 0, RenderTextureFormat.ARGBFloat);
renderTexture.enableRandomWrite = true;
renderTexture.filterMode = FilterMode.Point;
renderTexture.Create();
graphBackgroundMat.SetTexture("_MainTex", renderTexture);
computeBuffer = new ComputeBuffer(renderTexture.width * renderTexture.height , 20);
Particle_t[] particles = new Particle_t[computeBuffer.count];
computeBuffer.SetData(particles);
computeShader.SetTexture(initKernel, "Result", renderTexture);
computeShader.SetBuffer(initKernel, "ParticleBuffer", computeBuffer);
computeShader.SetInt("TexWidth", renderTexture.width);
computeShader.SetInt("TexHeight", renderTexture.height);
computeShader.Dispatch(initKernel, Mathf.CeilToInt(Size / 16), Mathf.CeilToInt(Size / 16), 1);
computeShader.SetTexture(updateKernel, "Result", renderTexture);
computeShader.SetBuffer(updateKernel, "ParticleBuffer", computeBuffer);
}
private void FixedUpdate()
{
if (Run)
{
computeShader.SetTexture(updateKernel, "Result", renderTexture);
computeShader.SetFloat("DeltaTime", Time.deltaTime);
computeShader.SetBuffer(updateKernel, "ParticleBuffer", computeBuffer);
computeShader.Dispatch(updateKernel, Mathf.CeilToInt(Size), Mathf.CeilToInt(Size), 1);
}
}
private void OnDestroy()
{
renderTexture.Release();
computeBuffer.Release();
}
private void OnMouseDown()
{
computeShader.SetBool("Dragging", true);
}
private void OnMouseDrag()
{
Vector3 mp = Input.mousePosition;
mp.x *= (renderTexture.width / (float)Screen.width);
mp.y *= (renderTexture.height / (float)Screen.height);
computeShader.SetVector("InputPosition", mp);
}
private void OnMouseUp()
{
computeShader.SetBool("Dragging", false);
}
}
sandbox.compute
#pragma kernel init
#pragma kernel update
/* Constant declarations */
#define MAT_ID_EMPTY 0x0000
#define MAT_ID_SAND 0x0001
/* Variable declarations */
struct Particle_t
{
uint id;
float4 color;
};
RWTexture2D<float4> Result; // render result
RWStructuredBuffer<Particle_t> ParticleBuffer; // render data
uint TexWidth, TexHeight; // render texture size
bool Dragging; // player input frag
float4 InputPosition; // player input position
float DeltaTime;
/* Prototype declarations */
bool is_empty(int2 src_id);
bool is_in_bounds(int2 src_id);
uint compute_idx(uint2 src_id);
Particle_t get_particle_at(uint2 src_id);
Particle_t particle_empty();
Particle_t particle_sand();
void spawn_particle(uint2 src_id);
void spawn_sand(uint2 src_id, float2 co);
void update_particle(uint2 src_id);
void update_sand(uint2 src_id);
void render_particle(uint2 src_id, Particle_t p);
/* Function declarations */
bool is_empty(int2 src_id) {
return (ParticleBuffer[compute_idx(src_id)].id == MAT_ID_EMPTY);
}
bool is_in_bounds(int2 src_id) {
if ((src_id.x < 0) || (src_id.x > (TexWidth - 1)) || (src_id.y < 0) || (src_id.y > (TexHeight - 1))) { return false; }
return true;
}
uint compute_idx(uint2 src_id) {
return src_id.y * TexWidth + src_id.x;
}
// get particle info from src_id
Particle_t get_particle_at(uint2 src_id) {
return ParticleBuffer[compute_idx(src_id)];
}
// create particle empty
Particle_t particle_empty()
{
Particle_t p;
p.id = MAT_ID_EMPTY;
p.color = float4(0.2, 0.2, 0.2, 1);
return p;
}
// create particle sand
Particle_t particle_sand()
{
Particle_t p;
p.id = MAT_ID_SAND;
p.color = float4(1, 0.882, 0.67, 1);
return p;
}
[numthreads(16, 16, 1)]
void init(uint3 id : SV_DispatchThreadID) {
render_particle(id.xy, particle_empty());
}
[numthreads(1, 1, 1)]
void update(uint3 id : SV_DispatchThreadID) {
spawn_particle(id.xy);
update_particle(id.xy);
}
void update_particle(uint2 src_id) {
int mat_id = get_particle_at(src_id).id;
//fall dot
switch (mat_id) {
case MAT_ID_SAND:
update_sand(src_id);
break;
default:
break;
}
}
/* Update Particle Functions */
void update_sand(uint2 src_id) {
Particle_t src_particle = get_particle_at(src_id);
int2 below_id = src_id + int2(0, -1);
if (is_in_bounds(below_id) && is_empty(below_id)) {
Particle_t tmp_dst_particle = get_particle_at(below_id);
render_particle(below_id, src_particle); // src_particle move down
render_particle(src_id, tmp_dst_particle); // set src_particle idx empty
}
}
void spawn_particle(uint2 src_id) {
if (!Dragging) { return; }
int2 mp = int2(InputPosition.xy);
spawn_sand(mp + uint2(0, 0), mp + src_id);
}
void spawn_sand(uint2 src_id, float2 co) {
render_particle(src_id, particle_sand());
}
void render_particle(uint2 src_id, Particle_t p) {
ParticleBuffer[compute_idx(src_id)] = p;
Result[src_id] = p.color;
}
The bugs I have identified are as follows.
・I have specified beige float4(1, 0.882, 0.67, 1) for particle color, but there are some red, green and blue pixels.
・There are pixels that are floating in the air.
I am having trouble finding the cause. What is wrong with my code?
It's difficult to say for sure, but I believe you're encountering a concurrency issue.
Compute shaders are run in parallel, which means multiple groups of "threads" are dispatched arbitrarily by your GPU and the order in which they're executed generally can't be counted on. In your sand shader, you're modifying data in the
ParticleBufferandResulttexture at arbitrary locations, so there's a potential race condition which could lead to scrambled data, as multiple particles are trying to read and write to the same value at the same time.I think you will get more consistent results if you make two changes.
ParticleBuffer, keep two buffers. One for "Input State" and one for "Output State". Every frame, you can swap whichComputeBufferis used for which, so the old output becomes the new input, and the old input will be overwritten with the new output. This will ensure that no compute shader threads will modify the data required by another as it's running.