我试图用鼠标输入生成沙子,并且沙子在计算着色器中一次一点地落下的行为。但它不起作用。
这是代码。 像素纹理.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);
}
}
沙箱.计算
#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;
}
我发现的Bug如下。
・我指定了米色 float4(1, 0.882, 0.67, 1) 作为粒子颜色,但有一些红色、绿色和蓝色像素。
・有漂浮在空中的像素。
我无法找到原因。我的代码有什么问题吗?
很难肯定地说,但我相信您遇到了并发问题。
计算着色器是并行运行的,这意味着您的 GPU 会任意调度多组“线程”,并且它们的执行顺序通常无法确定。在沙子着色器中,您要在任意位置修改
ParticleBuffer
和 Result
纹理中的数据,因此存在潜在的竞争条件,可能会导致数据混乱,因为多个粒子试图读取和写入相同的值同时。
我认为如果进行两项更改,您会得到更一致的结果。
ParticleBuffer
读取和写入,而是保留两个缓冲区。一种用于“输入状态”,一种用于“输出状态”。每一帧,您都可以交换哪个ComputeBuffer
用于哪个,因此旧的输出成为新的输入,并且旧的输入将被新的输出覆盖。这将确保没有计算着色器线程在运行时修改另一个计算着色器线程所需的数据。