Bulk update NvPro-Samples 03/18/21

This commit is contained in:
Mathias Heyer 2021-03-18 15:00:48 -07:00
parent a2b80ab819
commit 2da588b7e6
113 changed files with 3529 additions and 1508 deletions

View file

@ -0,0 +1,80 @@
#*****************************************************************************
# Copyright 2020 NVIDIA Corporation. All rights reserved.
#*****************************************************************************
cmake_minimum_required(VERSION 3.9.6 FATAL_ERROR)
#--------------------------------------------------------------------------------------------------
# Project setting
get_filename_component(PROJNAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
SET(PROJNAME vk_${PROJNAME}_KHR)
project(${PROJNAME} LANGUAGES C CXX)
message(STATUS "-------------------------------")
message(STATUS "Processing Project ${PROJNAME}:")
#--------------------------------------------------------------------------------------------------
# C++ target and defines
set(CMAKE_CXX_STANDARD 17)
add_executable(${PROJNAME})
_add_project_definitions(${PROJNAME})
#--------------------------------------------------------------------------------------------------
# Source files for this project
#
file(GLOB SOURCE_FILES *.cpp *.hpp *.inl *.h *.c)
file(GLOB EXTRA_COMMON ${TUTO_KHR_DIR}/common/*.*)
list(APPEND COMMON_SOURCE_FILES ${EXTRA_COMMON})
include_directories(${TUTO_KHR_DIR}/common)
#--------------------------------------------------------------------------------------------------
# GLSL to SPIR-V custom build
compile_glsl_directory(
SRC "${CMAKE_CURRENT_SOURCE_DIR}/shaders"
DST "${CMAKE_CURRENT_SOURCE_DIR}/spv"
VULKAN_TARGET "vulkan1.2"
DEPENDENCY ${VULKAN_BUILD_DEPENDENCIES}
)
#--------------------------------------------------------------------------------------------------
# Sources
target_sources(${PROJNAME} PUBLIC ${SOURCE_FILES} ${HEADER_FILES})
target_sources(${PROJNAME} PUBLIC ${COMMON_SOURCE_FILES})
target_sources(${PROJNAME} PUBLIC ${PACKAGE_SOURCE_FILES})
target_sources(${PROJNAME} PUBLIC ${GLSL_SOURCES} ${GLSL_HEADERS})
#--------------------------------------------------------------------------------------------------
# Sub-folders in Visual Studio
#
source_group("Common" FILES ${COMMON_SOURCE_FILES} ${PACKAGE_SOURCE_FILES})
source_group("Sources" FILES ${SOURCE_FILES})
source_group("Headers" FILES ${HEADER_FILES})
source_group("Shaders Src" FILES ${GLSL_SOURCES})
source_group("Shaders Hdr" FILES ${GLSL_HEADERS})
#--------------------------------------------------------------------------------------------------
# Linkage
#
target_link_libraries(${PROJNAME} ${PLATFORM_LIBRARIES} shared_sources)
foreach(DEBUGLIB ${LIBRARIES_DEBUG})
target_link_libraries(${PROJNAME} debug ${DEBUGLIB})
endforeach(DEBUGLIB)
foreach(RELEASELIB ${LIBRARIES_OPTIMIZED})
target_link_libraries(${PROJNAME} optimized ${RELEASELIB})
endforeach(RELEASELIB)
#--------------------------------------------------------------------------------------------------
# copies binaries that need to be put next to the exe files (ZLib, etc.)
#
_finalize_target( ${PROJNAME} )
install(FILES ${SPV_OUTPUT} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}/spv")
install(FILES ${SPV_OUTPUT} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}/spv")

491
ray_tracing_ao/README.md Normal file
View file

@ -0,0 +1,491 @@
# G-Buffer and Ambient Occlusion - Tutorial
![](images/ray_tracing_ao.png)
## Tutorial ([Setup](../docs/setup.md))
This is an extension of the Vulkan ray tracing [tutorial](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR).
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.
* 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.
![](images/Hemisphere_sampling.png)
The compute shader reads and stores into the AO buffer. It reads the previous information to cumulate it over time and store the accumulated value. Finally, the post shader is slightly modified to include the AO buffer and multiply its value by the RGBA color frame before tonemapping and writing to the frame buffer.
![](images/Tuto_Ao_workflow.png)
The following are the buffers are they can be seen in [NSight Graphics](https://developer.nvidia.com/nsight-graphics).
![](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::eR32G32B32A32Sfloat` to store the position and normal and one `vk::Format::eR32Sfloat` for the ambient occlusion.
~~~~ C++
// The G-Buffer (rgba32f) - position(xyz) / normal(w-compressed)
{
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, vk::Format::eR32G32B32A32Sfloat,
vk::ImageUsageFlagBits::eColorAttachment
| vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eStorage);
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
m_gBuffer = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
m_gBuffer.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
m_debug.setObjectName(m_gBuffer.image, "G-Buffer");
}
// The ambient occlusion result (r32)
{
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, vk::Format::eR32Sfloat,
vk::ImageUsageFlagBits::eColorAttachment
| vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eStorage);
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
m_aoBuffer = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
m_aoBuffer.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
m_debug.setObjectName(m_aoBuffer.image, "aoBuffer");
}
~~~~
Add the `nvvk::Texture` members for `m_gBuffer` and `m_aoBuffer` to the class and to the destructor function, and don't forget to set the right image layout.
The render pass for the fragment shader will need two color buffers, therefore we need to modify the offscreen framebuffer attachments.
```
// Creating the frame buffer for offscreen
std::vector<vk::ImageView> attachments = {m_offscreenColor.descriptor.imageView,
m_gBuffer.descriptor.imageView,
m_offscreenDepth.descriptor.imageView};
```
### Renderpass
This means that the renderpass in `main()` will have to be modified as well. The clear color will need to have 3 entries (2 color + 1 depth)
```
vk::ClearValue clearValues[3];
```
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
{
clearValues[1].setColor(std::array<float, 4>{0, 0, 0, 0});
clearValues[2].setDepthStencil({1.0f, 0});
```
```
// 2nd rendering pass: tone mapper, UI
{
clearValues[1].setDepthStencil({1.0f, 0});
```
### Fragment shader
The fragment shader can now write into two different textures.
We are omitting the code to compress and decompress the XYZ normal to and from a single unsigned integer, but you can find the code in [raycommon.glsl](shaders/raycommon.glsl)
```
// Outgoing
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec4 outGbuffer;
...
outGbuffer.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.
## 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.
### Descriptor
The shader takes two inputs, the G-Buffer and the TLAS, and has one output, the AO buffer:
~~~ C++
//--------------------------------------------------------------------------------------------------
// Compute shader descriptor
//
void HelloVulkan::createCompDescriptors()
{
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [in] G-Buffer
0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute));
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [out] AO
1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute));
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [in] TLAS
2, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eCompute));
m_compDescSetLayout = m_compDescSetLayoutBind.createLayout(m_device);
m_compDescPool = m_compDescSetLayoutBind.createPool(m_device, 1);
m_compDescSet = nvvk::allocateDescriptorSet(m_device, m_compDescPool, m_compDescSetLayout);
}
~~~
### Descriptor Update
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.
~~~ C++
//--------------------------------------------------------------------------------------------------
// Setting up the values to the descriptors
//
void HelloVulkan::updateCompDescriptors()
{
std::vector<vk::WriteDescriptorSet> writes;
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 0, &m_gBuffer.descriptor));
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 1, &m_aoBuffer.descriptor));
vk::AccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure();
vk::WriteDescriptorSetAccelerationStructureKHR descASInfo{1, &tlas};
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 2, &descASInfo));
m_device.updateDescriptorSets(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
instead of a single float.
The information we will push, will allow us to play with the AO algorithm.
~~~~ C++
struct AoControl
{
float rtao_radius{2.0f}; // Length of the ray
int rtao_samples{4}; // Nb samples at each iteration
float rtao_power{2.0f}; // Darkness is stronger for more hits
int rtao_distance_based{1}; // Attenuate based on distance
int frame{0}; // Current frame
};
~~~~
### Dispatch Compute
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 `vk::ImageMemoryBarrier` 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
// before the compute shader is using the buffer
vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
vk::ImageMemoryBarrier imgMemBarrier;
imgMemBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
imgMemBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
imgMemBarrier.setImage(m_gBuffer.image);
imgMemBarrier.setOldLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setNewLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setSubresourceRange(range);
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader,
vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlagBits::eDeviceGroup, {}, {}, {imgMemBarrier});
~~~~
Folowing is the call to dispatch the compute shader
~~~~ C++
// Preparing for the compute shader
cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_compPipeline);
cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, m_compPipelineLayout, 0,
{m_compDescSet}, {});
// Sending the push constant information
aoControl.frame = m_frame;
cmdBuf.pushConstants(m_compPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0,
sizeof(AoControl), &aoControl);
// Dispatching the shader
cmdBuf.dispatch((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
writing the AO so that the fragment shader (post) can use it.
~~~~ C++
// Adding a barrier to be sure the compute shader has finished
// writing to the AO buffer before the post shader is using it
imgMemBarrier.setImage(m_aoBuffer.image);
imgMemBarrier.setOldLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setNewLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setSubresourceRange(range);
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eFragmentShader,
vk::DependencyFlagBits::eDeviceGroup, {}, {}, {imgMemBarrier});
~~~~
## Update Frame
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
after modifying the GUI related to the AO.
~~~~ C++
//--------------------------------------------------------------------------------------------------
// If the camera matrix has changed, resets the frame otherwise, increments frame.
//
void HelloVulkan::updateFrame()
{
static nvmath::mat4f refCamMatrix;
static float fov = 0;
auto& m = CameraManip.getMatrix();
auto f = CameraManip.getFov();
if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0 || f != fov)
{
resetFrame();
refCamMatrix = m;
fov = f;
}
m_frame++;
}
void HelloVulkan::resetFrame()
{
m_frame = -1;
}
~~~~
## Compute Shader
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.
~~~~
void main()
{
float occlusion = 0.0;
ivec2 size = imageSize(inImage);
// 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.
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
this simple technique.
~~~~
// 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.
~~~~
// 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.
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.
~~~~
// Shooting rays only if a fragment was rendered
if(gBuffer != vec4(0))
{
vec3 origin = gBuffer.xyz;
vec3 normal = decompress_unit_vec(floatBitsToUint(gBuffer.w));
vec3 direction;
// Move origin slightly away from the surface to avoid self-occlusion
origin = OffsetRay(origin, normal);
~~~~
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)
~~~~
// 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/)
~~~~
// Sampling hemiphere n-time
for(int i = 0; i < rtao_samples; i++)
{
// Cosine sampling
float r1 = rnd(seed);
float r2 = rnd(seed);
float sq = sqrt(1.0 - r2);
float phi = 2 * M_PI * r1;
vec3 direction = vec3(cos(phi) * sq, sin(phi) * sq, sqrt(r2));
direction = direction.x * tangent + direction.y * bitangent + direction.z * normal;
// Initializes a ray query object but does not start traversal
rayQueryEXT rayQuery;
occlusion += TraceRay(rayQuery, origin, direction);
}
~~~~
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`.
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`
~~~~
//----------------------------------------------------------------------------
// Tracing a ray and returning the weight based on the distance of the hit
//
float TraceRay(in rayQueryEXT rayQuery, in vec3 origin, in vec3 direction)
{
uint flags = gl_RayFlagsNoneEXT;
if(rtao_distance_based == 0)
flags = gl_RayFlagsTerminateOnFirstHitEXT;
rayQueryInitializeEXT(rayQuery, topLevelAS, flags, 0xFF, origin, 0.0f, direction, rtao_radius);
// Start traversal: return false if traversal is complete
while(rayQueryProceedEXT(rayQuery))
{
}
// Returns type of committed (true) intersection
if(rayQueryGetIntersectionTypeEXT(rayQuery, true) != gl_RayQueryCommittedIntersectionNoneEXT)
{
// Got an intersection == Shadow
if(rtao_distance_based == 0)
return 1;
float length = 1 - (rayQueryGetIntersectionTEXT(rayQuery, true) / rtao_radius);
return length; // * length;
}
return 0;
}
~~~~
Similar to the camera jitter example, the result is stored at frame 0 and accumulate over time.
~~~~
// Writting out the AO
if(frame_number == 0)
{
imageStore(outImage, ivec2(gl_GlobalInvocationID.xy), vec4(occlusion));
}
else
{
// Accumulating over time
float old_ao = imageLoad(outImage, ivec2(gl_GlobalInvocationID.xy)).x;
float new_result = mix(old_ao, occlusion, 1.0f / float(frame_number));
imageStore(outImage, ivec2(gl_GlobalInvocationID.xy), vec4(new_result));
}
}
## IMGUI addition
In `main()`, we have added some controls to modify the behavior of how the AO is computed. Immediately after the call to `renderUI`,
the following code as added.
~~~~ C++
ImGui::SetNextTreeNodeOpen(true, ImGuiCond_Once);
if(ImGui::CollapsingHeader("Ambient Occlusion"))
{
bool changed{false};
changed |= ImGui::SliderFloat("Radius", &aoControl.rtao_radius, 0, 5);
changed |= ImGui::SliderInt("Rays per Pixel", &aoControl.rtao_samples, 1, 64);
changed |= ImGui::SliderFloat("Power", &aoControl.rtao_power, 1, 5);
changed |= ImGui::Checkbox("Distanced Based", (bool*)&aoControl.rtao_distance_based);
if(changed)
helloVk.resetFrame();
}
~~~~
We have also have added `AoControl aoControl;` somwhere in main() and passing the information to the execution of the compute shader.
~~~~
// Rendering Scene
{
cmdBuf.beginRenderPass(offscreenRenderPassBeginInfo, vk::SubpassContents::eInline);
helloVk.rasterize(cmdBuf);
cmdBuf.endRenderPass();
helloVk.runCompute(cmdBuf, aoControl);
}
~~~~
## 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
~~~~
m_postDescSetLayoutBind.addBinding(vkDS(1, vkDT::eCombinedImageSampler, 1, vkSS::eFragment));
~~~~
And the equivalent in `updatePostDescriptorSet`
~~~~
writes.push_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer.descriptor));
~~~~
### Post.frag
Then in the fragment shader of the post process, we need to add the layout for the AO image
~~~~
layout(set = 0, binding = 1) uniform sampler2D aoTxt;
~~~~
And the image will now be returned as the following
~~~~
vec4 color = texture(noisyTxt, uv);
float ao = texture(aoTxt, uv).x;
fragColor = pow(color * ao, vec4(gamma));
~~~~

View file

@ -0,0 +1,913 @@
/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sstream>
#include <vulkan/vulkan.hpp>
extern std::vector<std::string> defaultSearchPaths;
#define STB_IMAGE_IMPLEMENTATION
#include "fileformats/stb_image.h"
#include "obj_loader.h"
#include "hello_vulkan.h"
#include "nvh//cameramanipulator.hpp"
#include "nvvk/descriptorsets_vk.hpp"
#include "nvvk/pipeline_vk.hpp"
#include "nvh/fileoperations.hpp"
#include "nvvk/commands_vk.hpp"
#include "nvvk/renderpasses_vk.hpp"
#include "nvvk/shaders_vk.hpp"
// Holding the camera matrices
struct CameraMatrices
{
nvmath::mat4f view;
nvmath::mat4f proj;
nvmath::mat4f viewInverse;
// #VKRay
nvmath::mat4f projInverse;
};
//--------------------------------------------------------------------------------------------------
// Keep the handle on the device
// Initialize the tool to do all our allocations: buffers, images
//
void HelloVulkan::setup(const vk::Instance& instance,
const vk::Device& device,
const vk::PhysicalDevice& physicalDevice,
uint32_t queueFamily)
{
AppBase::setup(instance, device, physicalDevice, queueFamily);
m_alloc.init(device, physicalDevice);
m_debug.setup(m_device);
m_offscreenDepthFormat = nvvk::findDepthFormat(physicalDevice);
}
//--------------------------------------------------------------------------------------------------
// Called at each frame to update the camera matrix
//
void HelloVulkan::updateUniformBuffer(const vk::CommandBuffer& 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);
// #VKRay
hostUBO.projInverse = nvmath::invert(hostUBO.proj);
// UBO on the device, and what stages access it.
vk::Buffer deviceUBO = m_cameraMat.buffer;
auto uboUsageStages =
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eRayTracingShaderKHR;
// Ensure that the modified UBO is not visible to previous frames.
vk::BufferMemoryBarrier beforeBarrier;
beforeBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderRead);
beforeBarrier.setDstAccessMask(vk::AccessFlagBits::eTransferWrite);
beforeBarrier.setBuffer(deviceUBO);
beforeBarrier.setOffset(0);
beforeBarrier.setSize(sizeof hostUBO);
cmdBuf.pipelineBarrier(uboUsageStages, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eDeviceGroup, {}, {beforeBarrier}, {});
// Schedule the host-to-device upload. (hostUBO is copied into the cmd
// buffer so it is okay to deallocate when the function returns).
cmdBuf.updateBuffer<CameraMatrices>(m_cameraMat.buffer, 0, hostUBO);
// Making sure the updated UBO will be visible.
vk::BufferMemoryBarrier afterBarrier;
afterBarrier.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite);
afterBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
afterBarrier.setBuffer(deviceUBO);
afterBarrier.setOffset(0);
afterBarrier.setSize(sizeof hostUBO);
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, uboUsageStages,
vk::DependencyFlagBits::eDeviceGroup, {}, {afterBarrier}, {});
}
//--------------------------------------------------------------------------------------------------
// Describing the layout pushed when rendering
//
void HelloVulkan::createDescriptorSetLayout()
{
using vkDS = vk::DescriptorSetLayoutBinding;
using vkDT = vk::DescriptorType;
using vkSS = vk::ShaderStageFlagBits;
uint32_t nbTxt = static_cast<uint32_t>(m_textures.size());
uint32_t nbObj = static_cast<uint32_t>(m_objModel.size());
// Camera matrices (binding = 0)
m_descSetLayoutBind.addBinding(vkDS(0, vkDT::eUniformBuffer, 1, vkSS::eVertex));
// Materials (binding = 1)
m_descSetLayoutBind.addBinding(vkDS(1, vkDT::eStorageBuffer, nbObj, vkSS::eFragment));
// Scene description (binding = 2)
m_descSetLayoutBind.addBinding(vkDS(2, vkDT::eStorageBuffer, 1, vkSS::eVertex | vkSS::eFragment));
// Textures (binding = 3)
m_descSetLayoutBind.addBinding(vkDS(3, vkDT::eCombinedImageSampler, nbTxt, vkSS::eFragment));
// Materials (binding = 4)
m_descSetLayoutBind.addBinding(vkDS(4, vkDT::eStorageBuffer, nbObj, vkSS::eFragment));
m_descSetLayout = m_descSetLayoutBind.createLayout(m_device);
m_descPool = m_descSetLayoutBind.createPool(m_device, 1);
m_descSet = nvvk::allocateDescriptorSet(m_device, m_descPool, m_descSetLayout);
}
//--------------------------------------------------------------------------------------------------
// Setting up the buffers in the descriptor set
//
void HelloVulkan::updateDescriptorSet()
{
std::vector<vk::WriteDescriptorSet> writes;
// Camera matrices and scene description
vk::DescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif));
vk::DescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE};
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 2, &dbiSceneDesc));
// All material buffers, 1 buffer per OBJ
std::vector<vk::DescriptorBufferInfo> dbiMat;
std::vector<vk::DescriptorBufferInfo> dbiMatIdx;
std::vector<vk::DescriptorBufferInfo> dbiVert;
std::vector<vk::DescriptorBufferInfo> dbiIdx;
for(auto& obj : m_objModel)
{
dbiMat.emplace_back(obj.matColorBuffer.buffer, 0, VK_WHOLE_SIZE);
dbiMatIdx.emplace_back(obj.matIndexBuffer.buffer, 0, VK_WHOLE_SIZE);
dbiVert.emplace_back(obj.vertexBuffer.buffer, 0, VK_WHOLE_SIZE);
dbiIdx.emplace_back(obj.indexBuffer.buffer, 0, VK_WHOLE_SIZE);
}
writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 1, dbiMat.data()));
writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 4, dbiMatIdx.data()));
// All texture samplers
std::vector<vk::DescriptorImageInfo> diit;
for(auto& texture : m_textures)
{
diit.emplace_back(texture.descriptor);
}
writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 3, diit.data()));
// Writing the information
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Creating the pipeline layout
//
void HelloVulkan::createGraphicsPipeline()
{
using vkSS = vk::ShaderStageFlagBits;
vk::PushConstantRange pushConstantRanges = {vkSS::eVertex | vkSS::eFragment, 0,
sizeof(ObjPushConstant)};
// Creating the Pipeline Layout
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
vk::DescriptorSetLayout descSetLayout(m_descSetLayout);
pipelineLayoutCreateInfo.setSetLayoutCount(1);
pipelineLayoutCreateInfo.setPSetLayouts(&descSetLayout);
pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges);
m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
// Creating the Pipeline
std::vector<std::string> paths = defaultSearchPaths;
nvvk::GraphicsPipelineGeneratorCombined gpb(m_device, m_pipelineLayout, m_offscreenRenderPass);
gpb.depthStencilState.depthTestEnable = true;
gpb.addShader(nvh::loadFile("spv/vert_shader.vert.spv", true, paths, true), vkSS::eVertex);
gpb.addShader(nvh::loadFile("spv/frag_shader.frag.spv", true, paths, true), vkSS::eFragment);
gpb.addBindingDescription({0, sizeof(VertexObj)});
gpb.addAttributeDescriptions({{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, pos)},
{1, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, nrm)},
{2, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, color)},
{3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexObj, texCoord)}});
vk::PipelineColorBlendAttachmentState res;
res.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG
| vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
gpb.addBlendAttachmentState(res);
m_graphicsPipeline = gpb.createPipeline();
m_debug.setObjectName(m_graphicsPipeline, "Graphics");
}
//--------------------------------------------------------------------------------------------------
// Loading the OBJ file and setting up all buffers
//
void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform)
{
using vkBU = vk::BufferUsageFlagBits;
LOGI("Loading File: %s \n", filename.c_str());
ObjLoader loader;
loader.loadModel(filename);
// Converting from Srgb to linear
for(auto& m : loader.m_materials)
{
m.ambient = nvmath::pow(m.ambient, 2.2f);
m.diffuse = nvmath::pow(m.diffuse, 2.2f);
m.specular = nvmath::pow(m.specular, 2.2f);
}
ObjInstance instance;
instance.objIndex = static_cast<uint32_t>(m_objModel.size());
instance.transform = transform;
instance.transformIT = nvmath::transpose(nvmath::invert(transform));
instance.txtOffset = static_cast<uint32_t>(m_textures.size());
ObjModel model;
model.nbIndices = static_cast<uint32_t>(loader.m_indices.size());
model.nbVertices = static_cast<uint32_t>(loader.m_vertices.size());
// Create the buffers on Device and copy vertices, indices and materials
nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex);
vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
model.vertexBuffer =
m_alloc.createBuffer(cmdBuf, loader.m_vertices,
vkBU::eVertexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress
| vkBU::eAccelerationStructureBuildInputReadOnlyKHR);
model.indexBuffer =
m_alloc.createBuffer(cmdBuf, loader.m_indices,
vkBU::eIndexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress
| vkBU::eAccelerationStructureBuildInputReadOnlyKHR);
model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, vkBU::eStorageBuffer);
model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, vkBU::eStorageBuffer);
// Creates all textures found
createTextureImages(cmdBuf, loader.m_textures);
cmdBufGet.submitAndWait(cmdBuf);
m_alloc.finalizeAndReleaseStaging();
std::string objNb = std::to_string(instance.objIndex);
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_objModel.emplace_back(model);
m_objInstance.emplace_back(instance);
}
//--------------------------------------------------------------------------------------------------
// Creating the uniform buffer holding the camera matrices
// - Buffer is host visible
//
void HelloVulkan::createUniformBuffer()
{
using vkBU = vk::BufferUsageFlagBits;
using vkMP = vk::MemoryPropertyFlagBits;
m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices),
vkBU::eUniformBuffer | vkBU::eTransferDst, vkMP::eDeviceLocal);
m_debug.setObjectName(m_cameraMat.buffer, "cameraMat");
}
//--------------------------------------------------------------------------------------------------
// Create a storage buffer containing the description of the scene elements
// - Which geometry is used by which instance
// - Transformation
// - Offset for texture
//
void HelloVulkan::createSceneDescriptionBuffer()
{
using vkBU = vk::BufferUsageFlagBits;
nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex);
auto cmdBuf = cmdGen.createCommandBuffer();
m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, vkBU::eStorageBuffer);
cmdGen.submitAndWait(cmdBuf);
m_alloc.finalizeAndReleaseStaging();
m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc");
}
//--------------------------------------------------------------------------------------------------
// Creating all textures and samplers
//
void HelloVulkan::createTextureImages(const vk::CommandBuffer& cmdBuf,
const std::vector<std::string>& textures)
{
using vkIU = vk::ImageUsageFlagBits;
vk::SamplerCreateInfo samplerCreateInfo{
{}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear};
samplerCreateInfo.setMaxLod(FLT_MAX);
vk::Format format = vk::Format::eR8G8B8A8Srgb;
// If no textures are present, create a dummy one to accommodate the pipeline layout
if(textures.empty() && m_textures.empty())
{
nvvk::Texture texture;
std::array<uint8_t, 4> color{255u, 255u, 255u, 255u};
vk::DeviceSize bufferSize = sizeof(color);
auto imgSize = vk::Extent2D(1, 1);
auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format);
// Creating the VKImage
nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, color.data(), imageCreateInfo);
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo);
texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo);
// The image format must be in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
nvvk::cmdBarrierImageLayout(cmdBuf, texture.image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eShaderReadOnlyOptimal);
m_textures.push_back(texture);
}
else
{
// Uploading all images
for(const auto& texture : textures)
{
std::stringstream o;
int texWidth, texHeight, texChannels;
o << "media/textures/" << texture;
std::string txtFile = nvh::findFile(o.str(), defaultSearchPaths, true);
stbi_uc* stbi_pixels =
stbi_load(txtFile.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
std::array<stbi_uc, 4> color{255u, 0u, 255u, 255u};
stbi_uc* pixels = stbi_pixels;
// Handle failure
if(!stbi_pixels)
{
texWidth = texHeight = 1;
texChannels = 4;
pixels = reinterpret_cast<stbi_uc*>(color.data());
}
vk::DeviceSize bufferSize = static_cast<uint64_t>(texWidth) * texHeight * sizeof(uint8_t) * 4;
auto imgSize = vk::Extent2D(texWidth, texHeight);
auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, vkIU::eSampled, true);
{
nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, pixels, imageCreateInfo);
nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels);
vk::ImageViewCreateInfo ivInfo =
nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo);
nvvk::Texture texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo);
m_textures.push_back(texture);
}
stbi_image_free(stbi_pixels);
}
}
}
//--------------------------------------------------------------------------------------------------
// Destroying all allocations
//
void HelloVulkan::destroyResources()
{
m_device.destroy(m_graphicsPipeline);
m_device.destroy(m_pipelineLayout);
m_device.destroy(m_descPool);
m_device.destroy(m_descSetLayout);
m_alloc.destroy(m_cameraMat);
m_alloc.destroy(m_sceneDesc);
for(auto& m : m_objModel)
{
m_alloc.destroy(m.vertexBuffer);
m_alloc.destroy(m.indexBuffer);
m_alloc.destroy(m.matColorBuffer);
m_alloc.destroy(m.matIndexBuffer);
}
for(auto& t : m_textures)
{
m_alloc.destroy(t);
}
//#Post
m_device.destroy(m_postPipeline);
m_device.destroy(m_postPipelineLayout);
m_device.destroy(m_postDescPool);
m_device.destroy(m_postDescSetLayout);
m_alloc.destroy(m_offscreenColor);
m_alloc.destroy(m_gBuffer);
m_alloc.destroy(m_aoBuffer);
m_alloc.destroy(m_offscreenDepth);
m_device.destroy(m_offscreenRenderPass);
m_device.destroy(m_offscreenFramebuffer);
// Compute
m_device.destroy(m_compDescPool);
m_device.destroy(m_compDescSetLayout);
m_device.destroy(m_compPipeline);
m_device.destroy(m_compPipelineLayout);
// #VKRay
m_rtBuilder.destroy();
}
//--------------------------------------------------------------------------------------------------
// Drawing the scene in raster mode
//
void HelloVulkan::rasterize(const vk::CommandBuffer& cmdBuf)
{
using vkPBP = vk::PipelineBindPoint;
using vkSS = vk::ShaderStageFlagBits;
vk::DeviceSize offset{0};
m_debug.beginLabel(cmdBuf, "Rasterize");
// Dynamic Viewport
cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)});
cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
// Drawing all triangles
cmdBuf.bindPipeline(vkPBP::eGraphics, m_graphicsPipeline);
cmdBuf.bindDescriptorSets(vkPBP::eGraphics, m_pipelineLayout, 0, {m_descSet}, {});
for(int i = 0; i < m_objInstance.size(); ++i)
{
auto& inst = m_objInstance[i];
auto& model = m_objModel[inst.objIndex];
m_pushConstant.instanceId = i; // Telling which instance is drawn
cmdBuf.pushConstants<ObjPushConstant>(m_pipelineLayout, vkSS::eVertex | vkSS::eFragment, 0,
m_pushConstant);
cmdBuf.bindVertexBuffers(0, {model.vertexBuffer.buffer}, {offset});
cmdBuf.bindIndexBuffer(model.indexBuffer.buffer, 0, vk::IndexType::eUint32);
cmdBuf.drawIndexed(model.nbIndices, 1, 0, 0, 0);
}
m_debug.endLabel(cmdBuf);
}
//--------------------------------------------------------------------------------------------------
// Handling resize of the window
//
void HelloVulkan::onResize(int /*w*/, int /*h*/)
{
createOffscreenRender();
updatePostDescriptorSet();
updateCompDescriptors();
resetFrame();
}
//////////////////////////////////////////////////////////////////////////
// Post-processing
//////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------------------------
// Creating an offscreen frame buffer and the associated render pass
//
void HelloVulkan::createOffscreenRender()
{
m_alloc.destroy(m_offscreenColor);
m_alloc.destroy(m_gBuffer);
m_alloc.destroy(m_aoBuffer);
m_alloc.destroy(m_offscreenDepth);
// Creating the color image
{
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenColorFormat,
vk::ImageUsageFlagBits::eColorAttachment
| vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eStorage);
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
m_offscreenColor = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
m_debug.setObjectName(m_offscreenColor.image, "offscreen");
}
// The G-Buffer (rgba32f) - position(xyz) / normal(w-compressed)
{
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, vk::Format::eR32G32B32A32Sfloat,
vk::ImageUsageFlagBits::eColorAttachment
| vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eStorage);
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
m_gBuffer = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
m_gBuffer.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
m_debug.setObjectName(m_gBuffer.image, "G-Buffer");
}
// The ambient occlusion result (r32)
{
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, vk::Format::eR32Sfloat,
vk::ImageUsageFlagBits::eColorAttachment
| vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eStorage);
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
m_aoBuffer = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
m_aoBuffer.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
m_debug.setObjectName(m_aoBuffer.image, "aoBuffer");
}
// Creating the depth buffer
auto depthCreateInfo =
nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat,
vk::ImageUsageFlagBits::eDepthStencilAttachment);
{
nvvk::Image image = m_alloc.createImage(depthCreateInfo);
vk::ImageViewCreateInfo depthStencilView;
depthStencilView.setViewType(vk::ImageViewType::e2D);
depthStencilView.setFormat(m_offscreenDepthFormat);
depthStencilView.setSubresourceRange({vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1});
depthStencilView.setImage(image.image);
m_offscreenDepth = m_alloc.createTexture(image, depthStencilView);
}
// Setting the image layout for both color and depth
{
nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex);
auto cmdBuf = genCmdBuf.createCommandBuffer();
nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenColor.image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eGeneral);
nvvk::cmdBarrierImageLayout(cmdBuf, m_gBuffer.image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eGeneral);
nvvk::cmdBarrierImageLayout(cmdBuf, m_aoBuffer.image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eGeneral);
nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eDepthStencilAttachmentOptimal,
vk::ImageAspectFlagBits::eDepth);
genCmdBuf.submitAndWait(cmdBuf);
}
// Creating a renderpass for the offscreen
if(!m_offscreenRenderPass)
{
m_offscreenRenderPass =
nvvk::createRenderPass(m_device,
{m_offscreenColorFormat, m_offscreenColorFormat}, // RGBA + G-Buffer
m_offscreenDepthFormat, 1, true, true, vk::ImageLayout::eGeneral,
vk::ImageLayout::eGeneral);
}
// Creating the frame buffer for offscreen
std::vector<vk::ImageView> attachments = {m_offscreenColor.descriptor.imageView,
m_gBuffer.descriptor.imageView,
m_offscreenDepth.descriptor.imageView};
m_device.destroy(m_offscreenFramebuffer);
vk::FramebufferCreateInfo info;
info.setRenderPass(m_offscreenRenderPass);
info.setAttachmentCount(static_cast<int>(attachments.size()));
info.setPAttachments(attachments.data());
info.setWidth(m_size.width);
info.setHeight(m_size.height);
info.setLayers(1);
m_offscreenFramebuffer = m_device.createFramebuffer(info);
}
//--------------------------------------------------------------------------------------------------
// The pipeline is how things are rendered, which shaders, type of primitives, depth test and more
//
void HelloVulkan::createPostPipeline()
{
// Push constants in the fragment shader
vk::PushConstantRange pushConstantRanges = {vk::ShaderStageFlagBits::eFragment, 0, sizeof(float)};
// Creating the pipeline layout
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
pipelineLayoutCreateInfo.setSetLayoutCount(1);
pipelineLayoutCreateInfo.setPSetLayouts(&m_postDescSetLayout);
pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges);
m_postPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
// Pipeline: completely generic, no vertices
nvvk::GraphicsPipelineGeneratorCombined pipelineGenerator(m_device, m_postPipelineLayout,
m_renderPass);
pipelineGenerator.addShader(nvh::loadFile("spv/passthrough.vert.spv", true, defaultSearchPaths,
true),
vk::ShaderStageFlagBits::eVertex);
pipelineGenerator.addShader(nvh::loadFile("spv/post.frag.spv", true, defaultSearchPaths, true),
vk::ShaderStageFlagBits::eFragment);
pipelineGenerator.rasterizationState.setCullMode(vk::CullModeFlagBits::eNone);
m_postPipeline = pipelineGenerator.createPipeline();
m_debug.setObjectName(m_postPipeline, "post");
}
//--------------------------------------------------------------------------------------------------
// The descriptor layout is the description of the data that is passed to the vertex or the
// fragment program.
//
void HelloVulkan::createPostDescriptor()
{
using vkDS = vk::DescriptorSetLayoutBinding;
using vkDT = vk::DescriptorType;
using vkSS = vk::ShaderStageFlagBits;
m_postDescSetLayoutBind.addBinding(vkDS(0, vkDT::eCombinedImageSampler, 1, vkSS::eFragment));
m_postDescSetLayoutBind.addBinding(vkDS(1, vkDT::eCombinedImageSampler, 1, vkSS::eFragment));
m_postDescSetLayout = m_postDescSetLayoutBind.createLayout(m_device);
m_postDescPool = m_postDescSetLayoutBind.createPool(m_device);
m_postDescSet = nvvk::allocateDescriptorSet(m_device, m_postDescPool, m_postDescSetLayout);
}
//--------------------------------------------------------------------------------------------------
// Update the output
//
void HelloVulkan::updatePostDescriptorSet()
{
std::vector<vk::WriteDescriptorSet> writes;
writes.push_back(
m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor));
writes.push_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer.descriptor));
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Draw a full screen quad with the attached image
//
void HelloVulkan::drawPost(vk::CommandBuffer cmdBuf)
{
m_debug.beginLabel(cmdBuf, "Post");
cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)});
cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
auto aspectRatio = static_cast<float>(m_size.width) / static_cast<float>(m_size.height);
cmdBuf.pushConstants<float>(m_postPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0,
aspectRatio);
cmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, m_postPipeline);
cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_postPipelineLayout, 0,
m_postDescSet, {});
cmdBuf.draw(3, 1, 0, 0);
m_debug.endLabel(cmdBuf);
}
//////////////////////////////////////////////////////////////////////////
// Raytracing, creation of BLAS and TLAS
//////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------------------------
// Initialize Vulkan ray tracing
// #VKRay
void HelloVulkan::initRayTracing()
{
// Requesting ray tracing properties
auto properties =
m_physicalDevice.getProperties2<vk::PhysicalDeviceProperties2,
vk::PhysicalDeviceRayTracingPipelinePropertiesKHR>();
m_rtProperties = properties.get<vk::PhysicalDeviceRayTracingPipelinePropertiesKHR>();
m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex);
}
//--------------------------------------------------------------------------------------------------
// Converting a OBJ primitive to the ray tracing geometry used for the BLAS
//
nvvk::RaytracingBuilderKHR::BlasInput HelloVulkan::objectToVkGeometryKHR(const ObjModel& model)
{
// Building part
vk::DeviceAddress vertexAddress = m_device.getBufferAddress({model.vertexBuffer.buffer});
vk::DeviceAddress indexAddress = m_device.getBufferAddress({model.indexBuffer.buffer});
vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat);
triangles.setVertexData(vertexAddress);
triangles.setVertexStride(sizeof(VertexObj));
triangles.setIndexType(vk::IndexType::eUint32);
triangles.setIndexData(indexAddress);
triangles.setTransformData({});
triangles.setMaxVertex(model.nbVertices);
// Setting up the build info of the acceleration
vk::AccelerationStructureGeometryKHR asGeom;
asGeom.setGeometryType(vk::GeometryTypeKHR::eTriangles);
asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
asGeom.geometry.setTriangles(triangles);
// The primitive itself
vk::AccelerationStructureBuildRangeInfoKHR offset;
offset.setFirstVertex(0);
offset.setPrimitiveCount(model.nbIndices / 3); // Nb triangles
offset.setPrimitiveOffset(0);
offset.setTransformOffset(0);
// Our blas is only one geometry, but could be made of many geometries
nvvk::RaytracingBuilderKHR::BlasInput input;
input.asGeometry.emplace_back(asGeom);
input.asBuildOffsetInfo.emplace_back(offset);
return input;
}
//--------------------------------------------------------------------------------------------------
//
//
void HelloVulkan::createBottomLevelAS()
{
// BLAS - Storing each primitive in a geometry
std::vector<nvvk::RaytracingBuilderKHR::BlasInput> allBlas;
allBlas.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
allBlas.emplace_back(blas);
}
m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
}
void HelloVulkan::createTopLevelAS()
{
std::vector<nvvk::RaytracingBuilderKHR::Instance> tlas;
tlas.reserve(m_objInstance.size());
for(uint32_t i = 0; i < static_cast<uint32_t>(m_objInstance.size()); i++)
{
nvvk::RaytracingBuilderKHR::Instance rayInst;
rayInst.transform = m_objInstance[i].transform; // Position of the instance
rayInst.instanceCustomId = i; // gl_InstanceCustomIndexEXT
rayInst.blasId = m_objInstance[i].objIndex;
rayInst.hitGroupId = 0; // 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);
}
//////////////////////////////////////////////////////////////////////////
// Compute shader from ANIMATION tutorial
//////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------------------------
// Compute shader descriptor
//
void HelloVulkan::createCompDescriptors()
{
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [in] G-Buffer
0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute));
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [out] AO
1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute));
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [in] TLAS
2, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eCompute));
m_compDescSetLayout = m_compDescSetLayoutBind.createLayout(m_device);
m_compDescPool = m_compDescSetLayoutBind.createPool(m_device, 1);
m_compDescSet = nvvk::allocateDescriptorSet(m_device, m_compDescPool, m_compDescSetLayout);
}
//--------------------------------------------------------------------------------------------------
// Setting up the values to the descriptors
//
void HelloVulkan::updateCompDescriptors()
{
std::vector<vk::WriteDescriptorSet> writes;
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 0, &m_gBuffer.descriptor));
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 1, &m_aoBuffer.descriptor));
vk::AccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure();
vk::WriteDescriptorSetAccelerationStructureKHR descASInfo{1, &tlas};
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 2, &descASInfo));
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
}
//--------------------------------------------------------------------------------------------------
// Creating the pipeline: shader ...
//
void HelloVulkan::createCompPipelines()
{
// pushing time
vk::PushConstantRange push_constants = {vk::ShaderStageFlagBits::eCompute, 0, sizeof(AoControl)};
vk::PipelineLayoutCreateInfo layout_info{{}, 1, &m_compDescSetLayout, 1, &push_constants};
m_compPipelineLayout = m_device.createPipelineLayout(layout_info);
vk::ComputePipelineCreateInfo computePipelineCreateInfo{{}, {}, m_compPipelineLayout};
computePipelineCreateInfo.stage =
nvvk::createShaderStageInfo(m_device,
nvh::loadFile("spv/ao.comp.spv", true, defaultSearchPaths, true),
VK_SHADER_STAGE_COMPUTE_BIT);
m_compPipeline = static_cast<const vk::Pipeline&>(
m_device.createComputePipeline({}, computePipelineCreateInfo));
m_device.destroy(computePipelineCreateInfo.stage.module);
}
//--------------------------------------------------------------------------------------------------
// Running compute shader
//
#define GROUP_SIZE 16 // Same group size as in compute shader
void HelloVulkan::runCompute(vk::CommandBuffer cmdBuf, AoControl& aoControl)
{
updateFrame();
// Stop by default after 100'000 samples
if(m_frame * aoControl.rtao_samples > aoControl.max_samples)
return;
m_debug.beginLabel(cmdBuf, "Compute");
// Adding a barrier to be sure the fragment has finished writing to the G-Buffer
// before the compute shader is using the buffer
vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
vk::ImageMemoryBarrier imgMemBarrier;
imgMemBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
imgMemBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
imgMemBarrier.setImage(m_gBuffer.image);
imgMemBarrier.setOldLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setNewLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setSubresourceRange(range);
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader,
vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlagBits::eDeviceGroup, {}, {}, {imgMemBarrier});
// Preparing for the compute shader
cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_compPipeline);
cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, m_compPipelineLayout, 0,
{m_compDescSet}, {});
// Sending the push constant information
aoControl.frame = m_frame;
cmdBuf.pushConstants(m_compPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0,
sizeof(AoControl), &aoControl);
// Dispatching the shader
cmdBuf.dispatch((m_size.width + (GROUP_SIZE - 1)) / GROUP_SIZE,
(m_size.height + (GROUP_SIZE - 1)) / GROUP_SIZE, 1);
// Adding a barrier to be sure the compute shader has finished
// writing to the AO buffer before the post shader is using it
imgMemBarrier.setImage(m_aoBuffer.image);
imgMemBarrier.setOldLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setNewLayout(vk::ImageLayout::eGeneral);
imgMemBarrier.setSubresourceRange(range);
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eFragmentShader,
vk::DependencyFlagBits::eDeviceGroup, {}, {}, {imgMemBarrier});
m_debug.endLabel(cmdBuf);
}
//////////////////////////////////////////////////////////////////////////
// Reset from JITTER CAM tutorial
//////////////////////////////////////////////////////////////////////////
//--------------------------------------------------------------------------------------------------
// If the camera matrix has changed, resets the frame otherwise, increments frame.
//
void HelloVulkan::updateFrame()
{
static nvmath::mat4f refCamMatrix;
static float fov = 0;
auto& m = CameraManip.getMatrix();
auto f = CameraManip.getFov();
if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0 || f != fov)
{
resetFrame();
refCamMatrix = m;
fov = f;
}
m_frame++;
}
void HelloVulkan::resetFrame()
{
m_frame = -1;
}

View file

@ -0,0 +1,174 @@
/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#define NVVK_ALLOC_DEDICATED
#include "nvvk/allocator_vk.hpp"
#include "nvvk/appbase_vkpp.hpp"
#include "nvvk/debug_util_vk.hpp"
#include "nvvk/descriptorsets_vk.hpp"
// #VKRay
#include "nvvk/raytraceKHR_vk.hpp"
struct AoControl
{
float rtao_radius{2.0f}; // Length of the ray
int rtao_samples{4}; // Nb samples at each iteration
float rtao_power{3.0f}; // Darkness is stronger for more hits
int rtao_distance_based{1}; // Attenuate based on distance
int frame{0}; // Current frame
int max_samples{100'000}; // Max samples before it stops
};
//--------------------------------------------------------------------------------------------------
// Simple rasterizer of OBJ objects
// - Each OBJ loaded are stored in an `ObjModel` and referenced by a `ObjInstance`
// - It is possible to have many `ObjInstance` referencing the same `ObjModel`
// - Rendering is done in an offscreen framebuffer
// - The image of the framebuffer is displayed in post-process in a full-screen quad
//
class HelloVulkan : public nvvk::AppBase
{
public:
void setup(const vk::Instance& instance,
const vk::Device& device,
const vk::PhysicalDevice& physicalDevice,
uint32_t queueFamily) override;
void createDescriptorSetLayout();
void createGraphicsPipeline();
void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1));
void updateDescriptorSet();
void createUniformBuffer();
void createSceneDescriptionBuffer();
void createTextureImages(const vk::CommandBuffer& cmdBuf,
const std::vector<std::string>& textures);
void updateUniformBuffer(const vk::CommandBuffer& cmdBuf);
void onResize(int /*w*/, int /*h*/) override;
void destroyResources();
void rasterize(const vk::CommandBuffer& cmdBuff);
// The OBJ model
struct ObjModel
{
uint32_t nbIndices{0};
uint32_t nbVertices{0};
nvvk::Buffer vertexBuffer; // Device buffer of all 'Vertex'
nvvk::Buffer indexBuffer; // Device buffer of the indices forming triangles
nvvk::Buffer matColorBuffer; // Device buffer of array of 'Wavefront material'
nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material'
};
// Instance of the OBJ
struct ObjInstance
{
uint32_t objIndex{0}; // Reference to the `m_objModel`
uint32_t txtOffset{0}; // Offset in `m_textures`
nvmath::mat4f transform{1}; // Position of the instance
nvmath::mat4f transformIT{1}; // Inverse transpose
};
// Information pushed at each draw call
struct ObjPushConstant
{
nvmath::vec3f lightPosition{3.5f, 8.f, 5.f};
int instanceId{0}; // To retrieve the transformation matrix
float lightIntensity{100.f};
int lightType{0}; // 0: point, 1: infinite
};
ObjPushConstant m_pushConstant;
// Array of objects and instances in the scene
std::vector<ObjModel> m_objModel;
std::vector<ObjInstance> m_objInstance;
// Graphic pipeline
vk::PipelineLayout m_pipelineLayout;
vk::Pipeline m_graphicsPipeline;
nvvk::DescriptorSetBindings m_descSetLayoutBind;
vk::DescriptorPool m_descPool;
vk::DescriptorSetLayout m_descSetLayout;
vk::DescriptorSet m_descSet;
nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices
nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances
std::vector<nvvk::Texture> m_textures; // vector of all textures of the scene
nvvk::AllocatorDedicated m_alloc; // Allocator for buffer, images, acceleration structures
nvvk::DebugUtil m_debug; // Utility to name objects
// #Post
void createOffscreenRender();
void createPostPipeline();
void createPostDescriptor();
void updatePostDescriptorSet();
void drawPost(vk::CommandBuffer cmdBuf);
nvvk::DescriptorSetBindings m_postDescSetLayoutBind;
vk::DescriptorPool m_postDescPool;
vk::DescriptorSetLayout m_postDescSetLayout;
vk::DescriptorSet m_postDescSet;
vk::Pipeline m_postPipeline;
vk::PipelineLayout m_postPipelineLayout;
vk::RenderPass m_offscreenRenderPass;
vk::Framebuffer m_offscreenFramebuffer;
nvvk::Texture m_offscreenColor;
nvvk::Texture m_gBuffer;
nvvk::Texture m_aoBuffer;
vk::Format m_offscreenColorFormat{vk::Format::eR32G32B32A32Sfloat};
nvvk::Texture m_offscreenDepth;
vk::Format m_offscreenDepthFormat;
// #Tuto_rayquery
void initRayTracing();
nvvk::RaytracingBuilderKHR::BlasInput objectToVkGeometryKHR(const ObjModel& model);
void createBottomLevelAS();
void createTopLevelAS();
vk::PhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties;
nvvk::RaytracingBuilderKHR m_rtBuilder;
// #Tuto_animation
void createCompDescriptors();
void updateCompDescriptors();
void createCompPipelines();
void runCompute(vk::CommandBuffer cmdBuf, AoControl& aoControl);
nvvk::DescriptorSetBindings m_compDescSetLayoutBind;
vk::DescriptorPool m_compDescPool;
vk::DescriptorSetLayout m_compDescSetLayout;
vk::DescriptorSet m_compDescSet;
vk::Pipeline m_compPipeline;
vk::PipelineLayout m_compPipelineLayout;
// #Tuto_jitter_cam
void updateFrame();
void resetFrame();
int m_frame{0};
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

342
ray_tracing_ao/main.cpp Normal file
View file

@ -0,0 +1,342 @@
/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// ImGui - standalone example application for Glfw + Vulkan, using programmable
// pipeline If you are new to ImGui, see examples/README.txt and documentation
// at the top of imgui.cpp.
#include <array>
#include <iostream>
#include <vulkan/vulkan.hpp>
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
#include "imgui.h"
#include "imgui/backends/imgui_impl_glfw.h"
#include "hello_vulkan.h"
#include "imgui/extras/imgui_camera_widget.h"
#include "nvh/cameramanipulator.hpp"
#include "nvh/fileoperations.hpp"
#include "nvpsystem.hpp"
#include "nvvk/appbase_vkpp.hpp"
#include "nvvk/commands_vk.hpp"
#include "nvvk/context_vk.hpp"
//////////////////////////////////////////////////////////////////////////
#define UNUSED(x) (void)(x)
//////////////////////////////////////////////////////////////////////////
// Default search path for shaders
std::vector<std::string> defaultSearchPaths;
// GLFW Callback functions
static void onErrorCallback(int error, const char* description)
{
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
// Extra UI
void renderUI(HelloVulkan& helloVk)
{
ImGuiH::CameraWidget();
if(ImGui::CollapsingHeader("Light"))
{
ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0);
ImGui::SameLine();
ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.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);
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static int const SAMPLE_WIDTH = 1280;
static int const SAMPLE_HEIGHT = 720;
//--------------------------------------------------------------------------------------------------
// Application Entry
//
int main(int argc, char** argv)
{
UNUSED(argc);
// Setup GLFW window
glfwSetErrorCallback(onErrorCallback);
if(!glfwInit())
{
return 1;
}
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));
// Setup Vulkan
if(!glfwVulkanSupported())
{
printf("GLFW: Vulkan Not Supported\n");
return 1;
}
// setup some basic things for the sample, logging file for example
NVPSystem system(PROJECT_NAME);
// Search path for shaders and other media
defaultSearchPaths = {
NVPSystem::exePath() + PROJECT_RELDIRECTORY,
NVPSystem::exePath() + PROJECT_RELDIRECTORY "..",
std::string(PROJECT_NAME),
};
// Requesting Vulkan extensions and layers
nvvk::ContextCreateInfo contextInfo(true);
contextInfo.setVersion(1, 2);
contextInfo.addInstanceLayer("VK_LAYER_LUNARG_monitor", true);
contextInfo.addInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME);
contextInfo.addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, true);
#ifdef WIN32
contextInfo.addInstanceExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#else
contextInfo.addInstanceExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
contextInfo.addInstanceExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
#endif
contextInfo.addInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
contextInfo.addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
contextInfo.addDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
contextInfo.addDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
// #VKRay: Activate the ray tracing extension
contextInfo.addDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME);
contextInfo.addDeviceExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME);
contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
contextInfo.addDeviceExtension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
// #VKRay: Activate the ray tracing extension
vk::PhysicalDeviceAccelerationStructureFeaturesKHR accelFeatures;
contextInfo.addDeviceExtension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, false,
&accelFeatures);
vk::PhysicalDeviceRayQueryFeaturesKHR rayQueryFeatures;
contextInfo.addDeviceExtension(VK_KHR_RAY_QUERY_EXTENSION_NAME, false, &rayQueryFeatures);
// Creating Vulkan base application
nvvk::Context vkctx{};
vkctx.initInstance(contextInfo);
// Find all compatible devices
auto compatibleDevices = vkctx.getCompatibleDevices(contextInfo);
assert(!compatibleDevices.empty());
// Use a compatible device
vkctx.initDevice(compatibleDevices[0], contextInfo);
// Create example
HelloVulkan helloVk;
// Window need to be opened to get the surface on which to draw
const vk::SurfaceKHR surface = helloVk.getVkSurface(vkctx.m_instance, window);
vkctx.setGCTQueueWithPresent(surface);
helloVk.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice,
vkctx.m_queueGCT.familyIndex);
helloVk.createSwapchain(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT);
helloVk.createDepthBuffer();
helloVk.createRenderPass();
helloVk.createFrameBuffers();
// Setup Imgui
helloVk.initGUI(0); // Using sub-pass 0
// Creation of the example
nvmath::mat4f t = nvmath::translation_mat4(nvmath::vec3f{0, 0.0, 0});
helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true), t);
//helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths, true));
helloVk.loadModel(nvh::findFile("media/scenes/Medieval_building.obj", defaultSearchPaths, true));
helloVk.createOffscreenRender();
helloVk.createDescriptorSetLayout();
helloVk.createGraphicsPipeline();
helloVk.createUniformBuffer();
helloVk.createSceneDescriptionBuffer();
// #VKRay
helloVk.initRayTracing();
helloVk.createBottomLevelAS();
helloVk.createTopLevelAS();
// Need the Top level AS
helloVk.updateDescriptorSet();
helloVk.createPostDescriptor();
helloVk.createPostPipeline();
helloVk.updatePostDescriptorSet();
helloVk.createCompDescriptors();
helloVk.updateCompDescriptors();
helloVk.createCompPipelines();
nvmath::vec4f clearColor = nvmath::vec4f(0, 0, 0, 0);
helloVk.setupGlfwCallbacks(window);
ImGui_ImplGlfw_InitForVulkan(window, true);
AoControl aoControl;
// Main loop
while(!glfwWindowShouldClose(window))
{
try
{
glfwPollEvents();
if(helloVk.isMinimized())
continue;
// Start the Dear ImGui frame
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// Show UI window.
if(helloVk.showGui())
{
ImGuiH::Panel::Begin();
ImGui::ColorEdit3("Clear color", reinterpret_cast<float*>(&clearColor));
renderUI(helloVk);
ImGui::SetNextTreeNodeOpen(true, ImGuiCond_Once);
if(ImGui::CollapsingHeader("Ambient Occlusion"))
{
bool changed{false};
changed |= ImGui::SliderFloat("Radius", &aoControl.rtao_radius, 0, 5);
changed |= ImGui::SliderInt("Rays per Pixel", &aoControl.rtao_samples, 1, 64);
changed |= ImGui::SliderFloat("Power", &aoControl.rtao_power, 1, 5);
changed |= ImGui::InputInt("Max Samples", &aoControl.max_samples);
changed |= ImGui::Checkbox("Distanced Based", (bool*)&aoControl.rtao_distance_based);
if(changed)
helloVk.resetFrame();
}
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)",
1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGuiH::Control::Info("", "", "(F10) Toggle Pane", ImGuiH::Control::Flags::Disabled);
ImGuiH::Panel::End();
}
// Start rendering the scene
helloVk.prepareFrame();
// Start command buffer of this frame
auto curFrame = helloVk.getCurFrame();
const vk::CommandBuffer& cmdBuf = helloVk.getCommandBuffers()[curFrame];
cmdBuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
// Updating camera buffer
helloVk.updateUniformBuffer(cmdBuf);
// Clearing screen
vk::ClearValue clearValues[3];
clearValues[0].setColor(
std::array<float, 4>({clearColor[0], clearColor[1], clearColor[2], clearColor[3]}));
// Offscreen render pass
{
clearValues[1].setColor(std::array<float, 4>{0, 0, 0, 0});
clearValues[2].setDepthStencil({1.0f, 0});
vk::RenderPassBeginInfo offscreenRenderPassBeginInfo;
offscreenRenderPassBeginInfo.setClearValueCount(3);
offscreenRenderPassBeginInfo.setPClearValues(clearValues);
offscreenRenderPassBeginInfo.setRenderPass(helloVk.m_offscreenRenderPass);
offscreenRenderPassBeginInfo.setFramebuffer(helloVk.m_offscreenFramebuffer);
offscreenRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()});
// Rendering Scene
{
cmdBuf.beginRenderPass(offscreenRenderPassBeginInfo, vk::SubpassContents::eInline);
helloVk.rasterize(cmdBuf);
cmdBuf.endRenderPass();
helloVk.runCompute(cmdBuf, aoControl);
}
}
// 2nd rendering pass: tone mapper, UI
{
clearValues[1].setDepthStencil({1.0f, 0});
vk::RenderPassBeginInfo postRenderPassBeginInfo;
postRenderPassBeginInfo.setClearValueCount(2);
postRenderPassBeginInfo.setPClearValues(clearValues);
postRenderPassBeginInfo.setRenderPass(helloVk.getRenderPass());
postRenderPassBeginInfo.setFramebuffer(helloVk.getFramebuffers()[curFrame]);
postRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()});
cmdBuf.beginRenderPass(postRenderPassBeginInfo, vk::SubpassContents::eInline);
// Rendering tonemapper
helloVk.drawPost(cmdBuf);
// Rendering UI
ImGui::Render();
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuf);
cmdBuf.endRenderPass();
}
// Submit for display
cmdBuf.end();
helloVk.submitFrame();
}
catch(const std::system_error& e)
{
if(e.code() == vk::Result::eErrorDeviceLost)
{
#if _WIN32
MessageBoxA(nullptr, e.what(), "Fatal Error", MB_ICONERROR | MB_OK | MB_DEFBUTTON1);
#endif
}
std::cout << e.what() << std::endl;
return e.code().value();
}
}
// Cleanup
helloVk.getDevice().waitIdle();
helloVk.destroyResources();
helloVk.destroy();
vkctx.deinit();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

View file

@ -0,0 +1,123 @@
#version 460
#extension GL_ARB_separate_shader_objects : enable
#extension GL_EXT_nonuniform_qualifier : enable
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_scalar_block_layout : enable
#extension GL_EXT_ray_tracing : enable
#extension GL_EXT_ray_query : enable
#include "raycommon.glsl"
const int GROUP_SIZE = 16;
layout(local_size_x = GROUP_SIZE, local_size_y = GROUP_SIZE) in;
layout(set = 0, binding = 0, rgba32f) uniform image2D inImage;
layout(set = 0, binding = 1, r32f) uniform image2D outImage;
layout(set = 0, binding = 2) uniform accelerationStructureEXT topLevelAS;
// See AoControl
layout(push_constant) uniform params_
{
float rtao_radius;
int rtao_samples;
float rtao_power;
int rtao_distance_based;
int frame_number;
int max_samples;
};
//----------------------------------------------------------------------------
// Tracing a ray and returning the weight based on the distance of the hit
//
float TraceRay(in rayQueryEXT rayQuery, in vec3 origin, in vec3 direction)
{
uint flags = gl_RayFlagsNoneEXT;
if(rtao_distance_based == 0)
flags = gl_RayFlagsTerminateOnFirstHitEXT;
rayQueryInitializeEXT(rayQuery, topLevelAS, flags, 0xFF, origin, 0.0f, direction, rtao_radius);
// Start traversal: return false if traversal is complete
while(rayQueryProceedEXT(rayQuery))
{
}
// Returns type of committed (true) intersection
if(rayQueryGetIntersectionTypeEXT(rayQuery, true) != gl_RayQueryCommittedIntersectionNoneEXT)
{
// Got an intersection == Shadow
if(rtao_distance_based == 0)
return 1;
float length = 1 - (rayQueryGetIntersectionTEXT(rayQuery, true) / rtao_radius);
return length; // * length;
}
return 0;
}
void main()
{
float occlusion = 0.0;
ivec2 size = imageSize(inImage);
// Check if not outside boundaries
if(gl_GlobalInvocationID.x >= size.x || gl_GlobalInvocationID.y >= size.y)
return;
// Initialize the random number
uint seed = tea(size.x * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x, frame_number);
// Retrieving position and normal
vec4 gBuffer = imageLoad(inImage, ivec2(gl_GlobalInvocationID.xy));
// Shooting rays only if a fragment was rendered
if(gBuffer != vec4(0))
{
vec3 origin = gBuffer.xyz;
vec3 normal = DecompressUnitVec(floatBitsToUint(gBuffer.w));
vec3 direction;
// Move origin slightly away from the surface to avoid self-occlusion
origin = OffsetRay(origin, normal);
// Finding the basis (tangent and bitangent) from the normal
vec3 n, tangent, bitangent;
ComputeDefaultBasis(normal, tangent, bitangent);
// Sampling hemiphere n-time
for(int i = 0; i < rtao_samples; i++)
{
// Cosine sampling
float r1 = rnd(seed);
float r2 = rnd(seed);
float sq = sqrt(1.0 - r2);
float phi = 2 * M_PI * r1;
vec3 direction = vec3(cos(phi) * sq, sin(phi) * sq, sqrt(r2));
direction = direction.x * tangent + direction.y * bitangent + direction.z * normal;
// Initializes a ray query object but does not start traversal
rayQueryEXT rayQuery;
occlusion += TraceRay(rayQuery, origin, direction);
}
// Computing occlusion
occlusion = 1 - (occlusion / rtao_samples);
occlusion = pow(clamp(occlusion, 0, 1), rtao_power);
}
// Writting out the AO
if(frame_number == 0)
{
imageStore(outImage, ivec2(gl_GlobalInvocationID.xy), vec4(occlusion));
}
else
{
// Accumulating over time
float old_ao = imageLoad(outImage, ivec2(gl_GlobalInvocationID.xy)).x;
float new_result = mix(old_ao, occlusion, 1.0f / float(frame_number + 1));
imageStore(outImage, ivec2(gl_GlobalInvocationID.xy), vec4(new_result));
}
}

View file

@ -0,0 +1,90 @@
#version 460
#extension GL_ARB_separate_shader_objects : enable
#extension GL_EXT_nonuniform_qualifier : enable
#extension GL_GOOGLE_include_directive : enable
#extension GL_EXT_scalar_block_layout : enable
#extension GL_EXT_ray_tracing : enable
#extension GL_EXT_ray_query : enable
#include "raycommon.glsl"
#include "wavefront.glsl"
layout(push_constant) uniform shaderInformation
{
vec3 lightPosition;
uint instanceId;
float lightIntensity;
int lightType;
}
pushC;
// clang-format off
// Incoming
//layout(location = 0) flat in int matIndex;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 2) in vec3 fragNormal;
layout(location = 3) in vec3 viewDir;
layout(location = 4) in vec3 worldPos;
// Outgoing
layout(location = 0) out vec4 outColor;
layout(location = 1) out vec4 outGbuffer;
// Buffers
layout(binding = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[];
layout(binding = 2, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc;
layout(binding = 3) uniform sampler2D[] textureSamplers;
layout(binding = 4, scalar) buffer MatIndex { int i[]; } matIdx[];
//layout(binding = 7, set = 0) uniform accelerationStructureEXT topLevelAS;
// clang-format on
void main()
{
// Object of this instance
int objId = scnDesc.i[pushC.instanceId].objId;
// Material of the object
int matIndex = matIdx[nonuniformEXT(objId)].i[gl_PrimitiveID];
WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIndex];
vec3 N = normalize(fragNormal);
// Vector toward light
vec3 L;
float lightDistance;
float lightIntensity = pushC.lightIntensity;
if(pushC.lightType == 0)
{
vec3 lDir = pushC.lightPosition - worldPos;
float d = length(lDir);
lightIntensity = pushC.lightIntensity / (d * d);
L = normalize(lDir);
lightDistance = d;
}
else
{
L = normalize(pushC.lightPosition - vec3(0));
lightDistance = 10000;
}
// Diffuse
vec3 diffuse = computeDiffuse(mat, L, N);
diffuse = vec3(1);
// if(mat.textureId >= 0)
// {
// int txtOffset = scnDesc.i[pushC.instanceId].txtOffset;
// uint txtId = txtOffset + mat.textureId;
// vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz;
// diffuse *= diffuseTxt;
// }
//lightIntensity = 1;
// 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)));
}

View file

@ -0,0 +1,15 @@
#version 450
layout (location = 0) out vec2 outUV;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outUV * 2.0f - 1.0f, 1.0f, 1.0f);
}

View file

@ -0,0 +1,23 @@
#version 450
layout(location = 0) in vec2 outUV;
layout(location = 0) out vec4 fragColor;
layout(set = 0, binding = 0) uniform sampler2D noisyTxt;
layout(set = 0, binding = 1) uniform sampler2D aoTxt;
layout(push_constant) uniform shaderInformation
{
float aspectRatio;
}
pushc;
void main()
{
vec2 uv = outUV;
float gamma = 1. / 2.2;
vec4 color = texture(noisyTxt, uv);
float ao = texture(aoTxt, uv).x;
fragColor = pow(color * ao, vec4(gamma));
}

View file

@ -0,0 +1,190 @@
/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//-
// This utility compresses a normal(x,y,z) to a uint and decompresses it
#define C_Stack_Max 3.402823466e+38f
uint CompressUnitVec(vec3 nv)
{
// map to octahedron and then flatten to 2D (see 'Octahedron Environment Maps' by Engelhardt & Dachsbacher)
if((nv.x < C_Stack_Max) && !isinf(nv.x))
{
const float d = 32767.0f / (abs(nv.x) + abs(nv.y) + abs(nv.z));
int x = int(roundEven(nv.x * d));
int y = int(roundEven(nv.y * d));
if(nv.z < 0.0f)
{
const int maskx = x >> 31;
const int masky = y >> 31;
const int tmp = 32767 + maskx + masky;
const int tmpx = x;
x = (tmp - (y ^ masky)) ^ maskx;
y = (tmp - (tmpx ^ maskx)) ^ masky;
}
uint packed = (uint(y + 32767) << 16) | uint(x + 32767);
if(packed == ~0u)
return ~0x1u;
return packed;
}
else
{
return ~0u;
}
}
float ShortToFloatM11(const int v) // linearly maps a short 32767-32768 to a float -1-+1 //!! opt.?
{
return (v >= 0) ? (uintBitsToFloat(0x3F800000u | (uint(v) << 8)) - 1.0f) :
(uintBitsToFloat((0x80000000u | 0x3F800000u) | (uint(-v) << 8)) + 1.0f);
}
vec3 DecompressUnitVec(uint packed)
{
if(packed != ~0u) // sanity check, not needed as isvalid_unit_vec is called earlier
{
int x = int(packed & 0xFFFFu) - 32767;
int y = int(packed >> 16) - 32767;
const int maskx = x >> 31;
const int masky = y >> 31;
const int tmp0 = 32767 + maskx + masky;
const int ymask = y ^ masky;
const int tmp1 = tmp0 - (x ^ maskx);
const int z = tmp1 - ymask;
float zf;
if(z < 0)
{
x = (tmp0 - ymask) ^ maskx;
y = tmp1 ^ masky;
zf = uintBitsToFloat((0x80000000u | 0x3F800000u) | (uint(-z) << 8)) + 1.0f;
}
else
{
zf = uintBitsToFloat(0x3F800000u | (uint(z) << 8)) - 1.0f;
}
return normalize(vec3(ShortToFloatM11(x), ShortToFloatM11(y), zf));
}
else
{
return vec3(C_Stack_Max);
}
}
//-------------------------------------------------------------------------------------------------
// Avoiding self intersections (see Ray Tracing Gems, Ch. 6)
//
vec3 OffsetRay(in vec3 p, in vec3 n)
{
const float intScale = 256.0f;
const float floatScale = 1.0f / 65536.0f;
const float origin = 1.0f / 32.0f;
ivec3 of_i = ivec3(intScale * n.x, intScale * n.y, intScale * n.z);
vec3 p_i = vec3(intBitsToFloat(floatBitsToInt(p.x) + ((p.x < 0) ? -of_i.x : of_i.x)),
intBitsToFloat(floatBitsToInt(p.y) + ((p.y < 0) ? -of_i.y : of_i.y)),
intBitsToFloat(floatBitsToInt(p.z) + ((p.z < 0) ? -of_i.z : of_i.z)));
return vec3(abs(p.x) < origin ? p.x + floatScale * n.x : p_i.x, //
abs(p.y) < origin ? p.y + floatScale * n.y : p_i.y, //
abs(p.z) < origin ? p.z + floatScale * n.z : p_i.z);
}
//////////////////////////// AO //////////////////////////////////////
#define EPS 0.05
const float M_PI = 3.141592653589;
void ComputeDefaultBasis(const vec3 normal, out vec3 x, out vec3 y)
{
// ZAP's default coordinate system for compatibility
vec3 z = normal;
const float yz = -z.y * z.z;
y = normalize(((abs(z.z) > 0.99999f) ? vec3(-z.x * z.y, 1.0f - z.y * z.y, yz) :
vec3(-z.x * z.z, yz, 1.0f - z.z * z.z)));
x = cross(y, z);
}
//-------------------------------------------------------------------------------------------------
// Random
//-------------------------------------------------------------------------------------------------
// Generate a random unsigned int from two unsigned int values, using 16 pairs
// of rounds of the Tiny Encryption Algorithm. See Zafar, Olano, and Curtis,
// "GPU Random Numbers via the Tiny Encryption Algorithm"
uint tea(uint val0, uint val1)
{
uint v0 = val0;
uint v1 = val1;
uint s0 = 0;
for(uint n = 0; n < 16; n++)
{
s0 += 0x9e3779b9;
v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4);
v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e);
}
return v0;
}
uvec2 pcg2d(uvec2 v)
{
v = v * 1664525u + 1013904223u;
v.x += v.y * 1664525u;
v.y += v.x * 1664525u;
v = v ^ (v >> 16u);
v.x += v.y * 1664525u;
v.y += v.x * 1664525u;
v = v ^ (v >> 16u);
return v;
}
// Generate a random unsigned int in [0, 2^24) given the previous RNG state
// using the Numerical Recipes linear congruential generator
uint lcg(inout uint prev)
{
uint LCG_A = 1664525u;
uint LCG_C = 1013904223u;
prev = (LCG_A * prev + LCG_C);
return prev & 0x00FFFFFF;
}
// Generate a random float in [0, 1) given the previous RNG state
float rnd(inout uint seed)
{
return (float(lcg(seed)) / float(0x01000000));
}

View file

@ -0,0 +1,61 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
#extension GL_EXT_scalar_block_layout : enable
#extension GL_GOOGLE_include_directive : enable
#include "wavefront.glsl"
// clang-format off
layout(binding = 2, set = 0, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc;
// clang-format on
layout(binding = 0) uniform UniformBufferObject
{
mat4 view;
mat4 proj;
mat4 viewI;
}
ubo;
layout(push_constant) uniform shaderInformation
{
vec3 lightPosition;
uint instanceId;
float lightIntensity;
int lightType;
}
pushC;
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) 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;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
mat4 objMatrix = scnDesc.i[pushC.instanceId].transfo;
mat4 objMatrixIT = scnDesc.i[pushC.instanceId].transfoIT;
vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1));
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);
}

View file

@ -0,0 +1,58 @@
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
{
int objId;
int txtOffset;
mat4 transfo;
mat4 transfoIT;
};
vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal)
{
// Lambertian
float dotNL = max(dot(normal, lightDir), 0.0);
vec3 c = mat.diffuse * dotNL;
if(mat.illum >= 1)
c += mat.ambient;
return c;
}
vec3 computeSpecular(WaveFrontMaterial mat, vec3 viewDir, vec3 lightDir, vec3 normal)
{
if(mat.illum < 2)
return vec3(0);
// Compute specular only if not in shadow
const float kPi = 3.14159265;
const float kShininess = max(mat.shininess, 4.0);
// Specular
const float kEnergyConservation = (2.0 + kShininess) / (2.0 * kPi);
vec3 V = normalize(-viewDir);
vec3 R = reflect(-lightDir, normal);
float specular = kEnergyConservation * pow(max(dot(V, R), 0.0), kShininess);
return vec3(mat.specular * specular);
}