【问题标题】:Unity: Compute Shader to calculate closest point to each vertexUnity:计算着色器以计算到每个顶点的最近点
【发布时间】:2019-06-17 21:11:51
【问题描述】:

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

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

【问题讨论】:

  • hlsl 代码中的dxl 变量是什么?它似乎没有在那里定义/声明。
  • 与之前版本的命名不匹配,现已更正。对不起。
  • 我很惊讶point.Length 在 hlsl 中编译。它可能不小心引用了其他东西。尝试添加一个 int count 变量,使用 SetInt 传入点缓冲区的长度。请参阅here 了解我所说的示例。
  • 添加count 变量并没有改变任何东西。但我不知何故部分找到了解决方案。问题似乎是我没有调度正确数量的线程。如果我在着色器中设置 ` [numthreads(32, 1, 1)] ` 并且 shader.Dispatch(kernelHandle, vertices.Length, 1, 1); 例程会给出正确的结果。但是有这么多线程组,都只有一个线程,似乎是非常糟糕的优化......

标签: c# unity3d directx hlsl compute-shader


【解决方案1】:

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

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

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

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

你也可以添加类似的东西

if (id.x >= vertexLength) return;

在您的内核中(因为除非您碰巧有多个 32 个顶点,否则某些线程将超出范围)...但实际上,这可能对性能或安全性没有任何帮助; vertices.Length 之外的读取和写入基本上是无操作的,而内核中的额外分支可能会产生成本。我想在这种情况下它可能是微不足道的,也许有这样的陈述可以使人类读者更清楚逻辑......但这确实意味着传递额外制服的额外样板。

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

【讨论】:

  • 你是对的,谢谢。我远不是计算着色器(以及一般的着色器)方面的专家,而且它肯定不是一个可以轻松找到教程或文档的领域....顺便说一句,本书中描述了越界行为,似乎我只是需要担心性能:link
  • 太好了,谢谢@kefren。也许同样的事情也适用于我的追加问题。谷歌说我“要么达到了我的观看限制,要么这本书的那部分不可用”。我刚刚发现 this 有点密集,但可能至少包含我需要的 DirectX 信息。
  • 实际上,如果有的话,if (id.x &gt;= vertexLength) return; 只会引入一个额外的分支,这可能对性能不利,所以没有它可能会更好。我也可以编辑我的答案。
猜你喜欢
  • 2021-11-28
  • 1970-01-01
  • 1970-01-01
  • 2013-02-12
  • 1970-01-01
  • 1970-01-01
  • 2013-12-10
  • 2019-07-31
  • 1970-01-01
相关资源
最近更新 更多