Refactoring

This commit is contained in:
mklefrancois 2021-09-07 09:42:21 +02:00
parent 3e399adf0a
commit d90ce79135
222 changed files with 9045 additions and 5734 deletions

View file

@ -1,6 +1,5 @@
# G-Buffer and Ambient Occlusion - Tutorial
![](images/ray_tracing_ao.png)
## Tutorial ([Setup](../docs/setup.md))
@ -10,17 +9,16 @@ This is an extension of the Vulkan ray tracing [tutorial](https://nvpro-samples.
This extension to the tutorial is showing how G-Buffers from the fragment shader, can be used in a compute shader to cast ambient occlusion rays using
ray queries [(GLSL_EXT_ray_query)](https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_query.txt).
We are using some previous extensions of the tutorial to create this one.
We are using some previous extensions of the tutorial to create this one.
* The usage of `ray query` is from [ray_tracing_rayquery](../ray_tracing_rayquery)
* The notion of accumulated frames, is comming from [ray_tracing_jitter_cam](../ray_tracing_jitter_cam)
* The creation and dispatch of compute shader was inspired from [ray_tracing_animation](../ray_tracing_animation)
## Workflow
The fragment shader no longer just writes to an RGBA buffer for the colored image, but also writes to a G-buffer the position and normal for each fragment.
Then a compute shader takes the G-buffer and sends random ambient occlusion rays into the hemisphere formed by position and normal.
The fragment shader no longer just writes to an RGBA buffer for the colored image, but also writes to a G-buffer the position and normal for each fragment.
Then a compute shader takes the G-buffer and sends random ambient occlusion rays into the hemisphere formed by position and normal.
![](images/Hemisphere_sampling.png)
@ -32,11 +30,9 @@ The following are the buffers are they can be seen in [NSight Graphics](https://
![](images/buffers.png)
## G-Buffer
The framework was already writing to G-Buffers, but was writing to a single `VK_FORMAT_R32G32B32A32_SFLOAT` buffer. In the function `HelloVulkan::createOffscreenRender()`, we will add the creation of two new buffers. One `VK_FORMAT_R32G32B32A32_SFLOAT` to store the position and normal and one `VK_FORMAT_R32_SFLOAT` for the ambient occlusion.
The framework was already writing to G-Buffers, but was writing to a single `VK_FORMAT_R32G32B32A32_SFLOAT` buffer. In the function `HelloVulkan::createOffscreenRender()`, we will add the creation of two new buffers. One `VK_FORMAT_R32G32B32A32_SFLOAT` to store the position and normal and one `VK_FORMAT_R32_SFLOAT` for the ambient occlusion.
~~~~ C++
// The G-Buffer (rgba32f) - position(xyz) / normal(w-compressed)
@ -77,7 +73,7 @@ The render pass for the fragment shader will need two color buffers, therefore w
std::vector<VkImageView> attachments = {m_offscreenColor.descriptor.imageView,
m_gBuffer.descriptor.imageView,
m_offscreenDepth.descriptor.imageView};
```
```
### Renderpass
@ -87,7 +83,7 @@ This means that the renderpass in `main()` will have to be modified as well. The
std::array<VkClearValue, 3> clearValues{};
```
Since the clear value will be re-used by the offscreen (3 attachments) and the post/UI (2 attachments), we will set the clear values in each section.
Since the clear value will be re-used by the offscreen (3 attachments) and the post/UI (2 attachments), we will set the clear values in each section.
```
// Offscreen render pass
@ -110,21 +106,20 @@ We are omitting the code to compress and decompress the XYZ normal to and from a
```
// Outgoing
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec4 outGbuffer;
layout(location = 0) out vec4 o_color;
layout(location = 1) out vec4 o_gbuffer;
...
outGbuffer.rgba = vec4(worldPos, uintBitsToFloat(CompressUnitVec(N)));
o_gbuffer.rgba = vec4(worldPos, uintBitsToFloat(CompressUnitVec(N)));
```
## Ray Tracing
As for the [ray_tracing_rayquery](../ray_tracing_rayquery) sample, we use the VK_KHR_acceleration_structure extension to generate the ray tracing acceleration structure, while the ray tracing itself is carried out in a compute shader. This section remains unchanged compared to the rayquery example.
As for the [ray_tracing_rayquery](../ray_tracing_rayquery) sample, we use the VK_KHR_acceleration_structure extension to generate the ray tracing acceleration structure, while the ray tracing itself is carried out in a compute shader. This section remains unchanged compared to the rayquery example.
## Compute Shader
## Compute Shader
The compute shader will take the G-Buffer containing the position and normal and will randomly shot rays in the hemisphere defined by the normal.
The compute shader will take the G-Buffer containing the position and normal and will randomly shot rays in the hemisphere defined by the normal.
### Descriptor
@ -147,7 +142,8 @@ void HelloVulkan::createCompDescriptors()
~~~
### Descriptor Update
The function `updateCompDescriptors()` is done separately from the descriptor, because it can happen that some resources
The function `updateCompDescriptors()` is done separately from the descriptor, because it can happen that some resources
are re-created, therefore their address isn't valid and we need to set those values back to the decriptors. For example,
when resizing the window and the G-Buffer and AO buffer are resized.
@ -169,16 +165,14 @@ void HelloVulkan::updateCompDescriptors()
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
~~~~
~~~~
### Pipeline
The creation of the pipeline is identical to the animation tutorial, but we will push a structure to the pushConstant
The creation of the pipeline is identical to the animation tutorial, but we will push a structure to the pushConstant
instead of a single float.
The information we will push, will allow us to play with the AO algorithm.
The information we will push, will allow us to play with the AO algorithm.
~~~~ C++
struct AoControl
@ -191,13 +185,12 @@ struct AoControl
};
~~~~
### Dispatch Compute
The first thing we are doing in the `runCompute` is to call `updateFrame()` (see [jitter cam](../ray_tracing_jitter_cam)).
The first thing we are doing in the `runCompute` is to call `updateFrame()` (see [jitter cam](../ray_tracing_jitter_cam)).
This sets the current frame index, which allows us to accumulate AO samples over time.
Next, we are adding a `VkImageMemoryBarrier` to be sure the G-Buffer image is ready to be read from the compute shader.
Next, we are adding a `VkImageMemoryBarrier` to be sure the G-Buffer image is ready to be read from the compute shader.
~~~~ C++
// Adding a barrier to be sure the fragment has finished writing to the G-Buffer
@ -215,7 +208,7 @@ Next, we are adding a `VkImageMemoryBarrier` to be sure the G-Buffer image is re
VK_DEPENDENCY_DEVICE_GROUP_BIT, 0, nullptr, 0, nullptr, 1, &imgMemBarrier);
~~~~
Folowing is the call to dispatch the compute shader
Folowing is the call to dispatch the compute shader
~~~~ C++
// Preparing for the compute shader
@ -231,7 +224,7 @@ Folowing is the call to dispatch the compute shader
vkCmdDispatch(cmdBuf, (m_size.width + (GROUP_SIZE - 1)) / GROUP_SIZE, (m_size.height + (GROUP_SIZE - 1)) / GROUP_SIZE, 1);
~~~~
Then we are adding a final barrier to make sure the compute shader is done
Then we are adding a final barrier to make sure the compute shader is done
writing the AO so that the fragment shader (post) can use it.
~~~~ C++
@ -247,7 +240,7 @@ writing the AO so that the fragment shader (post) can use it.
The following functions were added to tell which frame we are rendering.
The function `updateFrame()` is called only once per frame, and we are doing this in runCompute()/
And the `resetFrame()` should be called whenever the image is changed, like in `onResize()` or
And the `resetFrame()` should be called whenever the image is changed, like in `onResize()` or
after modifying the GUI related to the AO.
~~~~ C++
@ -275,14 +268,15 @@ void HelloVulkan::resetFrame()
{
m_frame = -1;
}
~~~~
~~~~
## Compute Shader
## Compute Shader (code)
The compute shader, which can be found under [ao.comp](shaders/ao.comp) is using the [ray query](https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_query.txt) extension.
The first thing in `main()` is to check if the invocation is not exceeding the size of the image.
~~~~
~~~~C
void main()
{
float occlusion = 0.0;
@ -291,34 +285,33 @@ void main()
// Check if not outside boundaries
if(gl_GlobalInvocationID.x >= size.x || gl_GlobalInvocationID.y >= size.y)
return;
~~~~
~~~~
The seed of the random number sequence is initialized using the TEA algorithm, while the random number themselves will be generated using PCG.
The seed of the random number sequence is initialized using the TEA algorithm, while the random number themselves will be generated using PCG.
This is a fine when many random numbers are generated from this seed, but tea isn't a random
number generator and if you use only one sample per pixel, you will see correlation and the AO will not look fine because it won't
sample uniformly the entire hemisphere. This could be resolved if the seed was kept over frame, but for this example, we will use
number generator and if you use only one sample per pixel, you will see correlation and the AO will not look fine because it won't
sample uniformly the entire hemisphere. This could be resolved if the seed was kept over frame, but for this example, we will use
this simple technique.
~~~~
~~~~C
// Initialize the random number
uint seed = tea(size.x * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x, frame_number);
~~~~
Secondly, we are retrieving the position and normal stored in the fragment shader.
~~~~
~~~~C
// Retrieving position and normal
vec4 gBuffer = imageLoad(inImage, ivec2(gl_GlobalInvocationID.xy));
~~~~
The G-Buffer was cleared and we will sample the hemisphere only if a fragment was rendered. In `w`
we stored the compressed normal, which is nonzero only if a normal was actually stored into the pixel.
we stored the compressed normal, which is nonzero only if a normal was actually stored into the pixel.
Note that while this compression introduces some level of quantization, it does not result in visible artifacts in this example.
The `OffsetRay` can be found in [raycommon.glsl](shaders/raycommon.glsl), and was taken from [Ray Tracing Gems, Ch. 6](http://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.7.pdf). This is a convenient way to avoid finding manually the appropriate minimum offset.
~~~~
~~~~C
// Shooting rays only if a fragment was rendered
if(gBuffer != vec4(0))
{
@ -333,15 +326,15 @@ The `OffsetRay` can be found in [raycommon.glsl](shaders/raycommon.glsl), and wa
From the normal, we generate the basis (tangent and bitangent) which will be used for sampling.
The function `compute_default_basis` is also in [raycommon.glsl](shaders/raycommon.glsl)
~~~~
~~~~C
// Finding the basis (tangent and bitangent) from the normal
vec3 n, tangent, bitangent;
compute_default_basis(normal, tangent, bitangent);
~~~~
~~~~
Then we are sampling the hemisphere `rtao_samples` time, using a [cosine weighted sampling](https://people.cs.kuleuven.be/~philip.dutre/GI/)
~~~~
~~~~C
// Sampling hemiphere n-time
for(int i = 0; i < rtao_samples; i++)
{
@ -359,16 +352,16 @@ Then we are sampling the hemisphere `rtao_samples` time, using a [cosine weighte
}
~~~~
The function `TraceRay` is a very simple way to send a shadow ray using ray query.
The function `TraceRay` is a very simple way to send a shadow ray using ray query.
For any type of shadow, we don't care which object we hit as long as the ray hit something
before maximum length. For this, we can set the flag to `gl_RayFlagsTerminateOnFirstHitEXT`.
But there is a case where we may want to know the distance of the hit from the closest hit, in this case
the flag is set to `gl_RayFlagsNoneEXT`.
But there is a case where we may want to know the distance of the hit from the closest hit, in this case
the flag is set to `gl_RayFlagsNoneEXT`.
The function returns 0 if it didn't hit anything or a value between 0 and 1, depending on how close the
The function returns 0 if it didn't hit anything or a value between 0 and 1, depending on how close the
hit was. It will return 1 if `rtao_distance_based == 0`
~~~~
~~~~C
//----------------------------------------------------------------------------
// Tracing a ray and returning the weight based on the distance of the hit
//
@ -401,7 +394,7 @@ float TraceRay(in rayQueryEXT rayQuery, in vec3 origin, in vec3 direction)
Similar to the camera jitter example, the result is stored at frame 0 and accumulate over time.
~~~~
~~~~C
// Writting out the AO
if(frame_number == 0)
{
@ -438,7 +431,7 @@ if(ImGui::CollapsingHeader("Ambient Occlusion"))
We have also have added `AoControl aoControl;` somwhere in main() and passing the information to the execution of the compute shader.
~~~~
~~~~C
// Rendering Scene
{
vkCmdBeginRenderPass(cmdBuf, &offscreenRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
@ -450,16 +443,16 @@ We have also have added `AoControl aoControl;` somwhere in main() and passing th
## Post shader
The post shader will combine the result of the fragment (color) and the result of the compute shader (ao).
In `createPostDescriptor` we will need to add the descriptor
The post shader will combine the result of the fragment (color) and the result of the compute shader (ao).
In `createPostDescriptor` we will need to add the descriptor
~~~~
~~~~C
m_postDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
~~~~
And the equivalent in `updatePostDescriptorSet()`
~~~~
~~~~C
writes.push_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer.descriptor));
~~~~
@ -467,16 +460,15 @@ writes.push_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer
Then in the fragment shader of the post process, we need to add the layout for the AO image
~~~~
~~~~C
layout(set = 0, binding = 1) uniform sampler2D aoTxt;
~~~~
And the image will now be returned as the following
And the image will now be returned as the following
~~~~
~~~~C
vec4 color = texture(noisyTxt, uv);
float ao = texture(aoTxt, uv).x;
fragColor = pow(color * ao, vec4(gamma));
~~~~

View file

@ -40,15 +40,6 @@
extern std::vector<std::string> defaultSearchPaths;
// Holding the camera matrices
struct CameraMatrices
{
nvmath::mat4f view;
nvmath::mat4f proj;
nvmath::mat4f viewInverse;
};
//--------------------------------------------------------------------------------------------------
// Keep the handle on the device
// Initialize the tool to do all our allocations: buffers, images
@ -68,14 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf)
{
// Prepare new UBO contents on host.
const float aspectRatio = m_size.width / static_cast<float>(m_size.height);
CameraMatrices hostUBO = {};
hostUBO.view = CameraManip.getMatrix();
hostUBO.proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f);
// hostUBO.proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK).
hostUBO.viewInverse = nvmath::invert(hostUBO.view);
GlobalUniforms hostUBO = {};
const auto& view = CameraManip.getMatrix();
const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f);
// proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK).
hostUBO.viewProj = proj * view;
hostUBO.viewInverse = nvmath::invert(view);
hostUBO.projInverse = nvmath::invert(proj);
// UBO on the device, and what stages access it.
VkBuffer deviceUBO = m_cameraMat.buffer;
VkBuffer deviceUBO = m_bGlobals.buffer;
auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR;
// Ensure that the modified UBO is not visible to previous frames.
@ -91,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf)
// Schedule the host-to-device upload. (hostUBO is copied into the cmd
// buffer so it is okay to deallocate when the function returns).
vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO);
vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO);
// Making sure the updated UBO will be visible.
VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
@ -111,12 +105,13 @@ void HelloVulkan::createDescriptorSetLayout()
{
auto nbTxt = static_cast<uint32_t>(m_textures.size());
// Camera matrices (binding = 0)
m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT);
// Scene description (binding = 1)
m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
// Textures (binding = 3)
m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT);
// Camera matrices
m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT);
// Obj descriptions
m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
// Textures
m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT);
m_descSetLayout = m_descSetLayoutBind.createLayout(m_device);
@ -132,11 +127,11 @@ void HelloVulkan::updateDescriptorSet()
std::vector<VkWriteDescriptorSet> writes;
// Camera matrices and scene description
VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif));
VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif));
VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc));
VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc));
// All texture samplers
std::vector<VkDescriptorImageInfo> diit;
@ -144,7 +139,7 @@ void HelloVulkan::updateDescriptorSet()
{
diit.emplace_back(texture.descriptor);
}
writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data()));
writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data()));
// Writing the information
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
@ -156,7 +151,7 @@ void HelloVulkan::updateDescriptorSet()
//
void HelloVulkan::createGraphicsPipeline()
{
VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)};
VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)};
// Creating the Pipeline Layout
VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
@ -221,30 +216,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform
model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags);
model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag);
model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag);
// Creates all textures found
uint32_t txtOffset = static_cast<uint32_t>(m_textures.size());
// Creates all textures found and find the offset for this model
auto txtOffset = static_cast<uint32_t>(m_textures.size());
createTextureImages(cmdBuf, loader.m_textures);
cmdBufGet.submitAndWait(cmdBuf);
m_alloc.finalizeAndReleaseStaging();
std::string objNb = std::to_string(m_objModel.size());
m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb).c_str()));
m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb).c_str()));
m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb).c_str()));
m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb).c_str()));
m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb)));
m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb)));
m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb)));
m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb)));
// Keeping transformation matrix of the instance
ObjInstance instance;
instance.objIndex = static_cast<uint32_t>(m_objModel.size());
instance.transform = transform;
instance.transformIT = nvmath::transpose(nvmath::invert(transform));
instance.txtOffset = txtOffset;
instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer);
instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer);
instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer);
instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer);
instance.transform = transform;
instance.objIndex = static_cast<uint32_t>(m_objModel.size());
m_instances.push_back(instance);
// Creating information for device access
ObjDesc desc;
desc.txtOffset = txtOffset;
desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer);
desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer);
desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer);
desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer);
// Keeping the obj host model and device description
m_objModel.emplace_back(model);
m_objInstance.emplace_back(instance);
m_objDesc.emplace_back(desc);
}
@ -254,9 +254,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform
//
void HelloVulkan::createUniformBuffer()
{
m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
m_debug.setObjectName(m_cameraMat.buffer, "cameraMat");
m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
m_debug.setObjectName(m_bGlobals.buffer, "Globals");
}
//--------------------------------------------------------------------------------------------------
@ -265,15 +265,15 @@ void HelloVulkan::createUniformBuffer()
// - Transformation
// - Offset for texture
//
void HelloVulkan::createSceneDescriptionBuffer()
void HelloVulkan::createObjDescriptionBuffer()
{
nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex);
auto cmdBuf = cmdGen.createCommandBuffer();
m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
cmdGen.submitAndWait(cmdBuf);
m_alloc.finalizeAndReleaseStaging();
m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc");
m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs");
}
//--------------------------------------------------------------------------------------------------
@ -359,8 +359,8 @@ void HelloVulkan::destroyResources()
vkDestroyDescriptorPool(m_device, m_descPool, nullptr);
vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr);
m_alloc.destroy(m_cameraMat);
m_alloc.destroy(m_sceneDesc);
m_alloc.destroy(m_bGlobals);
m_alloc.destroy(m_bObjDesc);
for(auto& m : m_objModel)
{
@ -415,14 +415,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf)
vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr);
for(int i = 0; i < m_objInstance.size(); ++i)
for(const HelloVulkan::ObjInstance& inst : m_instances)
{
auto& inst = m_objInstance[i];
auto& model = m_objModel[inst.objIndex];
m_pushConstant.instanceId = i; // Telling which instance is drawn
auto& model = m_objModel[inst.objIndex];
m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn
m_pcRaster.modelMatrix = inst.transform;
vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(ObjPushConstant), &m_pushConstant);
sizeof(PushConstantRaster), &m_pcRaster);
vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset);
vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0);
@ -441,10 +441,12 @@ void HelloVulkan::onResize(int /*w*/, int /*h*/)
resetFrame();
}
//////////////////////////////////////////////////////////////////////////
// Post-processing
//////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------------------------
// Creating an offscreen frame buffer and the associated render pass
//
@ -649,7 +651,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model)
// Describe buffer as array of VertexObj.
VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR};
triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data.
triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data.
triangles.vertexData.deviceAddress = vertexAddress;
triangles.vertexStride = sizeof(VertexObj);
// Describe index data (32-bit unsigned int)
@ -698,19 +700,22 @@ void HelloVulkan::createBottomLevelAS()
m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR);
}
//--------------------------------------------------------------------------------------------------
//
//
void HelloVulkan::createTopLevelAS()
{
std::vector<VkAccelerationStructureInstanceKHR> tlas;
tlas.reserve(m_objInstance.size());
for(uint32_t i = 0; i < static_cast<uint32_t>(m_objInstance.size()); i++)
tlas.reserve(m_instances.size());
for(const HelloVulkan::ObjInstance& inst : m_instances)
{
VkAccelerationStructureInstanceKHR rayInst;
rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance
rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT
rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex);
VkAccelerationStructureInstanceKHR rayInst{};
rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance
rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT
rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex);
rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0
rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects
rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
rayInst.mask = 0xFF;
tlas.emplace_back(rayInst);
}
m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR);

View file

@ -24,6 +24,7 @@
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/memallocator_dma_vk.hpp"
#include "nvvk/resourceallocator_vk.hpp"
#include "shaders/host_device.h"
// #VKRay
#include "nvvk/raytraceKHR_vk.hpp"
@ -55,7 +56,7 @@ public:
void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1));
void updateDescriptorSet();
void createUniformBuffer();
void createSceneDescriptionBuffer();
void createObjDescriptionBuffer();
void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector<std::string>& textures);
void updateUniformBuffer(const VkCommandBuffer& cmdBuf);
void onResize(int /*w*/, int /*h*/) override;
@ -73,32 +74,27 @@ public:
nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material'
};
// Instance of the OBJ
struct ObjInstance
{
nvmath::mat4f transform{1}; // Position of the instance
nvmath::mat4f transformIT{1}; // Inverse transpose
uint32_t objIndex{0}; // Reference to the `m_objModel`
uint32_t txtOffset{0}; // Offset in `m_textures`
VkDeviceAddress vertices;
VkDeviceAddress indices;
VkDeviceAddress materials;
VkDeviceAddress materialIndices;
nvmath::mat4f transform; // Matrix of the instance
uint32_t objIndex{0}; // Model index reference
};
// Information pushed at each draw call
struct ObjPushConstant
{
nvmath::vec3f lightPosition{10.f, 15.f, 8.f};
int instanceId{0}; // To retrieve the transformation matrix
float lightIntensity{100.f};
int lightType{0}; // 0: point, 1: infinite
PushConstantRaster m_pcRaster{
{1}, // Identity matrix
{10.f, 15.f, 8.f}, // light position
0, // instance Id
100.f, // light intensity
0 // light type
};
ObjPushConstant m_pushConstant;
// Array of objects and instances in the scene
std::vector<ObjModel> m_objModel;
std::vector<ObjInstance> m_objInstance;
std::vector<ObjModel> m_objModel; // Model on host
std::vector<ObjDesc> m_objDesc; // Model description for device access
std::vector<ObjInstance> m_instances; // Scene model instances
// Graphic pipeline
VkPipelineLayout m_pipelineLayout;
@ -108,8 +104,8 @@ public:
VkDescriptorSetLayout m_descSetLayout;
VkDescriptorSet m_descSet;
nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices
nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances
nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices
nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions
std::vector<nvvk::Texture> m_textures; // vector of all textures of the scene
@ -118,7 +114,7 @@ public:
nvvk::DebugUtil m_debug; // Utility to name objects
// #Post
// #Post - Draw the rendered image on a quad using a tonemapper
void createOffscreenRender();
void createPostPipeline();
void createPostDescriptor();

View file

@ -57,12 +57,12 @@ void renderUI(HelloVulkan& helloVk)
ImGuiH::CameraWidget();
if(ImGui::CollapsingHeader("Light"))
{
ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0);
ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0);
ImGui::SameLine();
ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1);
ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.lightType, 1);
ImGui::SliderFloat3("Position", &helloVk.m_pushConstant.lightPosition.x, -20.f, 20.f);
ImGui::SliderFloat("Intensity", &helloVk.m_pushConstant.lightIntensity, 0.f, 150.f);
ImGui::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f);
ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f);
}
}
@ -89,6 +89,7 @@ int main(int argc, char** argv)
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(SAMPLE_WIDTH, SAMPLE_HEIGHT, PROJECT_NAME, nullptr, nullptr);
// Setup camera
CameraManip.setWindowSize(SAMPLE_WIDTH, SAMPLE_HEIGHT);
CameraManip.setLookat(nvmath::vec3f(5, 4, -4), nvmath::vec3f(0, 1, 0), nvmath::vec3f(0, 1, 0));
@ -166,7 +167,7 @@ int main(int argc, char** argv)
helloVk.createDescriptorSetLayout();
helloVk.createGraphicsPipeline();
helloVk.createUniformBuffer();
helloVk.createSceneDescriptionBuffer();
helloVk.createObjDescriptionBuffer();
// #VKRay
helloVk.initRayTracing();

View file

@ -30,62 +30,58 @@
#include "wavefront.glsl"
layout(push_constant) uniform shaderInformation
layout(push_constant) uniform _PushConstantRaster
{
vec3 lightPosition;
uint instanceId;
float lightIntensity;
int lightType;
}
pushC;
PushConstantRaster pcRaster;
};
// clang-format off
// Incoming
layout(location = 1) in vec2 fragTexCoord;
layout(location = 2) in vec3 fragNormal;
layout(location = 3) in vec3 viewDir;
layout(location = 4) in vec3 worldPos;
layout(location = 1) in vec3 i_worldPos;
layout(location = 2) in vec3 i_worldNrm;
layout(location = 3) in vec3 i_viewDir;
layout(location = 4) in vec2 i_texCoord;
// Outgoing
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec4 outGbuffer;
layout(location = 0) out vec4 o_color;
layout(location = 1) out vec4 o_gbuffer;
layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object
layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices
layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object
layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle
layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc;
layout(binding = 2) uniform sampler2D[] textureSamplers;
layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc;
layout(binding = eTextures) uniform sampler2D[] textureSamplers;
// clang-format on
void main()
{
// Material of the object
SceneDesc objResource = sceneDesc.i[pushC.instanceId];
ObjDesc objResource = objDesc.i[pcRaster.objIndex];
MatIndices matIndices = MatIndices(objResource.materialIndexAddress);
Materials materials = Materials(objResource.materialAddress);
int matIndex = matIndices.i[gl_PrimitiveID];
WaveFrontMaterial mat = materials.m[matIndex];
vec3 N = normalize(fragNormal);
vec3 N = normalize(i_worldNrm);
// Vector toward light
vec3 L;
float lightDistance;
float lightIntensity = pushC.lightIntensity;
if(pushC.lightType == 0)
float lightIntensity = pcRaster.lightIntensity;
if(pcRaster.lightType == 0)
{
vec3 lDir = pushC.lightPosition - worldPos;
vec3 lDir = pcRaster.lightPosition - i_worldPos;
float d = length(lDir);
lightIntensity = pushC.lightIntensity / (d * d);
lightIntensity = pcRaster.lightIntensity / (d * d);
L = normalize(lDir);
lightDistance = d;
}
else
{
L = normalize(pushC.lightPosition - vec3(0));
L = normalize(pcRaster.lightPosition - vec3(0));
lightDistance = 10000;
}
@ -95,7 +91,7 @@ void main()
diffuse = vec3(1);
// if(mat.textureId >= 0)
// {
// int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset;
// int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset;
// uint txtId = txtOffset + mat.textureId;
// vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz;
// diffuse *= diffuseTxt;
@ -104,9 +100,8 @@ void main()
// Specular
vec3 specular = vec3(0); //computeSpecular(mat, viewDir, L, N);
lightIntensity = 1;
// Result
outColor = vec4(lightIntensity * (diffuse + specular), 1);
outGbuffer.rgba = vec4(worldPos, uintBitsToFloat(CompressUnitVec(N)));
o_color = vec4(lightIntensity * (diffuse + specular), 1);
o_gbuffer.rgba = vec4(i_worldPos, uintBitsToFloat(CompressUnitVec(N)));
}

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef COMMON_HOST_DEVICE
#define COMMON_HOST_DEVICE
#ifdef __cplusplus
#include "nvmath/nvmath.h"
// GLSL Type
using vec2 = nvmath::vec2f;
using vec3 = nvmath::vec3f;
using vec4 = nvmath::vec4f;
using mat4 = nvmath::mat4f;
using uint = unsigned int;
#endif
// clang-format off
#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL
#define START_BINDING(a) enum a {
#define END_BINDING() }
#else
#define START_BINDING(a) const uint
#define END_BINDING()
#endif
START_BINDING(SceneBindings)
eGlobals = 0, // Global uniform containing camera matrices
eObjDescs = 1, // Access to the object descriptions
eTextures = 2 // Access to textures
END_BINDING();
START_BINDING(RtxBindings)
eTlas = 0, // Top-level acceleration structure
eOutImage = 1 // Ray tracer output image
END_BINDING();
// clang-format on
// Information of a obj model when referenced in a shader
struct ObjDesc
{
int txtOffset; // Texture index offset in the array of textures
uint64_t vertexAddress; // Address of the Vertex buffer
uint64_t indexAddress; // Address of the index buffer
uint64_t materialAddress; // Address of the material buffer
uint64_t materialIndexAddress; // Address of the triangle material index buffer
};
// Uniform buffer set at each frame
struct GlobalUniforms
{
mat4 viewProj; // Camera view * projection
mat4 viewInverse; // Camera inverse view matrix
mat4 projInverse; // Camera inverse projection matrix
};
// Push constant structure for the raster
struct PushConstantRaster
{
mat4 modelMatrix; // matrix of the instance
vec3 lightPosition;
uint objIndex;
float lightIntensity;
int lightType;
};
// Push constant structure for the ray tracer
struct PushConstantRay
{
vec4 clearColor;
vec3 lightPosition;
float lightIntensity;
int lightType;
};
struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device
{
vec3 pos;
vec3 nrm;
vec3 color;
vec2 texCoord;
};
struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device
{
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 transmittance;
vec3 emission;
float shininess;
float ior; // index of refraction
float dissolve; // 1 == opaque; 0 == fully transparent
int illum; // illumination model (see http://www.fileformat.info/format/material/)
int textureId;
};
#endif

View file

@ -26,38 +26,26 @@
#include "wavefront.glsl"
// clang-format off
layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc;
// clang-format on
layout(binding = 0) uniform UniformBufferObject
layout(binding = 0) uniform _GlobalUniforms
{
mat4 view;
mat4 proj;
mat4 viewI;
}
ubo;
GlobalUniforms uni;
};
layout(push_constant) uniform shaderInformation
layout(push_constant) uniform _PushConstantRaster
{
vec3 lightPosition;
uint instanceId;
float lightIntensity;
int lightType;
}
pushC;
PushConstantRaster pcRaster;
};
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec3 inColor;
layout(location = 3) in vec2 inTexCoord;
layout(location = 0) in vec3 i_position;
layout(location = 1) in vec3 i_normal;
layout(location = 2) in vec3 i_color;
layout(location = 3) in vec2 i_texCoord;
//layout(location = 0) flat out int matIndex;
layout(location = 1) out vec2 fragTexCoord;
layout(location = 2) out vec3 fragNormal;
layout(location = 3) out vec3 viewDir;
layout(location = 4) out vec3 worldPos;
layout(location = 1) out vec3 o_worldPos;
layout(location = 2) out vec3 o_worldNrm;
layout(location = 3) out vec3 o_viewDir;
layout(location = 4) out vec2 o_texCoord;
out gl_PerVertex
{
@ -67,16 +55,12 @@ out gl_PerVertex
void main()
{
mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo;
mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT;
vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1));
vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1));
o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0));
o_viewDir = vec3(o_worldPos - origin);
o_texCoord = i_texCoord;
o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal;
worldPos = vec3(objMatrix * vec4(inPosition, 1.0));
viewDir = vec3(worldPos - origin);
fragTexCoord = inTexCoord;
fragNormal = vec3(objMatrixIT * vec4(inNormal, 0.0));
// matIndex = inMatID;
gl_Position = ubo.proj * ubo.view * vec4(worldPos, 1.0);
gl_Position = uni.viewProj * vec4(o_worldPos, 1.0);
}

View file

@ -17,41 +17,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
struct Vertex
{
vec3 pos;
vec3 nrm;
vec3 color;
vec2 texCoord;
};
struct WaveFrontMaterial
{
vec3 ambient;
vec3 diffuse;
vec3 specular;
vec3 transmittance;
vec3 emission;
float shininess;
float ior; // index of refraction
float dissolve; // 1 == opaque; 0 == fully transparent
int illum; // illumination model (see http://www.fileformat.info/format/material/)
int textureId;
};
struct SceneDesc
{
mat4 transfo;
mat4 transfoIT;
int objId;
int txtOffset;
uint64_t vertexAddress;
uint64_t indexAddress;
uint64_t materialAddress;
uint64_t materialIndexAddress;
};
#include "host_device.h"
vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal)
{