Unity compute shader make pixels look weird

61 Views Asked by At

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.

https://imgur.com/8svQDu9.jpg

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?

1

There are 1 best solutions below

0
Andrew Gotow On

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 ParticleBuffer and Result texture 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.

  1. Double-buffer your data. Rather than reading and writing from the same ParticleBuffer, keep two buffers. One for "Input State" and one for "Output State". Every frame, you can swap which ComputeBuffer is 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.
  2. Reframe your updates to avoid threads writing to cells other than their own. Rather than saying "Where should this particle go?", say "What should this location be next?". So instead of sand updating the particle below it, particles below sand would become sand, and sand with nothing below it would become empty. This ensures that no compute threads will write to a location outside of their own cell, resolving the race conditions.