diff --git a/source/Changelog.md b/source/Changelog.md index e1f2ef5..0225239 100644 --- a/source/Changelog.md +++ b/source/Changelog.md @@ -4,6 +4,10 @@ 此更新日志为纵览更新,对于具体文章的更新位于每个文章的开头的 `更新记录` 中。 ``` +## 2023/10/26 + +>* 更新`glTF 场景`文档 + ## 2023/10/24 >* 增加`glTF 场景`文档 diff --git a/source/Literature/NVIDIAVulkanRayTracingTutorial/extensions/glTFScene.rst b/source/Literature/NVIDIAVulkanRayTracingTutorial/extensions/glTFScene.rst index 1f74996..71f8e50 100644 --- a/source/Literature/NVIDIAVulkanRayTracingTutorial/extensions/glTFScene.rst +++ b/source/Literature/NVIDIAVulkanRayTracingTutorial/extensions/glTFScene.rst @@ -8,6 +8,10 @@ glTF 场景 * 2023/10/24 增加该扩展文档 * 2023/10/24 增加 ``教程`` 章节 * 2023/10/24 增加 ``场景数据`` 章节 + * 2023/10/26 增加 ``加载 glTF 场景`` 章节 + * 2023/10/26 增加 ``加载场景`` 章节 + * 2023/10/26 增加 ``几何体转底层加速结构`` 章节 + * 2023/10/26 增加 ``创建顶层加速结构`` 章节 `文献源`_ @@ -100,3 +104,198 @@ glTF 场景 nvvk::Buffer m_materialBuffer; nvvk::Buffer m_primInfo; nvvk::Buffer m_sceneDesc; + +加载 glTF 场景 +#################### + +我们这里将会使用 `TinyGLTF `_ 加载 ``glTF`` ,之后为了屏蔽掉繁琐的场景解析遍历,将会使用 `gltfScene `_ 来帮助我们将场景进行压缩。 + +加载场景 +************************ + +相较于加载一个模型,这次我们将加载一个场景,所以我们使用 ``loadScene()`` 来代替 ``loadModel()`` 加载场景。 + +``loadScene()`` 函数在源文件中的开头将会引入 ``TinyGLTF`` 。 + +.. code:: c++ + + tinygltf::Model tmodel; + tinygltf::TinyGLTF tcontext; + std::string warn, error; + if(!tcontext.LoadASCIIFromFile(&tmodel, &error, &warn, filename)) + assert(!"Error while loading scene"); + +之后我们将使用 ``gltfScene`` 来帮助我们压缩和提炼我们需要的数据。 + +.. code:: c++ + + m_gltfScene.importMaterials(tmodel); + m_gltfScene.importDrawableNodes(tmodel, + nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0); + +接下来就是分配缓存并存储数据。比如,顶点位置,法线,纹理坐标等等。 + +.. code:: c++ + + // 在设备上创建换次年并拷贝顶点,索引和材质信息 + nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex); + VkCommandBuffer cmdBuf = cmdBufGet.createCommandBuffer(); + + m_vertexBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_positions, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR); + m_indexBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_indices, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR); + m_normalBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_normals, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + m_uvBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_texcoords0, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + +由于该示例的材质是一个简化版本,这里我们将从 ``glTF`` 材质中提取我们需要的部分。 + +.. code:: c++ + + // 仅获取我们需要的材质数据 + std::vector shadeMaterials; + for(auto& m : m_gltfScene.m_materials) + { + shadeMaterials.emplace_back(GltfShadeMaterial{m.baseColorFactor, m.emissiveFactor, m.baseColorTexture}); + } + m_materialBuffer = m_alloc.createBuffer(cmdBuf, shadeMaterials, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + +为了能够在最近命中着色器中找到命中位置,同时也能获取其它属性,我们将存储几何的偏移信息。 + +.. code:: c++ + + // 如下是用于在最近命中着色器中找到网格的图元信息 + std::vector primLookup; + for(auto& primMesh : m_gltfScene.m_primMeshes) + { + primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex}); + } + m_rtPrimLookup = + m_alloc.createBuffer(cmdBuf, primLookup, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + +.. admonition:: std::vector primLookup + :class: note + + 在本示例中一共有 ``10`` 个物体( ``5`` 面墙, ``3`` 个矩形, ``2`` 个球体)。然而解析完 ``glTF`` 场景后 ``primLookup`` 中有 ``9`` 个元素(模型)。其中顶面和底面墙体共用同一个网格。 + +最后创建一个缓存存储所以缓存的引用: + +.. code:: c++ + + SceneDesc sceneDesc; + sceneDesc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer); + sceneDesc.indexAddress = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer); + sceneDesc.normalAddress = nvvk::getBufferDeviceAddress(m_device, m_normalBuffer.buffer); + sceneDesc.uvAddress = nvvk::getBufferDeviceAddress(m_device, m_uvBuffer.buffer); + sceneDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_materialBuffer.buffer); + sceneDesc.primInfoAddress = nvvk::getBufferDeviceAddress(m_device, m_primInfo.buffer); + m_sceneDesc = m_alloc.createBuffer(cmdBuf, sizeof(SceneDesc), &sceneDesc, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + +在退出该函数之前,我们需要创建纹理(默认场景中没有纹理)并提交指令缓存。最后等到所有的数据拷贝完成。 + +.. code:: c++ + + // 创建所有找到的纹理 + createTextureImages(cmdBuf, tmodel); + cmdBufGet.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + + + NAME_VK(m_vertexBuffer.buffer); + NAME_VK(m_indexBuffer.buffer); + NAME_VK(m_normalBuffer.buffer); + NAME_VK(m_uvBuffer.buffer); + NAME_VK(m_materialBuffer.buffer); + NAME_VK(m_primInfo.buffer); + NAME_VK(m_sceneDesc.buffer); + } + +.. admonition:: NAME_VK + :class: note + + 宏 ``NAME_VK`` 是用于简化 ``Nsight Graphics`` 中 ``Vulkan`` 对象命名,用于调试时获取相应的创建信息。 + +几何体转底层加速结构 +#################### + +我们不再使用 ``objectToVkGeometryKHR()`` 而会使用 ``primitiveToVkGeometry(const nvh::GltfPrimMesh& prim)`` 。该函数与之前的相似,仅仅是输入不同,这里 ``VkAccelerationStructureBuildRangeInfoKHR`` 将会设置偏移数据。 + +.. code:: c++ + + //-------------------------------------------------------------------------------------------------- + // 将 glTF 中的网格转换成底层加速结构中的几何图元 + // + auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) + { + // 底层加速结构的构建需要原始的设备内存地址 + VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer); + VkDeviceAddress indexAddress = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer); + + uint32_t maxPrimitiveCount = prim.indexCount / 3; + + // 设置顶点数组缓存 + VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. + triangles.vertexData.deviceAddress = vertexAddress; + triangles.vertexStride = sizeof(nvmath::vec3f); + // 设置索引缓存(32比特无符号整形) + triangles.indexType = VK_INDEX_TYPE_UINT32; + triangles.indexData.deviceAddress = indexAddress; + // 底层加速结构本身的变换为单位矩阵(无变换) + //triangles.transformData = {}; + triangles.maxVertex = prim.vertexCount - 1; + + // 将之前的三角形设置为不透明 + VkAccelerationStructureGeometryKHR asGeom{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; + asGeom.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + asGeom.flags = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR; // 对任意命中的限制 + asGeom.geometry.triangles = triangles; + + VkAccelerationStructureBuildRangeInfoKHR offset; + offset.firstVertex = prim.vertexOffset; + offset.primitiveCount = prim.indexCount / 3; + offset.primitiveOffset = prim.firstIndex * sizeof(uint32_t); + offset.transformOffset = 0; + + // 目前我们一个底层加速结构对应一个几何体,但其实可以加入更多几何体 + nvvk::RaytracingBuilderKHR::BlasInput input; + input.asGeometry.emplace_back(asGeom); + input.asBuildOffsetInfo.emplace_back(offset); + + return input; + } + +创建顶层加速结构 +#################### + +基本上与之前的没什么区别,除了索引数据存在 ``primMesh`` 中。 + +.. code:: c++ + + for(auto& node : m_gltfScene.m_nodes) + { + VkAccelerationStructureInstanceKHR rayInst; + rayInst.transform = nvvk::toTransformMatrixKHR(node.worldMatrix); + rayInst.instanceCustomIndex = node.primMesh; // gl_InstanceCustomIndexEXT: 用于寻找图元 + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(node.primMesh); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; + rayInst.instanceShaderBindingTableRecordOffset = 0; // 所有的物体都是用相同的命中组 + tlas.emplace_back(rayInst); + } + +.. admonition:: m_gltfScene.m_nodes + :class: note + + 长度为 ``10`` ,每个元素分别对应场景中的 ``10`` 个物体。 + + +