New version of the samples and tutorials based on KHR_ray_tracing
This commit is contained in:
parent
2fd15056a2
commit
b6402f0c09
271 changed files with 134108 additions and 2 deletions
587
docs/vkrt_tuto_animation.md.htm
Normal file
587
docs/vkrt_tuto_animation.md.htm
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
<meta charset="utf-8" lang="en">
|
||||
**NVIDIA Vulkan Ray Tracing Tutorial**
|
||||
**Animation**
|
||||
|
||||
<small>Authors: [Martin-Karl Lefrançois](https://devblogs.nvidia.com/author/mlefrancois/), Neil Bickford </small>
|
||||
|
||||
|
||||
# Animation
|
||||
|
||||

|
||||
|
||||
This is an extension of the [Vulkan ray tracing tutorial](vkrt_tutorial.md.htm).
|
||||
|
||||
We will discuss two animation methods: animating only the transformation matrices, and animating the geometry itself.
|
||||
|
||||
(insert setup.md.html here)
|
||||
|
||||
# Animating the Matrices
|
||||
This first example shows how we can update the matrices used for instances in the TLAS.
|
||||
|
||||
## Creating a Scene
|
||||
|
||||
In main.cpp we can create a new scene with a ground plane and 21 instances of the Wuson model, by replacing the
|
||||
`helloVk.loadModel` calls in `main()`. The code below creates all of the instances
|
||||
at the same position, but we will displace them later in the animation function. If you run the example,
|
||||
you will find that the rendering is considerably slow, because the geometries are exactly at the same position
|
||||
and the acceleration structure does not deal with this well.
|
||||
|
||||
~~~~ C++
|
||||
helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths),
|
||||
nvmath::scale_mat4(nvmath::vec3f(2.f, 1.f, 2.f)));
|
||||
helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths));
|
||||
HelloVulkan::ObjInstance inst = helloVk.m_objInstance.back();
|
||||
for(int i = 0; i < 20; i++)
|
||||
helloVk.m_objInstance.push_back(inst);
|
||||
~~~~
|
||||
|
||||
## Animation Function
|
||||
We want to have all of the Wuson models running in a circle, and we will first modify the rasterizer to handle this.
|
||||
Animating the transformation matrices will be done entirely on the CPU, and we will copy the computed transformation to the GPU.
|
||||
In the next example, the animation will be done on the GPU using a compute shader.
|
||||
|
||||
Add the declaration of the animation to the `HelloVulkan` class.
|
||||
~~~~ C++
|
||||
void animationInstances(float time);
|
||||
~~~~
|
||||
|
||||
The first part computes the transformations for all of the Wuson models, placing each one behind another.
|
||||
~~~~ C++
|
||||
void HelloVulkan::animationInstances(float time)
|
||||
{
|
||||
const int32_t nbWuson = static_cast<int32_t>(m_objInstance.size() - 1);
|
||||
const float deltaAngle = 6.28318530718f / static_cast<float>(nbWuson);
|
||||
const float wusonLength = 3.f;
|
||||
const float radius = wusonLength / (2.f * sin(deltaAngle / 2.0f));
|
||||
const float offset = time * 0.5f;
|
||||
|
||||
for(int i = 0; i < nbWuson; i++)
|
||||
{
|
||||
int wusonIdx = i + 1;
|
||||
ObjInstance& inst = m_objInstance[wusonIdx];
|
||||
inst.transform = nvmath::rotation_mat4_y(i * deltaAngle + offset)
|
||||
* nvmath::translation_mat4(radius, 0.f, 0.f);
|
||||
inst.transformIT = nvmath::transpose(nvmath::invert(inst.transform));
|
||||
}
|
||||
~~~~
|
||||
|
||||
Next, we update the buffer that describes the scene, which is used by the rasterizer to set each object's position, and also by the ray tracer to compute shading normals.
|
||||
~~~~ C++
|
||||
// Update the buffer
|
||||
vk::DeviceSize bufferSize = m_objInstance.size() * sizeof(ObjInstance);
|
||||
nvvkBuffer stagingBuffer = m_alloc.createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible);
|
||||
// Copy data to staging buffer
|
||||
auto* gInst = m_alloc.map(stagingBuffer);
|
||||
memcpy(gInst, m_objInstance.data(), bufferSize);
|
||||
m_alloc.unmap(stagingBuffer);
|
||||
// Copy staging buffer to the Scene Description buffer
|
||||
nvvkpp::SingleCommandBuffer genCmdBuf(m_device, m_graphicsQueueIndex);
|
||||
vk::CommandBuffer cmdBuf = genCmdBuf.createCommandBuffer();
|
||||
cmdBuf.copyBuffer(stagingBuffer.buffer, m_sceneDesc.buffer, vk::BufferCopy(0, 0, bufferSize));
|
||||
m_debug.endLabel(cmdBuf);
|
||||
genCmdBuf.flushCommandBuffer(cmdBuf);
|
||||
m_alloc.destroy(stagingBuffer);
|
||||
}
|
||||
~~~~
|
||||
<script type="preformatted">
|
||||
|
||||
!!! Note:
|
||||
We could have used `cmdBuf.updateBuffer<ObjInstance>(m_sceneDesc.buffer, 0, m_objInstance)` to
|
||||
update the buffer, but this function only works for buffers with less than 65,536 bytes. If we had 2000 Wuson models, this
|
||||
call wouldn't work.
|
||||
|
||||
## Loop Animation
|
||||
In `main()`, just before the main loop, add a variable to hold the start time.
|
||||
We will use this time in our animation function.
|
||||
|
||||
~~~~ C++
|
||||
auto start = std::chrono::system_clock::now();
|
||||
~~~~
|
||||
|
||||
Inside the `while` loop, just before calling `appBase.prepareFrame()`, invoke the animation function.
|
||||
|
||||
~~~~ C++
|
||||
std::chrono::duration<float> diff = std::chrono::system_clock::now() - start;
|
||||
helloVk.animationInstances(diff.count());
|
||||
~~~~
|
||||
|
||||
If you run the application, the Wuson models will be running in a circle when using the rasterizer, but
|
||||
they will still be at their original positions in the ray traced version. We will need to update the TLAS for this.
|
||||
|
||||
|
||||
# Update TLAS
|
||||
|
||||
Since we want to update the transformation matrices in the TLAS, we need to keep some of the objects used to create it.
|
||||
|
||||
First, move the vector of `nvvkpp::RaytracingBuilder::Instance` objects from `HelloVulkan::createTopLevelAS()` to the
|
||||
`HelloVulkan` class.
|
||||
~~~~ C++
|
||||
std::vector<nvvkpp::RaytracingBuilder::Instance> m_tlas;
|
||||
~~~~
|
||||
|
||||
Make sure to rename it to `m_tlas`, instead of `tlas`.
|
||||
|
||||
One important point is that we need to set the TLAS build flags to allow updates, by adding the`vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate` flag.
|
||||
This is absolutely needed, since otherwise the TLAS cannot be updated.
|
||||
|
||||
~~~~ C++
|
||||
void HelloVulkan::createTopLevelAS()
|
||||
{
|
||||
m_tlas.reserve(m_objInstance.size());
|
||||
for(int i = 0; i < static_cast<int>(m_objInstance.size()); i++)
|
||||
{
|
||||
nvvkpp::RaytracingBuilder::Instance rayInst;
|
||||
rayInst.transform = m_objInstance[i].transform; // Position of the instance
|
||||
rayInst.instanceId = i; // gl_InstanceID
|
||||
rayInst.blasId = m_objInstance[i].objIndex;
|
||||
rayInst.hitGroupId = m_objInstance[i].hitgroup;
|
||||
rayInst.flags = vk::GeometryInstanceFlagBitsKHR::eTriangleCullDisable;
|
||||
m_tlas.emplace_back(rayInst);
|
||||
}
|
||||
m_rtBuilder.buildTlas(m_tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace
|
||||
| vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate);
|
||||
}
|
||||
~~~~
|
||||
|
||||
Back in `HelloVulkan::animationInstances()`, we need to copy the new computed transformation
|
||||
matrices to the vector of `nvvkpp::RaytracingBuilder::Instance` objects.
|
||||
|
||||
In the `for` loop, add at the end
|
||||
|
||||
~~~~ C++
|
||||
nvvkpp::RaytracingBuilder::Instance& tinst = m_tlas[wusonIdx];
|
||||
tinst.transform = inst.transform;
|
||||
~~~~
|
||||
|
||||
The last point is to call the update at the end of the function.
|
||||
|
||||
~~~~ C++
|
||||
m_rtBuilder.updateTlasMatrices(m_tlas);
|
||||
~~~~
|
||||
|
||||

|
||||
|
||||
## nvvkpp::RaytracingBuilder::updateTlasMatrices (Implementation)
|
||||
|
||||
We currently use `nvvkpp::RaytracingBuilder` to update the matrices for convenience, but
|
||||
this could be done more efficiently if one kept some of the buffer and memory references. Using a
|
||||
memory allocator, such as the one described in the [Many Objects Tutorial](vkrt_tuto_instances.md.htm),
|
||||
could also be an alternative for avoiding multiple reallocations. Here's the implementation of `nvvkpp::RaytracingBuilder::updateTlasMatrices`.
|
||||
|
||||
### Staging Buffer
|
||||
|
||||
As in the rasterizer, the data needs to be staged before it can be copied to the buffer used for
|
||||
building the TLAS.
|
||||
|
||||
~~~~ C++
|
||||
void updateTlasMatrices(const std::vector<Instance>& instances)
|
||||
{
|
||||
VkDeviceSize bufferSize = instances.size() * sizeof(vk::AccelerationStructureInstanceKHR);
|
||||
// Create a staging buffer on the host to upload the new instance data
|
||||
nvvkBuffer stagingBuffer = m_alloc.createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc,
|
||||
#if defined(ALLOC_VMA)
|
||||
VmaMemoryUsage::VMA_MEMORY_USAGE_CPU_TO_GPU
|
||||
#else
|
||||
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
||||
#endif
|
||||
);
|
||||
|
||||
// Copy the instance data into the staging buffer
|
||||
auto* gInst = reinterpret_cast<vk::AccelerationStructureInstanceKHR*>(m_alloc.map(stagingBuffer));
|
||||
for(int i = 0; i < instances.size(); i++)
|
||||
{
|
||||
gInst[i] = instanceToVkGeometryInstanceKHR(instances[i]);
|
||||
}
|
||||
m_alloc.unmap(stagingBuffer);
|
||||
~~~~
|
||||
|
||||
### Scratch Memory
|
||||
Building the TLAS always needs scratch memory, and so we need to request it. If
|
||||
we hadn't set the `eAllowUpdate` flag, the returned size would be zero and the rest of the code
|
||||
would fail.
|
||||
~~~~ C++
|
||||
// Compute the amount of scratch memory required by the AS builder to update the TLAS
|
||||
vk::AccelerationStructureMemoryRequirementsInfoKHR memoryRequirementsInfo{
|
||||
vk::AccelerationStructureMemoryRequirementsTypeKHR::eUpdateScratch,
|
||||
vk::AccelerationStructureBuildTypeKHR::eDevice, m_tlas.as.accel};
|
||||
vk::DeviceSize scratchSize =
|
||||
m_device.getAccelerationStructureMemoryRequirementsKHR(memoryRequirementsInfo).memoryRequirements.size;
|
||||
// Allocate the scratch buffer
|
||||
nvvkBuffer scratchBuffer = m_alloc.createBuffer(scratchSize, vk::BufferUsageFlagBits::eRayTracingKHR
|
||||
| vk::BufferUsageFlagBits::eShaderDeviceAddress);
|
||||
vk::DeviceAddress scratchAddress = m_device.getBufferAddress({scratchBuffer.buffer});
|
||||
~~~~
|
||||
|
||||
### Update the Buffer
|
||||
In a new command buffer, we copy the staging buffer to the device buffer and
|
||||
add a barrier to make sure the memory finishes copying before updating the TLAS.
|
||||
|
||||
~~~~ C++
|
||||
// Update the instance buffer on the device side and build the TLAS
|
||||
nvvkpp::SingleCommandBuffer genCmdBuf(m_device, m_queueIndex);
|
||||
vk::CommandBuffer cmdBuf = genCmdBuf.createCommandBuffer();
|
||||
|
||||
cmdBuf.copyBuffer(stagingBuffer.buffer, m_instBuffer.buffer, vk::BufferCopy(0, 0, bufferSize));
|
||||
|
||||
vk::DeviceAddress instanceAddress = m_device.getBufferAddress(m_instBuffer.buffer);
|
||||
|
||||
// Make sure the copy of the instance buffer are copied before triggering the
|
||||
// acceleration structure build
|
||||
vk::MemoryBarrier barrier(vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eAccelerationStructureWriteKHR);
|
||||
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR,
|
||||
vk::DependencyFlags(), {barrier}, {}, {});
|
||||
~~~~
|
||||
|
||||
### Update Acceleration Structure
|
||||
|
||||
We update the TLAS using the same acceleration structure for source and
|
||||
destination to update it in place, and using the VK_TRUE parameter to trigger the update.
|
||||
|
||||
~~~~ C++
|
||||
vk::AccelerationStructureGeometryKHR topASGeometry{vk::GeometryTypeKHR::eInstances};
|
||||
topASGeometry.geometry.instances.arrayOfPointers = VK_FALSE;
|
||||
topASGeometry.geometry.instances.data = instanceAddress;
|
||||
const vk::AccelerationStructureGeometryKHR* pGeometry = &topASGeometry;
|
||||
|
||||
|
||||
vk::AccelerationStructureBuildGeometryInfoKHR topASInfo;
|
||||
topASInfo.setFlags(m_tlas.flags);
|
||||
topASInfo.setUpdate(VK_TRUE);
|
||||
topASInfo.setSrcAccelerationStructure(m_tlas.as.accel);
|
||||
topASInfo.setDstAccelerationStructure(m_tlas.as.accel);
|
||||
topASInfo.setGeometryArrayOfPointers(VK_FALSE);
|
||||
topASInfo.setGeometryCount(1);
|
||||
topASInfo.setPpGeometries(&pGeometry);
|
||||
topASInfo.setScratchData(scratchAddress);
|
||||
|
||||
uint32_t nbInstances = (uint32_t)instances.size();
|
||||
vk::AccelerationStructureBuildOffsetInfoKHR buildOffsetInfo = {nbInstances, 0, 0, 0};
|
||||
const vk::AccelerationStructureBuildOffsetInfoKHR* pBuildOffsetInfo = &buildOffsetInfo;
|
||||
|
||||
// Update the acceleration structure. Note the VK_TRUE parameter to trigger the update,
|
||||
// and the existing TLAS being passed and updated in place
|
||||
cmdBuf.buildAccelerationStructureKHR(1, &topASInfo, &pBuildOffsetInfo);
|
||||
genCmdBuf.flushCommandBuffer(cmdBuf);
|
||||
~~~~
|
||||
|
||||
### Cleanup
|
||||
|
||||
Finally, we release all temporary buffers.
|
||||
|
||||
~~~~ C++
|
||||
m_alloc.destroy(scratchBuffer);
|
||||
m_alloc.destroy(stagingBuffer);
|
||||
}
|
||||
~~~~
|
||||
|
||||
# BLAS Animation
|
||||
|
||||
In the previous chapter, we updated the transformation matrices. In this one we will modify vertices in a compute shader.
|
||||
|
||||
## Adding a Sphere
|
||||
|
||||
In this chapter, we will animate a sphere. In `main.cpp`, set up the scene like this:
|
||||
|
||||
~~~~ C++
|
||||
helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths),
|
||||
nvmath::scale_mat4(nvmath::vec3f(2.f, 1.f, 2.f)));
|
||||
helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths));
|
||||
HelloVulkan::ObjInstance inst = helloVk.m_objInstance.back();
|
||||
for(int i = 0; i < 5; i++)
|
||||
helloVk.m_objInstance.push_back(inst);
|
||||
helloVk.loadModel(nvh::findFile("media/scenes/sphere.obj", defaultSearchPaths));
|
||||
~~~~
|
||||
|
||||
Because we now have a new instance, we have to adjust the calculation of the number of Wuson models in `HelloVulkan::animationInstances()`.
|
||||
|
||||
~~~~ C++
|
||||
const int32_t nbWuson = static_cast<int32_t>(m_objInstance.size() - 2);
|
||||
~~~~
|
||||
|
||||
## Compute Shader
|
||||
|
||||
The compute shader will update the vertices in-place.
|
||||
|
||||
Add all of the following members to the `HelloVulkan` class:
|
||||
|
||||
~~~~ C++
|
||||
void createCompDescriptors();
|
||||
void updateCompDescriptors(nvvkBuffer& vertex);
|
||||
void createCompPipelines();
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> m_compDescSetLayoutBind;
|
||||
vk::DescriptorPool m_compDescPool;
|
||||
vk::DescriptorSetLayout m_compDescSetLayout;
|
||||
vk::DescriptorSet m_compDescSet;
|
||||
vk::Pipeline m_compPipeline;
|
||||
vk::PipelineLayout m_compPipelineLayout;
|
||||
~~~~
|
||||
|
||||
The compute shader will work on a single `VertexObj` buffer.
|
||||
|
||||
~~~~ C++
|
||||
void HelloVulkan::createCompDescriptors()
|
||||
{
|
||||
m_compDescSetLayoutBind.emplace_back(vk::DescriptorSetLayoutBinding(
|
||||
0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute));
|
||||
|
||||
m_compDescSetLayout = nvvkpp::util::createDescriptorSetLayout(m_device, m_compDescSetLayoutBind);
|
||||
m_compDescPool = nvvkpp::util::createDescriptorPool(m_device, m_compDescSetLayoutBind, 1);
|
||||
m_compDescSet = nvvkpp::util::createDescriptorSet(m_device, m_compDescPool, m_compDescSetLayout);
|
||||
}
|
||||
~~~~
|
||||
|
||||
`updateCompDescriptors` will set the set the descriptor to the buffer of `VertexObj` objects to which the animation will be applied.
|
||||
|
||||
~~~~ C++
|
||||
void HelloVulkan::updateCompDescriptors(nvvkBuffer& vertex)
|
||||
{
|
||||
std::vector<vk::WriteDescriptorSet> writes;
|
||||
vk::DescriptorBufferInfo dbiUnif{vertex.buffer, 0, VK_WHOLE_SIZE};
|
||||
writes.emplace_back(
|
||||
nvvkpp::util::createWrite(m_compDescSet, m_compDescSetLayoutBind[0], &dbiUnif));
|
||||
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
||||
}
|
||||
~~~~
|
||||
|
||||
The compute pipeline will consist of a simple shader and a push constant, which will be used
|
||||
to set the animation time.
|
||||
|
||||
~~~~ C++
|
||||
void HelloVulkan::createCompPipelines()
|
||||
{
|
||||
// pushing time
|
||||
vk::PushConstantRange push_constants = {vk::ShaderStageFlagBits::eCompute, 0, sizeof(float)};
|
||||
vk::PipelineLayoutCreateInfo layout_info{{}, 1, &m_compDescSetLayout, 1, &push_constants};
|
||||
m_compPipelineLayout = m_device.createPipelineLayout(layout_info);
|
||||
vk::ComputePipelineCreateInfo computePipelineCreateInfo{{}, {}, m_compPipelineLayout};
|
||||
|
||||
computePipelineCreateInfo.stage =
|
||||
nvvkpp::util::loadShader(m_device,
|
||||
nvh::loadFile("shaders/anim.comp.spv", true, defaultSearchPaths),
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
m_compPipeline = m_device.createComputePipelines({}, computePipelineCreateInfo, nullptr)[0];
|
||||
m_device.destroy(computePipelineCreateInfo.stage.module);
|
||||
}
|
||||
~~~~
|
||||
|
||||
Finally, destroy the resources in `HelloVulkan::destroyResources()`:
|
||||
|
||||
~~~~ C++
|
||||
m_device.destroy(m_compDescPool);
|
||||
m_device.destroy(m_compDescSetLayout);
|
||||
m_device.destroy(m_compPipeline);
|
||||
m_device.destroy(m_compPipelineLayout);
|
||||
~~~~
|
||||
|
||||
## `anim.comp`
|
||||
The compute shader will be simple. We need to add a new shader file, `anim.comp`, to the `shaders` filter in the solution.
|
||||
|
||||
This will move each vertex up and down over time.
|
||||
|
||||
~~~~ C++
|
||||
#version 460
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_EXT_scalar_block_layout : enable
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
#include "wavefront.glsl"
|
||||
|
||||
layout(binding = 0, scalar) buffer Vertices
|
||||
{
|
||||
Vertex v[];
|
||||
}
|
||||
vertices;
|
||||
|
||||
layout(push_constant) uniform shaderInformation
|
||||
{
|
||||
float iTime;
|
||||
}
|
||||
pushc;
|
||||
|
||||
void main()
|
||||
{
|
||||
Vertex v0 = vertices.v[gl_GlobalInvocationID.x];
|
||||
|
||||
// Compute vertex position
|
||||
const float PI = 3.14159265;
|
||||
const float signY = (v0.pos.y >= 0 ? 1 : -1);
|
||||
const float radius = length(v0.pos.xz);
|
||||
const float argument = pushc.iTime * 4 + radius * PI;
|
||||
const float s = sin(argument);
|
||||
v0.pos.y = signY * abs(s) * 0.5;
|
||||
|
||||
// Compute normal
|
||||
if(radius == 0.0f)
|
||||
{
|
||||
v0.nrm = vec3(0.0f, signY, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float c = cos(argument);
|
||||
const float xzFactor = -PI * s * c;
|
||||
const float yFactor = 2.0f * signY * radius * abs(s);
|
||||
v0.nrm = normalize(vec3(v0.pos.x * xzFactor, yFactor, v0.pos.z * xzFactor));
|
||||
}
|
||||
|
||||
vertices.v[gl_GlobalInvocationID.x] = v0;
|
||||
}
|
||||
~~~~
|
||||
|
||||
## Animating the Object
|
||||
|
||||
First add the declaration of the animation function in `HelloVulkan`:
|
||||
|
||||
~~~~ C++
|
||||
void animationObject(float time);
|
||||
~~~~
|
||||
|
||||
The implementation only pushes the current time and calls the compute shader (`dispatch`).
|
||||
|
||||
~~~~ C++
|
||||
void HelloVulkan::animationObject(float time)
|
||||
{
|
||||
ObjModel& model = m_objModel[2];
|
||||
|
||||
updateCompDescriptors(model.vertexBuffer);
|
||||
|
||||
nvvkpp::SingleCommandBuffer genCmdBuf(m_device, m_graphicsQueueIndex);
|
||||
vk::CommandBuffer cmdBuf = genCmdBuf.createCommandBuffer();
|
||||
|
||||
cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_compPipeline);
|
||||
cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, m_compPipelineLayout, 0,
|
||||
{m_compDescSet}, {});
|
||||
cmdBuf.pushConstants(m_compPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(float),
|
||||
&time);
|
||||
cmdBuf.dispatch(model.nbVertices, 1, 1);
|
||||
genCmdBuf.flushCommandBuffer(cmdBuf);
|
||||
}
|
||||
~~~~
|
||||
|
||||
## Invoking Animation
|
||||
|
||||
In `main.cpp`, after the other resource creation functions, add the creation functions for the compute shader.
|
||||
|
||||
~~~~ C++
|
||||
helloVk.createCompDescriptors();
|
||||
helloVk.createCompPipelines();
|
||||
~~~~
|
||||
|
||||
In the rendering loop, after the call to `animationInstances`, call the object animation function.
|
||||
|
||||
~~~~ C++
|
||||
helloVk.animationObject(diff.count());
|
||||
~~~~
|
||||
|
||||
!!! Note
|
||||
At this point, the object should be animated when using the rasterizer, but should still be immobile when using the ray tracer.
|
||||
|
||||
## Update BLAS
|
||||
|
||||
In `nvvkpp::RaytracingBuilder` in `raytrace_vkpp.hpp`, we can add a function to update a BLAS whose vertex buffer was previously updated. This function is very similar to the one used for instances, but in this case, there is no buffer transfer to do.
|
||||
|
||||
~~~~ C++
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
// Refit the BLAS from updated buffers
|
||||
//
|
||||
void updateBlas(uint32_t blasIdx)
|
||||
{
|
||||
Blas& blas = m_blas[blasIdx];
|
||||
|
||||
// Compute the amount of scratch memory required by the AS builder to update the TLAS
|
||||
vk::AccelerationStructureMemoryRequirementsInfoKHR memoryRequirementsInfo{
|
||||
vk::AccelerationStructureMemoryRequirementsTypeKHR::eUpdateScratch,
|
||||
vk::AccelerationStructureBuildTypeKHR::eDevice, blas.as.accel};
|
||||
vk::DeviceSize scratchSize =
|
||||
m_device.getAccelerationStructureMemoryRequirementsKHR(memoryRequirementsInfo).memoryRequirements.size;
|
||||
// Allocate the scratch buffer
|
||||
nvvkBuffer scratchBuffer = m_alloc.createBuffer(scratchSize, vk::BufferUsageFlagBits::eRayTracingKHR
|
||||
| vk::BufferUsageFlagBits::eShaderDeviceAddress);
|
||||
vk::DeviceAddress scratchAddress = m_device.getBufferAddress({scratchBuffer.buffer});
|
||||
|
||||
|
||||
const vk::AccelerationStructureGeometryKHR* pGeometry = blas.asGeometry.data();
|
||||
vk::AccelerationStructureBuildGeometryInfoKHR asInfo{vk::AccelerationStructureTypeKHR::eBottomLevel};
|
||||
asInfo.setFlags(blas.flags);
|
||||
asInfo.setUpdate(VK_TRUE);
|
||||
asInfo.setSrcAccelerationStructure(blas.as.accel);
|
||||
asInfo.setDstAccelerationStructure(blas.as.accel);
|
||||
asInfo.setGeometryArrayOfPointers(VK_FALSE);
|
||||
asInfo.setGeometryCount((uint32_t)blas.asGeometry.size());
|
||||
asInfo.setPpGeometries(&pGeometry);
|
||||
asInfo.setScratchData(scratchAddress);
|
||||
|
||||
|
||||
std::vector<const vk::AccelerationStructureBuildOffsetInfoKHR*> pBuildOffset(blas.asBuildOffsetInfo.size());
|
||||
for(size_t i = 0; i < blas.asBuildOffsetInfo.size(); i++)
|
||||
pBuildOffset[i] = &blas.asBuildOffsetInfo[i];
|
||||
|
||||
|
||||
// Update the instance buffer on the device side and build the TLAS
|
||||
nvvkpp::SingleCommandBuffer genCmdBuf(m_device, m_queueIndex);
|
||||
vk::CommandBuffer cmdBuf = genCmdBuf.createCommandBuffer();
|
||||
|
||||
// Update the acceleration structure. Note the VK_TRUE parameter to trigger the update,
|
||||
// and the existing BLAS being passed and updated in place
|
||||
cmdBuf.buildAccelerationStructureKHR(asInfo, pBuildOffset);
|
||||
|
||||
|
||||
genCmdBuf.flushCommandBuffer(cmdBuf);
|
||||
m_alloc.destroy(scratchBuffer);
|
||||
}
|
||||
~~~~
|
||||
|
||||
The previous function (`updateBlas`) uses geometry information stored in `m_blas`.
|
||||
To be able to re-use this information, we need to keep the structure of `nvvkpp::RaytracingBuilderKHR::Blas` objects
|
||||
used for its creation.
|
||||
|
||||
Move the `nvvkpp::RaytracingBuilderKHR::Blas` vector from `HelloVulkan::createBottomLevelAS()` to the `HelloVulkan` class, renaming it to `m_blas`.
|
||||
|
||||
~~~~ C++
|
||||
std::vector<nvvkpp::RaytracingBuilderKHR::Blas> m_blas;
|
||||
~~~~
|
||||
|
||||
As with the TLAS, the BLAS needs to allow updates. We will also enable the
|
||||
`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR` flag, which indicates that the given
|
||||
acceleration structure build should prioritize build time over trace performance.
|
||||
|
||||
~~~~ C++
|
||||
void HelloVulkan::createBottomLevelAS()
|
||||
{
|
||||
// BLAS - Storing each primitive in a geometry
|
||||
m_blas.reserve(m_objModel.size());
|
||||
for(const auto & obj : m_objModel)
|
||||
{
|
||||
auto blas = objectToVkGeometryKHR(obj);
|
||||
|
||||
// We could add more geometry in each BLAS, but we add only one for now
|
||||
m_blas.push_back(blas);
|
||||
}
|
||||
m_rtBuilder.buildBlas(m_blas, vk::BuildAccelerationStructureFlagBitsKHR::eAllowUpdate
|
||||
| vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastBuild);
|
||||
}
|
||||
~~~~
|
||||
|
||||
Finally, we can add a line at the end of `HelloVulkan::animationObject()` to update the BLAS.
|
||||
|
||||
~~~~ C++
|
||||
m_rtBuilder.updateBlas(2);
|
||||
~~~~
|
||||
|
||||

|
||||
|
||||
</script>
|
||||
|
||||
# Final Code
|
||||
|
||||
You can find the final code in the folder [ray_tracing_animation](https://github.com/nvpro-samples/vk_raytracing_tutorial/tree/master/ray_tracing_animation)
|
||||
|
||||
|
||||
<!-- Markdeep: -->
|
||||
<link rel="stylesheet" href="vkrt_tutorial.css?">
|
||||
<script> window.markdeepOptions = { tocStyle: "medium" };</script>
|
||||
<script src="markdeep.min.js" charset="utf-8"></script>
|
||||
<script src="https://developer.nvidia.com/sites/default/files/akamai/gameworks/whitepapers/markdeep.min.js" charset="utf-8"></script>
|
||||
<script>
|
||||
window.alreadyProcessedMarkdeep || (document.body.style.visibility = "visible")
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue