DirectX 11 compute shader for ray/mesh intersect

被刻印的时光 ゝ 提交于 2019-12-21 21:34:15

问题


I recently converted a DirectX 9 application that was using D3DXIntersect to find ray/mesh intersections to DirectX 11. Since D3DXIntersect is not available in DX11, I wrote my own code to find the intersection, which just loops over all the triangles in the mesh and tests them, keeping track of the closest hit to the origin. This is done on the CPU side and works fine for picking via the GUI, but I have another part of the application that creates a new mesh from an existing one based on several different viewpoints, and I need to check line of sight for every triangle in the mesh many times. This gets pretty slow.

Does it make sense to use a DX11 compute shader to do this (i.e. would there be a significant speedup from doing it on the CPU)? I searched the internet but could not find an existing example.

Assuming the answer is yes, here is the approach I am thinking of:

  • Launch a thread for every triangle in my mesh
  • Each thread computes the distance to a hit on that triangle, or returns max float on a miss. Store one value per thread in a buffer.
  • Then do a reduction and return the minimum (non-negative) value.

I wish I had access to something like CUDA Thrust in DirectX, because I think coding up that reduction is going to be a pain. That's why I'm asking, so I don't do a bunch of work for nothing!


回答1:


Actually it makes a lot of sense. Here is also a whitepaper which has some useful shader snippets: http://www.graphicon.ru/html/2012/conference/EN2%20-%20Graphics/gc2012Shumskiy.pdf . Also you can use DirectCompute/CUDA/OpenCL in DirectX, but if I might give you a hint, do it in DirectCompute, because I think it is the least hassle to set up and get it running




回答2:


This is totally doable, here is some HLSL code that allows to perform that (and also handles the case where you hit 2 triangles with the same distance).

I assume that you know how to create resources (Structured Buffer) and bind them to compute pipeline.

Also I'll consider that your geometry is Indexed.

The first step is to collect triangles that pass the test. Instead of using a "Hit" flag, we will use an Append buffer to only push elements that pass the test.

First create 2 structured buffers (position and triangle indices), and copy your model data onto those.

Then create a Structured Buffer with an Appendable Unordered view.

To perform Hit detection, you can use the following Compute code:

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

Please note that you need to provide a sufficient size for appendRayHitBuffer (worst case scenario is Triangle Count, eg :every triangle is hit by the ray).

Once this is done, the beginning part of the buffer contains hit data, and the unordered view counter the number of triangles that passed the test.

Then you need to create an argument buffer, and a small Byte Address Buffer (size 16, since I don't think runtime will allow 12)

You also need a small structured buffer (one element is enough), which will be used to store minimum distance

Use CopyStructureCount to pass the UnorderedView counter into those buffers (plase note that second and third element of the Argument buffer needs to be both set to 1, as they will be arguments for use dispatch).

Clear the small StructuredBuffer Buffer using UINT_MAXVALUE, and use the Argument buffer with DispatchIndirect

I assume that you will not have many hits, so for next part numthreads will be set to 1,1,1 (if you want to use larger groups, you will need to run another compute shader to build the argument buffer).

Then to find minimum distance:

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

Since we expect that hit distance will be greater than zero, we can use asuint and InterlockedMin in that scenario. Also since we use DispatchIndirect, this part is now only applied to the elements that previously passed the test.

Now your single element buffer contains the minimum distance, but not the index( or indices).

Last part, we need to finally extract triangle index that is at the minimum hit distance.

You need again a new StructuredBuffer with an UnorderedView to store the minimum index.

Use the same dispatch arguments as before (indirect), and perform the following:

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

Now appendMinHitIndexBuffer contains the triangle index that is the closest (or several if you have that scenario), you can copy it back using a Staging resource and Map your resource for reading.



来源:https://stackoverflow.com/questions/40184304/directx-11-compute-shader-for-ray-mesh-intersect

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!