DirectX 11计算着色器用于光线/网格相交

时间:2016-10-21 19:38:34

标签: directx-11 raytracing compute-shader

我最近转换了一个使用D3DXIntersect的DirectX 9应用程序来查找到DirectX 11的光线/网格交叉点。由于DX11中没有D3DXIntersect,我编写了自己的代码来查找交集,它只是遍历所有三角形。网格并测试它们,跟踪最接近原点的命中。这是在CPU端完成的,并且可以通过GUI进行选择,但我有另一部分应用程序根据几个不同的视点从现有的网格创建一个新网格,我需要检查每个三角形的视线在网格中多次。这变得很慢。

使用DX11计算着色器执行此操作是否有意义(即,在CPU上执行此操作会有显着的加速)吗?我搜索了互联网,但找不到现有的例子。

假设答案是肯定的,这就是我想到的方法:

  • 为我的网格中的每个三角形启动一个线程
  • 每个线程计算到该三角形上的命中距离,或者在未命中时返回最大浮点数。将每个线程的一个值存储在缓冲区中。
  • 然后进行缩小并返回最小值(非负值)。

我希望我能够在DirectX中访问类似CUDA Thrust的东西,因为我认为编写减少将会很痛苦。这就是我要问的原因,所以我不做任何工作!

2 个答案:

答案 0 :(得分:1)

实际上它很有道理。这里还有一张白皮书,其中包含一些有用的着色器片段:http://www.graphicon.ru/html/2012/conference/EN2%20-%20Graphics/gc2012Shumskiy.pdf。你也可以在DirectX中使用DirectCompute / CUDA / OpenCL,但是如果我给你一个提示,可以在DirectCompute中进行,因为我认为设置并使其运行是最麻烦的

答案 1 :(得分:1)

这是完全可行的,这里有一些允许执行该操作的HLSL代码(并且还处理了以相同距离命中2个三角形的情况)。

我假设您知道如何创建资源(结构化缓冲区)并将它们绑定到计算管道。

此外,我会考虑您的几何图形已编入索引。

第一步是收集通过测试的三角形。我们将使用Append缓冲区来仅推送通过测试的元素,而不是使用“Hit”标志。

首先创建2个结构化缓冲区(位置和三角形索引),然后将模型数据复制到这些缓冲区上。

然后使用Appendable Unordered视图创建结构化缓冲区。

要执行命中检测,您可以使用以下计算代码:

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计数器传递给那些缓冲区(请注意,Argument缓冲区的第二个和第三个元素都需要设置为1,因为它们将是使用调度的参数)。

使用UINT_MAXVALUE清除小型StructuredBuffer缓冲区,并使用带DispatchIndirect

的Argument缓冲区

我假设您没有多次点击,因此对于下一部分,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,此部分现在仅应用于先前通过测试的元素。

现在您的单个元素缓冲区包含最小距离,但不包含索引(或索引)。

最后一部分,我们需要最终提取最小命中距离的三角形索引。

您需要再次使用带有UnorderedView的新StructuredBuffer来存储最小索引。

使用与之前相同的分派参数(间接),并执行以下操作:

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包含最接近的三角形索引(如果你有这种情况,则包含几个),你可以使用Staging资源将其复制回来并映射你的资源以供阅读。