Bulk update NvPro-Samples 03/18/21
This commit is contained in:
parent
a2b80ab819
commit
2da588b7e6
113 changed files with 3529 additions and 1508 deletions
80
ray_tracing_ao/CMakeLists.txt
Normal file
80
ray_tracing_ao/CMakeLists.txt
Normal 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
491
ray_tracing_ao/README.md
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
# G-Buffer and Ambient Occlusion - Tutorial
|
||||
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
The following are the buffers are they can be seen in [NSight Graphics](https://developer.nvidia.com/nsight-graphics).
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 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));
|
||||
~~~~
|
||||
|
||||
913
ray_tracing_ao/hello_vulkan.cpp
Normal file
913
ray_tracing_ao/hello_vulkan.cpp
Normal 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;
|
||||
}
|
||||
174
ray_tracing_ao/hello_vulkan.h
Normal file
174
ray_tracing_ao/hello_vulkan.h
Normal 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};
|
||||
};
|
||||
BIN
ray_tracing_ao/images/Hemisphere_sampling.png
Normal file
BIN
ray_tracing_ao/images/Hemisphere_sampling.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
ray_tracing_ao/images/Tuto_Ao_workflow.png
Normal file
BIN
ray_tracing_ao/images/Tuto_Ao_workflow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
ray_tracing_ao/images/buffers.png
Normal file
BIN
ray_tracing_ao/images/buffers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
ray_tracing_ao/images/ray_tracing_ao.png
Normal file
BIN
ray_tracing_ao/images/ray_tracing_ao.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
342
ray_tracing_ao/main.cpp
Normal file
342
ray_tracing_ao/main.cpp
Normal 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;
|
||||
}
|
||||
123
ray_tracing_ao/shaders/ao.comp
Normal file
123
ray_tracing_ao/shaders/ao.comp
Normal 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));
|
||||
}
|
||||
}
|
||||
90
ray_tracing_ao/shaders/frag_shader.frag
Normal file
90
ray_tracing_ao/shaders/frag_shader.frag
Normal 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)));
|
||||
}
|
||||
15
ray_tracing_ao/shaders/passthrough.vert
Normal file
15
ray_tracing_ao/shaders/passthrough.vert
Normal 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);
|
||||
}
|
||||
23
ray_tracing_ao/shaders/post.frag
Normal file
23
ray_tracing_ao/shaders/post.frag
Normal 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));
|
||||
}
|
||||
190
ray_tracing_ao/shaders/raycommon.glsl
Normal file
190
ray_tracing_ao/shaders/raycommon.glsl
Normal 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));
|
||||
}
|
||||
61
ray_tracing_ao/shaders/vert_shader.vert
Normal file
61
ray_tracing_ao/shaders/vert_shader.vert
Normal 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);
|
||||
}
|
||||
58
ray_tracing_ao/shaders/wavefront.glsl
Normal file
58
ray_tracing_ao/shaders/wavefront.glsl
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue