Unity:计算着色器以计算到每个顶点的最近点

时间:2019-06-17 21:11:51

标签: c# unity3d directx hlsl compute-shader

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

        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 Shader。但是我是Compute Shaders的初学者……

这是我的Compute Shader代码:

#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();

1 个答案:

答案 0 :(得分:1)

我相信您在Dispatch()调用中的线程组与内核规范中的[numthreads()]之间的关系是错误的。

shader.Dispatch(kernelHandle, vertices.Length, 1, 1);[numthreads(32,1,1)]结合使用的结果不是“很多线程组都只有一个线程”,而是vertices.Length个线程组都具有32个线程。

因此,您的内核将被调用32*vertices.Length次,id.x会相应地增长……您可以从注释中获得正确的代码结果,因为尝试读写{{ 1}}在vertex[id.x]超出范围后,它并不会改变您已经计算出所有正确结果并将它们存储在适当位置的事实。

您需要做的事情,然后为了不浪费时间,将id.x中的threadGroupsX设置为Dispatch()(伪代码)。

您还可以添加

之类的内容
ceil(vertices.Length/32)

在内核中(因为除非碰巧有32个顶点的倍数,否则某些线程将超出范围)...但是实际上,这可能对性能或安全性没有任何帮助;长度基本上是无操作的,而内核中额外的分支可能会产生成本。我想在这种情况下,这两种方式都可能无关紧要,也许有这样的陈述可以使人类读者更清楚地了解逻辑……但这确实意味着传递了额外的制服会产生额外的样板。

顺便说一句,如果在您的应用程序中有意义,您可能还希望使用if (id.x >= vertexLength) return; 来避免将代码停滞在ASyncGPUReadbackRequest上。为了简洁起见,您可能会这样写(您可能会注意到,这并不总是我的强项)。