如何在 HLSL 计算着色器中实现乒乓技术?

问题描述 投票:0回答:1

我正在做流体模拟,用CPU实现这样的模拟非常慢,特别是当模拟网格尺寸变大或者是3d网格时。所以我使用着色器来与 GPU 并行完成计算,但这里遇到了一个问题。网格流体模拟的步骤之一需要求解线性方程,我通过雅可比方法实现了这一点。在 CPU 上实现此方法相当简单,但随着 GPU 并行工作,我必须保留保存结果数据的缓冲区的两个副本,并且在每次迭代时在两者之间翻转,以便读取一个,另一个存储进入。这种技术称为乒乓球,问题是我无法理解如何实现它,因为我对着色器还是新手。这是我当前的解决方案,我想这显然是错误的。有什么帮助我可以在这里实现乒乓球技术吗?

#pragma kernel Solve

RWStructuredBuffer<float3> Solution;
RWStructuredBuffer<float3> X;
StructuredBuffer<float3> X0;
float A;
float C;
int Iterations;
int N;

int index(int x, int y, int z) {
    return (x) + (y) * (N);
}

[numthreads(8, 8, 1)]
void Solve(uint3 id : SV_DispatchThreadID) {
    int z = id.z;
    if (id.x >= N || id.y >= N || z >= N)
        return;

    float cRecip = 1.0f / C;

    for (int iter = 0; iter < Iterations; ++iter) {
        float3 q0C = X0[index(id.x, id.y, z)];
        float3 qRight = float3(0, 0, 0);
        float3 qLeft = float3(0, 0, 0);
        float3 qTop = float3(0, 0, 0);
        float3 qBottom = float3(0, 0, 0);

        if (id.x + 1 < N)
            qRight = X[index(id.x + 1, id.y, z)];
        if (id.x > 0)
            qLeft = X[index(id.x - 1, id.y, z)];
        if (id.y + 1 < N)
            qTop = X[index(id.x, id.y + 1, z)];
        if (id.y > 0)
            qBottom = X[index(id.x, id.y - 1, z)];

        float3 newValue = (q0C + A * (qRight + qLeft + qTop + qBottom)) * cRecip;
        Solution[index(id.x, id.y, z)] = newValue;
        X[index(id.x, id.y, z)] = newValue;
    }
}

我尝试在 C# 脚本中的数据之间切换(我使用的是 Unity),但这导致了瓶颈,因为调度着色器 #iterations 次。

unity-game-engine simulation compute-shader
1个回答
0
投票

我认为这里的问题是您正在实现乒乓技术,但随后覆盖了 X StructuredBuffer。例如,在一个包含四个 float3 值(即 4 个 id)的线程组中,它将对第一个 id 执行操作,然后将其 X[index] 值更改为新值,但是对于下一个 id,当您尝试并抓取 X[(index new id)-1],这样它就会抓取预览索引中的 id,而不是您分配给 X[(index new id)-1] 的新值,因此它会根据下一帧更改值基本上,并且会对您在工作组中拥有的每个 ID 执行此操作。

您需要做什么:

在 HLSL 方面,保持简单,从 X 读取并写入 Solve。

在 C# 方面,您需要做的是设置两个缓冲区,并以相同的比例进行初始化。现在您需要保留一个 bool 变量来检查哪个变量“设置”为 X,哪个变量“设置”为“求解”,因为您将在每一帧中翻转它们。

我认为它看起来像这样

//Set them depending on the bool flag
RenderTexture currentBuffer = usePingBuffer ? pingBuffer : pongBuffer;
RenderTexture nextBuffer = usePingBuffer ? pongBuffer : pingBuffer;
        

//Set currentbuffer to the input and nextBuffer to output
compute.SetTexture(kernelToggle, "X", currentBuffer);
compute.SetTexture(kernelToggle, "Solve", nextBuffer);

   



compute.Dispatch(kernelToggle, data.width / 8, data.height / 8, 1);


//Change the flag for next frame
usePingBuffer = !usePingBuffer;
© www.soinside.com 2019 - 2024. All rights reserved.