Unity: вычислить шейдер для вычисления ближайшей точки к каждой вершине - PullRequest
2 голосов
/ 18 июня 2019

У меня есть сетка и массив точек.Я хочу вычислить для каждой вершины индекс ближайшей точки в массиве.У меня есть подпрограмма, которая работает:

        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;
}

Я не знаю почему, но этот код дает ошибочные результаты.Результаты изменятся, если я изменю ThreadGroups в вызове Dispatch, поэтому я полагаю, что это может быть связано с некоторыми проблемами синхронизации ...?

В случае необходимости, это код сценария, который вызывает шейдер:

        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 Ответ

1 голос
/ 10 июля 2019

Я полагаю, что вы ошиблись в отношении между threadGroups в вашем вызове Dispatch() и [numthreads()] в спецификации вашего ядра.

Результат shader.Dispatch(kernelHandle, vertices.Length, 1, 1); в сочетании с [numthreads(32,1,1)] не является«многие группы потоков все с одним потоком», это vertices.Length группы потоков, все с 32 потоками.

Таким образом, ваше ядро ​​будет вызываться 32*vertices.Length раз, при этом id.x будет соразмерно расти ...вы получите правильный результат с кодом из вашего комментария, потому что что бы ни происходило, когда вы пытаетесь читать и писать vertex[id.x] после того, как id.x вышло за пределы, это не меняет того факта, что к тому времени вы уже вычислили всеправильные результаты и сохранили их в соответствующем месте.

Что нужно сделать , тогда, чтобы не тратить время, установите threadGroupsX в вашем Dispatch() в ceil(vertices.Length/32) (псевдокод).

Вы также можете добавить что-то вроде

if (id.x >= vertexLength) return;

в свое ядро ​​(потому что если у вас не будет кратного 32 вершин, некоторые потоки будут выходить за пределы) ..но на самом деле это, вероятно, не поможет ни с точки зрения производительности, ни с точки зрения безопасности;чтение и запись за пределами вершин. Длина будет по существу бездействующей, в то время как дополнительное ветвление в ядре может повлечь за собой затраты.Я полагаю, что в данном случае это, вероятно, незначительно, и, возможно, такое утверждение может сделать логику более понятной для читателей ... но это действительно означает дополнительный шаблон передачи дополнительной униформы.

Кстати, вы также можете использовать ASyncGPUReadbackRequest, чтобы избежать остановки вашего кода на vertex.GetData(vertices);, если это имеет смысл в вашем приложении.Вы могли бы написать это так в вопросе краткости (что, как вы можете заметить, не всегда является моей сильной стороной).

...