Unity:Compute Shader 计算离每个顶点最近的点

Unity: Compute Shader to calculate closest point to each vertex

我有一个网格和一组点。我想为每个顶点计算数组中最近点的索引。 我有一个有效的例程:

        for (int i=0;i<vertexPositions.Length;i++)
    {
        float minDist = 100000.0f;
        int index=0;
        float dist;
        for (int a=0;a<pointPositions.Length;a++)
        {
            dist = (vertexPositions[i] - pointPositions[a]).sqrMagnitude;
            if (dist<minDist)
            {
                minDist = dist;
                index = a;
            }
        }
        vertexParameter[i] = index;
    }

vertexParameter 数组包含所需的结果。如果有很多顶点,这个例程会非常慢,所以我想制作一个计算着色器来做完全相同的事情。但我是 Compute Shaders 的初学者……

这是我的计算着色器代码:

#pragma kernel ClosestPoint

struct vertexData
{
    float3 position;
    int parameter;
};
struct pointData
{
    float3 position;
    float parameter;
};

RWStructuredBuffer<vertexData> vertex;
StructuredBuffer<pointData> point;


[numthreads(32, 1, 1)]
void ClosestPoint(uint3 id : SV_DispatchThreadID)
{
    int index;
    float dist;
    float minDist = 1000.0f;
    for (uint i = 0; i < point.Length; i++)
    {
        dist = distance(point[i].position, vertex[id.x].position);
        if (dist < minDist)
        {
            minDist = dist;
            index =  i;
        }

    }
    vertex[id.x].parameter =  index;
}

我不知道为什么,但是这段代码给出了错误的结果。如果我在 Dispatch 调用中修改 ThreadGroups,结果会发生变化,所以我想这可能是由于某些同步问题造成的……?

如果需要,这是调用着色器的脚本代码:

        vertex = new ComputeBuffer(vertices.Length, System.Runtime.InteropServices.Marshal.SizeOf(typeof(vertexData)));
    vertex.SetData(vertices);


    point= new ComputeBuffer(points.Length, System.Runtime.InteropServices.Marshal.SizeOf(typeof(pointData)));
    point.SetData(points);

    shader.SetBuffer(kernelHandle, "vertex", vertex);
    shader.SetBuffer(kernelHandle, "point", point);
    shader.Dispatch(kernelHandle, 1, 1, 1);
    vertex.GetData(vertices);
    for (int i = 0; i < vertexParameter.Length; i++)
    {
        vertexParameter[i] = vertices[i].parameter;
    }
    vertex.Release();
    point.Release();

我认为您弄错了 Dispatch() 调用中的线程组与内核规范中的 [numthreads()] 之间的关系。

shader.Dispatch(kernelHandle, vertices.Length, 1, 1);[numthreads(32,1,1)]组合的结果不是"many thread groups all with a single thread",而是vertices.Length个线程组,都是32个线程。

您的内核将因此被调用 32*vertices.Length 次,id.x 相应地增长...您从评论中的代码得到正确的结果,因为无论您尝试读写时发生什么vertex[id.x]id.x 超出范围后,它不会改变您已经计算出所有正确结果并将它们存储在适当位置的事实。

你需要做什么 然后为了不浪费时间,将你的 Dispatch() 中的 threadGroupsX 设置为 ceil(vertices.Length/32)(伪代码)。

您还可以添加类似

的内容
if (id.x >= vertexLength) return;

在您的内核中(因为除非您碰巧有 32 个顶点的倍数,否则某些线程将越界)...但实际上,这可能对性能或安全性没有任何帮助; vertices.Length 之外的读写本质上是 no-ops,而内核中的额外分支可能会产生成本。我想在这种情况下这两种方式都可能微不足道,也许有这样的陈述可以使人类读者的逻辑更清晰......但这确实意味着传递额外制服的额外样板。

顺便说一句,如果在您的应用程序中有意义,您可能还想使用 ASyncGPUReadbackRequest 来避免在 vertex.GetData(vertices); 上停止代码。为了简洁起见,您可能已经在问题中这样写了(您可能会注意到,这并不总是我的强项)。