Вычислительный шейдер DirectX 11 для пересечения луча / сетки
Недавно я преобразовал приложение DirectX 9, которое использовало D3DXIntersect для поиска пересечений лучей / сеток, в DirectX 11. Поскольку D3DXIntersect недоступно в DX11, я написал свой собственный код, чтобы найти пересечение, которое просто перебирает все треугольники в сетке и проверяет их, отслеживая ближайшее попадание в начало координат. Это делается на стороне процессора и отлично работает для выбора через графический интерфейс, но у меня есть другая часть приложения, которая создает новую сетку из существующей на основе нескольких различных точек обзора, и мне нужно проверить линию обзора для каждого треугольника в сетке много раз. Это становится довольно медленным.
Имеет ли смысл использовать для этого вычислительный шейдер DX11 (т. Е. Будет ли значительное ускорение при выполнении этого на процессоре)? Я искал в Интернете, но не смог найти существующий пример.
Предполагая, что ответ - да, вот подход, о котором я думаю:
- Запустить нить для каждого треугольника в моей сетке
- Каждый поток вычисляет расстояние до попадания в этот треугольник или возвращает максимальное число с плавающей точкой при промахе. Сохраните одно значение на поток в буфере.
- Затем сделайте уменьшение и верните минимальное (неотрицательное) значение.
Хотелось бы, чтобы у меня был доступ к чему-то вроде CUDA Thrust в DirectX, потому что я думаю, что кодирование этого сокращения будет проблемой. Вот почему я спрашиваю, поэтому я не делаю кучу работы даром!
2 ответа
Это вполне выполнимо, вот код HLSL, который позволяет это выполнить (а также обрабатывает случай, когда вы попадаете в 2 треугольника с одинаковым расстоянием).
Я предполагаю, что вы знаете, как создавать ресурсы (структурированный буфер) и связывать их для вычисления конвейера.
Также я буду считать, что ваша геометрия проиндексирована.
Первый шаг - собрать треугольники, которые проходят тест. Вместо использования флага "Hit" мы будем использовать буфер "Добавить" только для элементов, прошедших тест.
Сначала создайте 2 структурированных буфера (индексы положения и треугольника) и скопируйте в них данные модели.
Затем создайте структурированный буфер с добавлением неупорядоченного представления.
Для определения попадания вы можете использовать следующий код Compute:
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, поскольку они будут аргументами для диспетчеризации использования).
Очистите небольшой буфер Structured Buffer, используя 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, эта часть теперь применяется только к элементам, которые ранее прошли тест.
Теперь ваш одноэлементный буфер содержит минимальное расстояние, но не индекс (или индексы).
Последняя часть, нам нужно наконец извлечь индекс треугольника, который находится на минимальном расстоянии попадания.
Вам снова нужен новый Structured Buffer с 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 содержит индекс треугольника, который является ближайшим (или несколько, если у вас есть этот сценарий), вы можете скопировать его обратно, используя промежуточный ресурс, и сопоставить ваш ресурс для чтения.
На самом деле это имеет большой смысл. Вот также технический документ, в котором есть несколько полезных фрагментов шейдера: http://www.graphicon.ru/html/2012/conference/EN2%20-%20Graphics/gc2012Shumskiy.pdf. Также вы можете использовать DirectCompute/CUDA/OpenCL в DirectX, но, если я могу дать вам подсказку, сделайте это в DirectCompute, потому что я думаю, что это наименее хлопот для настройки и запуска его