Вычислительный шейдер DirectX 11 для пересечения лучей и мешей

Недавно я преобразовал приложение DirectX 9, которое использовало D3DXIntersect для поиска пересечений лучей / сеток, в DirectX 11. Поскольку D3DXIntersect недоступен в DX11, я написал свой собственный код для поиска пересечения, который просто перебирает все треугольники в сетке и проверяет их, отслеживая самое близкое попадание к исходной точке. Это делается на стороне процессора и отлично работает для выбора через графический интерфейс, но у меня есть другая часть приложения, которая создает новую сетку из существующей на основе нескольких разных точек обзора, и мне нужно проверять линию обзора для каждого треугольника. в сетке много раз. Это происходит довольно медленно.

Имеет ли смысл использовать для этого вычислительный шейдер DX11 (то есть будет ли значительное ускорение выполнения этого на процессоре)? Я поискал в Интернете, но не смог найти существующий пример.

Предполагая, что ответ положительный, я думаю о следующем подходе:

  • Запустить нить для каждого треугольника в моей сетке
  • Каждый поток вычисляет расстояние до попадания в этот треугольник или возвращает максимальное значение с плавающей запятой при промахе. Храните одно значение на поток в буфере.
  • Затем сделайте уменьшение и верните минимальное (неотрицательное) значение.

Мне жаль, что у меня не было доступа к чему-то вроде CUDA Thrust в DirectX, потому что я думаю, что кодирование этого сокращения будет проблемой. Вот почему я спрашиваю, чтобы я не делал кучу работы даром!


person Christopher B. Smith    schedule 21.10.2016    source источник
comment
Насколько я помню, существует * теоретически оптимальная реализация проверки пересечения луча и треугольника (bool, а не местоположение), которая может быть реализована в шейдерах. Дай мне посмотреть, смогу ли я снова его найти   -  person Aaron    schedule 21.10.2016
comment
На самом деле, все примеры, которые я нахожу, также относятся к процессору ... алгоритм, о котором я думал, - это алгоритм пересечения Меллера – Трумбора. Я также нашел хорошую ссылку на руководство здесь   -  person Aaron    schedule 21.10.2016


Ответы (2)


На самом деле в этом есть большой смысл. Вот также технический документ, в котором есть несколько полезных фрагментов шейдеров: http://www.graphicon.ru/html/2012/conference/EN2%20-%20Graphics/gc2012Shumskiy.pdf. Также вы можете использовать DirectCompute / CUDA / OpenCL в DirectX, но если я могу дать вам подсказку, сделайте это в DirectCompute, потому что я думаю, что это наименьшие хлопоты для настройки и запуска.

person Citrus    schedule 23.10.2016

Это вполне выполнимо, вот некоторый код HLSL, который позволяет это сделать (а также обрабатывает случай, когда вы ударите 2 треугольника с одинаковым расстоянием).

Я предполагаю, что вы знаете, как создавать ресурсы (структурированный буфер) и связывать их с вычислительным конвейером.

Также я буду считать, что ваша геометрия проиндексирована.

Первый шаг - собрать треугольники, которые проходят проверку. Вместо использования флага «Hit» мы будем использовать буфер добавления только для отправки элементов, прошедших проверку.

Сначала создайте 2 структурированных буфера (индексы положения и треугольника) и скопируйте в них данные вашей модели.

Затем создайте структурированный буфер с добавляемым неупорядоченным представлением.

Чтобы выполнить обнаружение попаданий, вы можете использовать следующий вычислительный код:

struct rayHit
{
    uint triangleID;
    float distanceToTriangle;
};

cbuffer cbRaySettings : register(b0)
{
    float3 rayFrom;
    float3 rayDir;
    uint TriangleCount;
};

StructuredBuffer<float3> positionBuffer : register(t0);
StructuredBuffer<uint3> indexBuffer : register(t1);

AppendStructuredBuffer<rayHit> appendRayHitBuffer : register(u0);

void TestTriangle(float3 p1, float3 p2, float3 p3, out bool hit, out float d)
{
    //Perform ray/triangle intersection
    hit = false;
    d = 0.0f;
}

[numthreads(64,1,1)]
void CS_RayAppend(uint3 tid : SV_DispatchThreadID)
{
    if (tid.x >= TriangleCount)
        return;

    uint3 indices = indexBuffer[tid.x];
    float3 p1 = positionBuffer[indices.x];
    float3 p2 = positionBuffer[indices.y];
    float3 p3 = positionBuffer[indices.z];

    bool hit;
    float d;
    TestTriangle(p1,p2,p3,hit, d);

    if (hit)
    {
        rayHit hitData;
        hitData.triangleID = tid.x;
        hitData.distanceToTriangle = d;
        appendRayHitBuffer.Append(hitData);
    }
}

Обратите внимание, что вам необходимо предоставить достаточный размер для appendRayHitBuffer (наихудший сценарий - счетчик треугольников, например: каждый треугольник попадает под луч).

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

Затем вам нужно создать буфер аргументов и небольшой буфер байтового адреса (размер 16, поскольку я не думаю, что среда выполнения позволит 12)

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

Используйте CopyStructureCount для передачи счетчик UnorderedView в эти буферы (обратите внимание, что второй и третий элементы буфера аргумента должны быть установлены в 1, поскольку они будут аргументами для использования диспетчеризации).

Очистите небольшой буфер StructuredBuffer с помощью UINT_MAXVALUE и используйте буфер аргументов с DispatchIndirect

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

Затем найти минимальное расстояние:

StructuredBuffer<rayHit> rayHitbuffer : register(t0);
ByteAddressBuffer rayHitCount : register(t1);

RWStructuredBuffer<uint> rwMinBuffer : register(u0);

[numthreads(1,1,1)]
void CS_CalcMin(uint3 tid : SV_DispatchThreadID)
{
    uint count = rayHitCount.Load(0);
    if (tid.x >= count)
        return;

    rayHit hit = rayHitbuffer[tid.x];

    uint dummy;
    InterlockedMin(rwMinBuffer[0],asuint(hit.distanceToTriangle), dummy);   
} 

Поскольку мы ожидаем, что расстояние попадания будет больше нуля, мы можем использовать asuint и InterlockedMin в этом сценарии. Кроме того, поскольку мы используем DispatchIndirect, эта часть теперь применяется только к элементам, которые ранее прошли тест.

Теперь ваш одноэлементный буфер содержит минимальное расстояние, но не индекс (или индексы).

Последняя часть, нам нужно, наконец, извлечь индекс треугольника, который находится на минимальном расстоянии срабатывания.

Вам снова нужен новый StructuredBuffer с UnorderedView для хранения минимального индекса.

Используйте те же аргументы отправки, что и раньше (косвенные), и выполните следующие действия:

ByteAddressBuffer rayHitCount : register(t1);
StructuredBuffer<uint> MinDistanceBuffer : register(t2);
AppendStructuredBuffer<uint> appendMinHitIndexBuffer : register(u0);

[numthreads(1,1,1)]
void CS_AppendMin(uint3 tid : SV_DispatchThreadID)
{
    uint count = rayHitCount.Load(0);
    if (tid.x >= count)
        return;

    rayHit hit = rayHitbuffer[tid.x];

    uint minDist = MinDistanceBuffer[0];

    uint d = asuint(hit.distanceToTriangle);

    if (d == minDist)
    {
        appendMinHitIndexBuffer.Append(hit.triangleID);
    }
}

Теперь appendMinHitIndexBuffer содержит ближайший индекс треугольника (или несколько, если у вас есть такой сценарий), вы можете скопировать его обратно, используя промежуточный ресурс и сопоставить свой ресурс для чтения.

person mrvux    schedule 25.10.2016