nvpro-samples / vk_raytracing_tutorial_KHR

Ray tracing examples and tutorials using VK_KHR_ray_tracing
Apache License 2.0
1.34k stars 142 forks source link

Troubles combining intersection and gltf tutorials #23

Closed richardeakin closed 3 years ago

richardeakin commented 3 years ago

Hi,

Starting somewhere around the end of the intersection tutorial, I had a go at switching to gltf support based on the related tutorial and while I got pretty close, have been stuck on a bug for a while now. I've tried just about everything I can think of so thought I'd ask for some suggestions, based on the symptoms. This is my test project.

Basically, the intersection shader's hit values now see completely off. You can see my issue in the following gifs:

vk_raytrace_bug

The following is a simplified scene, loading the Box.gltf file (red), and then an implicit object to the left and right:

vk_raytrace_bug_simplified

You can see where the implicit objects actually are if I modify the intersection shader to always call reportIntersectionEXT with some positive hit distance. Here it is with the bug (hit distance is going negative when it shouldn't):

image

And here it is when I hack it to use reportIntersectionEXT(abs(tHit), hitKind);:

image

Some notes:

So, not sure what next to look at. All I know is that the tHit value in the intersection shader is reporting < 0 when it shouldn't. Does this ring any bells, or any ideas what I can try?

Thanks so much again for the awesome tutorial series!

mklefrancois commented 3 years ago

Hi @richardeakin,

Make sure the hitGroupId uses the proper Hit Group Shaders for the implicit objects. In createTopLevelAS() you are setting the value to 0, which in incorrect in this case. Check that all BLAS containing implicit objects are using the Hit Group 1 or the one associated with the Group (Hit, Intersection, AnyHit) you create in the SBT, which I haven't see it done.

richardeakin commented 3 years ago

Thanks for taking a look at this! I believe I am adding the implicit objects to the TLAS with hit group = 1 here, this is what you're referring to right?

The link is a convenient place to ask you though, the line before seems like it could be the problem - does it make sense that the blasId for the implicit BLAS is m_gltfScene.m_primMeshes.size()? I haven't been able to reconcile in my mind how to deal with glTF scenes that have more nodes than meshes. For example taking cornellBox.gltf as used in the tutorial code, there are 9 primMeshes and thus 9 BLAS added in createBottomLevelAS(), and then in createTopLevelAS() there are 10 nvvk::RaytracingBuilderKHR::Instances added to the TLAS corresponding to the 10 scene nodes (the two spheres reuse the same mesh).

I thought this was the issue as Suzanne.gltf has only one node and one mesh and it worked, but then this was squashed when I tried Box.gltf which as is 1:1 nodes:meshes and it has the same problems as cornellBox.gltf. Thanks again for any tips on solving this, it is driving me a bit nuts!

For convenience I've pasted my updated version of these functions here:


void HelloVulkan::createBottomLevelAS()
{
    // BLAS - Storing each primitive in a geometry
    std::vector<nvvk::RaytracingBuilderKHR::BlasInput> allBlas;
    allBlas.reserve(m_gltfScene.m_primMeshes.size());
    for(auto& primMesh : m_gltfScene.m_primMeshes)
    {
        auto geo = primitiveToGeometry(primMesh);
        allBlas.push_back({geo});
    }

    // Implicits
    {
        auto blas = sphereToVkGeometryKHR();
        allBlas.emplace_back(blas);
    }

    m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
}

void HelloVulkan::createTopLevelAS()
{
    std::vector<nvvk::RaytracingBuilderKHR::Instance> tlas;
    tlas.reserve(m_gltfScene.m_nodes.size());
    uint32_t instID = 0;
    for(auto& node : m_gltfScene.m_nodes)
    {
        nvvk::RaytracingBuilderKHR::Instance rayInst;
        rayInst.transform        = node.worldMatrix;
        rayInst.instanceCustomId = node.primMesh;  // gl_InstanceCustomIndexEXT: to find which primitive
        rayInst.blasId           = node.primMesh;
        rayInst.flags            = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
        rayInst.hitGroupId       = 0;  // We will use the same hit group for all objects
        tlas.emplace_back(rayInst);
    }

    // Add the blas containing all implicits
    {
        nvvk::RaytracingBuilderKHR::Instance rayInst;
        rayInst.transform  = m_gltfScene.m_nodes[0].worldMatrix;          // Position of the instance
        rayInst.instanceCustomId = static_cast<uint32_t>(tlas.size());  // gl_InstanceID
        //rayInst.blasId     = static_cast<uint32_t>(m_gltfScene.m_nodes.size());
        rayInst.blasId     = static_cast<uint32_t>(m_gltfScene.m_primMeshes.size()); // TODO: this is hacky, assign blasId from createBottomLevelAS()
        rayInst.hitGroupId = 1;  // We will use the same hit group for all objects
        rayInst.flags      = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
        tlas.emplace_back(rayInst);
    }

    m_rtBuilder.buildTlas(tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
}
mklefrancois commented 3 years ago

I think you are not setting the right instance matrix for the implicit surface

Can you try replacing:

rayInst.transform = m_gltfScene.m_nodes[0].worldMatrix;  // Position of the instance

with

rayInst.transform = nvmath::mat4(1);                     // Position of the instance
mklefrancois commented 3 years ago

Hi, I took a bit of time looking into the issue. I was not able to re-use your example, but I have made a similar example starting with the glTF sample and adding the intersection code. I was able to see a similar artifacts when the matrix for the implicit surfaces was not identity. This makes sense, because the hitT is incorrectly calculated in the intersection shader.

Regarding the number of BLAS, it is m_gltfScene.m_primMeshes.size(). If the implicit surfaces are added after all glTF primitives, the blasId will be static_cast<uint32_t>(m_gltfScene.m_primMeshes.size()).

Here is a modified version of the glTF. Sorry, it uses share code that is not yet on GitHub, but it should be easy to adapt. ray_tracing_gltf.zip

Note that this glTF example uses a very simple shading model. For a more complete PBR shading, I have wrote a separated example: https://github.com/nvpro-samples/vk_raytrace

richardeakin commented 3 years ago

Ah, of course! That's what I get for blindly copying the sample code. I imagine then this line should also be nvmath::mat4(1), though it doesn't matter with the objs since their transform was always identity.

After I read your diagnose of the bug, I've been having a go at enabling those TLAS-level transforms since that seems useful for building up larger scenes based on the "Ray Intersection with Transformed Objects" chapter in Suffern's "Ray Tracing from the Ground Up", working well! No transform:

image

rayInst.transform = nvmath::translation_mat4( nvmath::vec3f( 0, 2, 0 ) ) * nvmath::scale_mat4( nvmath::vec3f( 2 ) );

image

I'm working toward your vk_raytrace, looking forward to it! But also being able to raytrace some things procedurally was very important to me, so I'm happy that I finally have a solution for this bug I was stuck on. Thanks so much!