This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
@@ -0,0 +1,35 @@
# Copyright (c) 2024, Google
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 the "License";
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
add_sample(
ID ${FOLDER_NAME}
CATEGORY ${CATEGORY_NAME}
AUTHOR "Google"
NAME "HPP Order-independent transparency (depth peeling)"
DESCRIPTION "Order-independent transparency using depth peeling using Vulkan-Hpp"
SHADER_FILES_GLSL
"oit_depth_peeling/background.frag"
"oit_depth_peeling/combine.frag"
"oit_depth_peeling/fullscreen.vert"
"oit_depth_peeling/gather.frag"
"oit_depth_peeling/gather.vert"
"oit_depth_peeling/gather_first.frag")
@@ -0,0 +1,80 @@
////
- Copyright (c) 2024, Google
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
////
= Order-independent transparency with depth peeling using Vulkan-Hpp
ifdef::site-gen-antora[]
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hpp_oit_depth_peeling[Khronos Vulkan samples github repository].
endif::[]
NOTE: This is a transcoded version of the API sample https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/oit_depth_peeling[OIT depth peeling] that illustrates the usage of the C{pp} bindings of vulkan provided by vulkan.hpp. Please see there for the documentation on this sample.
:pp: {plus}{plus}
image::samples/api/oit_depth_peeling/images/sample.png[Sample]
== Overview
This sample implements an order-independent transparency (OIT) algorithm using depth peeling.
It renders a single torus whose opacity can be controlled via the UI.
It produces pixel-perfect results.
It is based on the https://developer.download.nvidia.com/assets/gamedev/docs/OrderIndependentTransparency.pdf[original paper] from Cass Everitt.
== Algorithm
The OIT algorithm consists of several _gather_ passes followed by one _combine_ pass.
Each _gather_ pass renders one layer of transparent geometry.
The first pass renders the first layer, the second pass the second layer, etc.
The N^th^ layer consists of all the N^th^ fragments of each pixel when the fragments are ordered from front to back.
The _combine_ pass is a screen-space operation.
It merges the layer images from back to front to produce the final result.
The algorithm can produce pixel-perfect results, even with intersecting geometry.
When there are more geometry layers than gather passes, the backmost layers get skipped, but the visual results stay stable (i.e. no flickering pixels).
== Options
[cols="2,4,4"]
|===
| Option | Description | Comments
| Camera auto-rotation
| Enable the automatic rotation of the camera
|
| Background grayscale
| Specify the grayscale value by which the background color is multiplied (0.0 to 1.0)
|
| Object alpha
| Specify the opacity of the transparent object (0.0 to 1.0)
|
| Front layer index
| The first layer to be rendered (0 to 7).
|
| Back layer index
| The last layer to be rendered (0 to 7).
| This cannot be less that the front layer index.
|===
@@ -0,0 +1,595 @@
/* Copyright (c) 2024-2025, Google
* Copyright (c) 2024-2025, NVIDIA
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "hpp_oit_depth_peeling.h"
HPPOITDepthPeeling::HPPOITDepthPeeling()
{}
HPPOITDepthPeeling::~HPPOITDepthPeeling()
{
if (has_device())
{
vk::Device device = get_device().get_handle();
background.destroy(device);
combinePass.destroy(device);
for (auto &d : depths)
{
d.destroy();
}
device.destroyDescriptorPool(descriptor_pool);
gatherPass.destroy(device);
for (auto &l : layers)
{
l.destroy(device);
}
model.reset();
device.destroySampler(point_sampler);
scene_constants.reset();
}
}
bool HPPOITDepthPeeling::prepare(const vkb::ApplicationOptions &options)
{
assert(!prepared);
if (HPPApiVulkanSample::prepare(options))
{
prepare_camera();
load_assets();
create_point_sampler();
create_scene_constants_buffer();
create_descriptor_pool();
create_combine_pass();
create_images(extent.width, extent.height);
create_gather_pass();
create_background_pipeline();
update_scene_constants();
update_descriptors();
build_command_buffers();
prepared = true;
}
return prepared;
}
bool HPPOITDepthPeeling::resize(const uint32_t width, const uint32_t height)
{
create_images(width, height);
create_gather_pass_framebuffers(width, height);
update_descriptors();
return HPPApiVulkanSample::resize(width, height);
}
void HPPOITDepthPeeling::request_gpu_features(vkb::core::HPPPhysicalDevice &gpu)
{
if (gpu.get_features().samplerAnisotropy)
{
gpu.get_mutable_requested_features().samplerAnisotropy = VK_TRUE;
}
else
{
throw std::runtime_error("This sample requires support for anisotropic sampling");
}
}
void HPPOITDepthPeeling::build_command_buffers()
{
vk::CommandBufferBeginInfo command_buffer_begin_info;
std::array<vk::ClearValue, 2> clear_values = {{vk::ClearColorValue(std::array<float, 4>({{0.0f, 0.0f, 0.0f, 0.0f}})),
vk::ClearDepthStencilValue{0.0f, 0}}};
vk::RenderPassBeginInfo render_pass_begin_info{.renderArea = {{0, 0}, extent},
.clearValueCount = static_cast<uint32_t>(clear_values.size()),
.pClearValues = clear_values.data()};
vk::Viewport viewport{0.0f, 0.0f, static_cast<float>(extent.width), static_cast<float>(extent.height), 0.0f, 1.0f};
vk::Rect2D scissor{{0, 0}, extent};
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
{
auto const &command_buffer = draw_cmd_buffers[i];
command_buffer.begin(command_buffer_begin_info);
{
// Gather passes
// Each pass renders a single transparent layer into a layer texture.
for (uint32_t l = 0; l <= gui.layer_index_back; ++l)
{
// Two depth textures are used.
// Their roles alternates for each pass.
// The first depth texture is used for fixed-function depth test.
// The second one is the result of the depth test from the previous gatherPass pass.
// It is bound as texture and read in the shader to discard fragments from the
// previous layers.
vk::ImageSubresourceRange depth_subresource_range = {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1};
vkb::common::image_layout_transition(command_buffer,
depths[l % kDepthCount].image->get_handle(),
vk::PipelineStageFlagBits::eFragmentShader,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
vk::AccessFlagBits::eShaderRead,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
l <= 1 ? vk::ImageLayout::eUndefined : vk::ImageLayout::eDepthStencilReadOnlyOptimal,
vk::ImageLayout::eDepthStencilAttachmentOptimal,
depth_subresource_range);
if (l > 0)
{
vkb::common::image_layout_transition(command_buffer,
depths[(l + 1) % kDepthCount].image->get_handle(),
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
vk::AccessFlagBits::eShaderRead,
vk::ImageLayout::eDepthStencilAttachmentOptimal,
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
depth_subresource_range);
}
// Set one of the layer textures as color attachment, as the gatherPass pass will render to it.
vk::ImageSubresourceRange layer_subresource_range = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
vkb::common::image_layout_transition(command_buffer,
layers[l].image->get_handle(),
vk::PipelineStageFlagBits::eFragmentShader,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eShaderRead,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal,
layer_subresource_range);
render_pass_begin_info.framebuffer = layers[l].gather_framebuffer;
render_pass_begin_info.renderPass = gatherPass.render_pass;
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
{
// Render the geometry into the layer texture.
command_buffer.setViewport(0, viewport);
command_buffer.setScissor(0, scissor);
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gatherPass.pipeline_layout, 0, depths[l % kDepthCount].gather_descriptor_set, {});
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, l == 0 ? gatherPass.first_pipeline : gatherPass.pipeline);
draw_model(model, command_buffer);
}
command_buffer.endRenderPass();
// Get the layer texture ready to be read by the combinePass pass.
vkb::common::image_layout_transition(command_buffer,
layers[l].image->get_handle(),
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::AccessFlagBits::eShaderRead,
vk::ImageLayout::eColorAttachmentOptimal,
vk::ImageLayout::eShaderReadOnlyOptimal,
layer_subresource_range);
}
// Combine pass
// This pass blends all the layers into the final transparent color.
// The final color is then alpha blended into the background.
render_pass_begin_info.framebuffer = framebuffers[i];
render_pass_begin_info.renderPass = render_pass;
command_buffer.beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
{
command_buffer.setViewport(0, viewport);
command_buffer.setScissor(0, scissor);
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, combinePass.pipeline_layout, 0, combinePass.descriptor_set, {});
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, background.pipeline);
command_buffer.draw(3, 1, 0, 0);
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, combinePass.pipeline);
command_buffer.draw(3, 1, 0, 0);
draw_ui(command_buffer);
}
command_buffer.endRenderPass();
}
command_buffer.end();
}
}
void HPPOITDepthPeeling::on_update_ui_overlay(vkb::Drawer &drawer)
{
drawer.checkbox("Camera auto-rotation", &gui.camera_auto_rotation);
drawer.slider_float("Background grayscale", &gui.background_grayscale, kBackgroundGrayscaleMin, kBackgroundGrayscaleMax);
drawer.slider_float("Object opacity", &gui.object_opacity, kObjectAlphaMin, kObjectAlphaMax);
drawer.slider_int("Front layer index", &gui.layer_index_front, 0, gui.layer_index_back);
drawer.slider_int("Back layer index", &gui.layer_index_back, gui.layer_index_front, kLayerMaxCount - 1);
}
void HPPOITDepthPeeling::render(float delta_time)
{
if (prepared)
{
HPPApiVulkanSample::prepare_frame();
submit_info.setCommandBuffers(draw_cmd_buffers[current_buffer]);
queue.submit(submit_info);
HPPApiVulkanSample::submit_frame();
if (gui.camera_auto_rotation)
{
camera.rotate({delta_time * 5.0f, delta_time * 5.0f, 0.0f});
}
update_scene_constants();
}
}
////////////////////////////////////////////////////////////////////////////////
void HPPOITDepthPeeling::create_background_pipeline()
{
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("oit_depth_peeling/fullscreen.vert.spv", vk::ShaderStageFlagBits::eVertex),
load_shader("oit_depth_peeling/background.frag.spv", vk::ShaderStageFlagBits::eFragment)};
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)},
{1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPVertex, uv)}}};
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state{.depthCompareOp = vk::CompareOp::eGreater, .back = {.compareOp = vk::CompareOp::eAlways}};
background.pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
pipeline_cache,
shader_stages,
{},
vk::PrimitiveTopology::eTriangleList,
{},
vk::PolygonMode::eFill,
vk::CullModeFlagBits::eNone,
vk::FrontFace::eCounterClockwise,
{blend_attachment_state},
depth_stencil_state,
combinePass.pipeline_layout,
render_pass);
}
void HPPOITDepthPeeling::create_combine_pass()
{
vk::Device device = get_device().get_handle();
std::array<vk::DescriptorSetLayoutBinding, 3> set_layout_bindings = {
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
{2, vk::DescriptorType::eCombinedImageSampler, kLayerMaxCount, vk::ShaderStageFlagBits::eFragment}}};
combinePass.descriptor_set_layout = device.createDescriptorSetLayout({.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
combinePass.descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, combinePass.descriptor_set_layout);
combinePass.pipeline_layout = device.createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &combinePass.descriptor_set_layout});
create_combine_pass_pipeline();
}
void HPPOITDepthPeeling::create_combine_pass_pipeline()
{
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("oit_depth_peeling/fullscreen.vert.spv", vk::ShaderStageFlagBits::eVertex),
load_shader("oit_depth_peeling/combine.frag.spv", vk::ShaderStageFlagBits::eFragment)};
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)},
{1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPVertex, uv)}}};
vk::PipelineColorBlendAttachmentState blend_attachment_state{true,
vk::BlendFactor::eSrcAlpha,
vk::BlendFactor::eOneMinusSrcColor,
vk::BlendOp::eAdd,
vk::BlendFactor::eOne,
vk::BlendFactor::eZero,
vk::BlendOp::eAdd,
vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state{.depthCompareOp = vk::CompareOp::eGreater, .back = {.compareOp = vk::CompareOp::eAlways}};
combinePass.pipeline = vkb::common::create_graphics_pipeline(get_device().get_handle(),
pipeline_cache,
shader_stages,
{},
vk::PrimitiveTopology::eTriangleList,
{},
vk::PolygonMode::eFill,
vk::CullModeFlagBits::eNone,
vk::FrontFace::eCounterClockwise,
{blend_attachment_state},
depth_stencil_state,
combinePass.pipeline_layout,
render_pass);
}
void HPPOITDepthPeeling::create_descriptor_pool()
{
const uint32_t num_gather_pass_combined_image_sampler = kDepthCount;
const uint32_t num_gather_pass_uniform_buffer = kDepthCount;
const uint32_t num_combine_pass_combined_image_sampler = kLayerMaxCount + 1;
const uint32_t num_combine_pass_uniform_buffer = 1;
const uint32_t num_uniform_buffer_descriptors = num_gather_pass_uniform_buffer + num_combine_pass_uniform_buffer;
const uint32_t num_combined_image_sampler_descriptors = num_gather_pass_combined_image_sampler + num_combine_pass_combined_image_sampler;
std::array<vk::DescriptorPoolSize, 2> pool_sizes = {{{vk::DescriptorType::eUniformBuffer, num_uniform_buffer_descriptors},
{vk::DescriptorType::eCombinedImageSampler, num_combined_image_sampler_descriptors}}};
const uint32_t num_gather_descriptor_sets = 2;
const uint32_t num_combine_descriptor_sets = 1;
const uint32_t num_descriptor_sets = num_gather_descriptor_sets + num_combine_descriptor_sets;
vk::DescriptorPoolCreateInfo descriptor_pool_create_info{.maxSets = num_descriptor_sets,
.poolSizeCount = static_cast<uint32_t>(pool_sizes.size()),
.pPoolSizes = pool_sizes.data()};
descriptor_pool = get_device().get_handle().createDescriptorPool(descriptor_pool_create_info);
}
void HPPOITDepthPeeling::create_gather_pass()
{
create_gather_pass_descriptor_set_layout();
create_gather_pass_render_pass();
create_gather_pass_depth_descriptor_sets();
create_gather_pass_framebuffers(extent.width, extent.height);
gatherPass.pipeline_layout = get_device().get_handle().createPipelineLayout({.setLayoutCount = 1, .pSetLayouts = &gatherPass.descriptor_set_layout});
create_gather_pass_pipelines();
}
void HPPOITDepthPeeling::create_gather_pass_depth_descriptor_sets()
{
vk::Device device = get_device().get_handle();
for (auto &d : depths)
{
d.gather_descriptor_set = vkb::common::allocate_descriptor_set(device, descriptor_pool, gatherPass.descriptor_set_layout);
}
}
void HPPOITDepthPeeling::create_gather_pass_descriptor_set_layout()
{
std::array<vk::DescriptorSetLayoutBinding, 2> set_layout_bindings = {
{{0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}}};
gatherPass.descriptor_set_layout = get_device().get_handle().createDescriptorSetLayout(
{.bindingCount = static_cast<uint32_t>(set_layout_bindings.size()), .pBindings = set_layout_bindings.data()});
}
void HPPOITDepthPeeling::create_gather_pass_framebuffers(const uint32_t width, const uint32_t height)
{
vk::Device device = get_device().get_handle();
vk::FramebufferCreateInfo framebuffer_create_info{.width = width, .height = height, .layers = 1};
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
{
if (layers[i].gather_framebuffer)
{
device.destroyFramebuffer(layers[i].gather_framebuffer);
}
framebuffer_create_info.renderPass = gatherPass.render_pass;
const std::array<vk::ImageView, 2> attachments{layers[i].image_view->get_handle(), depths[i % kDepthCount].image_view->get_handle()};
framebuffer_create_info.setAttachments(attachments);
layers[i].gather_framebuffer = device.createFramebuffer(framebuffer_create_info);
}
}
void HPPOITDepthPeeling::create_gather_pass_pipelines()
{
vk::Device device = get_device().get_handle();
std::vector<vk::PipelineShaderStageCreateInfo> shader_stages = {load_shader("oit_depth_peeling/gather.vert.spv", vk::ShaderStageFlagBits::eVertex),
load_shader("oit_depth_peeling/gather_first.frag.spv", vk::ShaderStageFlagBits::eFragment)};
vk::VertexInputBindingDescription vertex_input_binding{0, sizeof(HPPVertex), vk::VertexInputRate::eVertex};
std::array<vk::VertexInputAttributeDescription, 2> vertex_input_attributes = {{{0, 0, vk::Format::eR32G32B32Sfloat, offsetof(HPPVertex, pos)},
{1, 0, vk::Format::eR32G32Sfloat, offsetof(HPPVertex, uv)}}};
vk::PipelineVertexInputStateCreateInfo vertex_input_state{.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &vertex_input_binding,
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_input_attributes.size()),
.pVertexAttributeDescriptions = vertex_input_attributes.data()};
vk::PipelineColorBlendAttachmentState blend_attachment_state{.colorWriteMask = vk::FlagTraits<vk::ColorComponentFlagBits>::allFlags};
vk::PipelineDepthStencilStateCreateInfo depth_stencil_state{
.depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vk::CompareOp::eGreater, .back = {.compareOp = vk::CompareOp::eAlways}};
gatherPass.first_pipeline = vkb::common::create_graphics_pipeline(device,
pipeline_cache,
shader_stages,
vertex_input_state,
vk::PrimitiveTopology::eTriangleList,
{},
vk::PolygonMode::eFill,
vk::CullModeFlagBits::eNone,
vk::FrontFace::eCounterClockwise,
{blend_attachment_state},
depth_stencil_state,
gatherPass.pipeline_layout,
gatherPass.render_pass);
shader_stages[1] = load_shader("oit_depth_peeling/gather.frag.spv", vk::ShaderStageFlagBits::eFragment);
gatherPass.pipeline = vkb::common::create_graphics_pipeline(device,
pipeline_cache,
shader_stages,
vertex_input_state,
vk::PrimitiveTopology::eTriangleList,
{},
vk::PolygonMode::eFill,
vk::CullModeFlagBits::eNone,
vk::FrontFace::eCounterClockwise,
{blend_attachment_state},
depth_stencil_state,
gatherPass.pipeline_layout,
gatherPass.render_pass);
}
void HPPOITDepthPeeling::create_gather_pass_render_pass()
{
std::array<vk::AttachmentDescription, 2> attachment_descriptions = {{{{},
vk::Format::eR8G8B8A8Unorm,
vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear,
vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare,
vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal},
{{},
vk::Format::eD32Sfloat,
vk::SampleCountFlagBits::e1,
vk::AttachmentLoadOp::eClear,
vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare,
vk::AttachmentStoreOp::eDontCare,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eDepthStencilAttachmentOptimal}}};
vk::AttachmentReference color_attachment_reference{0, vk::ImageLayout::eColorAttachmentOptimal};
vk::AttachmentReference depth_attachment_reference{1, vk::ImageLayout::eDepthStencilAttachmentOptimal};
vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment_reference,
.pDepthStencilAttachment = &depth_attachment_reference};
vk::RenderPassCreateInfo render_pass_create_info{.attachmentCount = static_cast<uint32_t>(attachment_descriptions.size()),
.pAttachments = attachment_descriptions.data(),
.subpassCount = 1,
.pSubpasses = &subpass};
gatherPass.render_pass = get_device().get_handle().createRenderPass(render_pass_create_info);
}
void HPPOITDepthPeeling::create_images(const uint32_t width, const uint32_t height)
{
const vk::Extent3D image_extent = {width, height, 1};
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
{
layers[i].image = std::make_unique<vkb::core::HPPImage>(get_device(),
image_extent,
vk::Format::eR8G8B8A8Unorm,
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eColorAttachment,
VMA_MEMORY_USAGE_GPU_ONLY,
vk::SampleCountFlagBits::e1);
layers[i].image_view = std::make_unique<vkb::core::HPPImageView>(*layers[i].image, vk::ImageViewType::e2D, vk::Format::eR8G8B8A8Unorm);
}
for (uint32_t i = 0; i < kDepthCount; ++i)
{
depths[i].image = std::make_unique<vkb::core::HPPImage>(get_device(),
image_extent,
vk::Format::eD32Sfloat,
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eDepthStencilAttachment,
VMA_MEMORY_USAGE_GPU_ONLY,
vk::SampleCountFlagBits::e1);
depths[i].image_view = std::make_unique<vkb::core::HPPImageView>(*depths[i].image, vk::ImageViewType::e2D, vk::Format::eD32Sfloat);
}
}
void HPPOITDepthPeeling::create_point_sampler()
{
point_sampler = vkb::common::create_sampler(
get_device().get_handle(), vk::Filter::eNearest, vk::Filter::eNearest, vk::SamplerMipmapMode::eNearest, vk::SamplerAddressMode::eClampToEdge, 1.0f, 1.0f);
}
void HPPOITDepthPeeling::create_scene_constants_buffer()
{
scene_constants =
std::make_unique<vkb::core::BufferCpp>(get_device(), sizeof(SceneConstants), vk::BufferUsageFlagBits::eUniformBuffer, VMA_MEMORY_USAGE_CPU_TO_GPU);
}
void HPPOITDepthPeeling::load_assets()
{
model = load_model("scenes/torusknot.gltf");
background.texture = load_texture("textures/vulkan_logo_full.ktx", vkb::scene_graph::components::HPPImage::Color);
}
void HPPOITDepthPeeling::prepare_camera()
{
camera.type = vkb::CameraType::LookAt;
camera.set_position({0.0f, 0.0f, -4.0f});
camera.set_rotation({0.0f, 0.0f, 0.0f});
camera.set_perspective(60.0f, static_cast<float>(extent.width) / static_cast<float>(extent.height), 16.0f, 0.1f);
}
void HPPOITDepthPeeling::update_descriptors()
{
vk::Device device = get_device().get_handle();
vk::DescriptorBufferInfo scene_constants_descriptor{scene_constants->get_handle(), 0, vk::WholeSize};
for (uint32_t i = 0; i < kDepthCount; ++i)
{
vk::DescriptorImageInfo depth_texture_descriptor{point_sampler,
depths[(i + 1) % kDepthCount].image_view->get_handle(),
vk::ImageLayout::eDepthStencilReadOnlyOptimal};
std::array<vk::WriteDescriptorSet, 2> write_descriptor_sets = {{{.dstSet = depths[i].gather_descriptor_set,
.dstBinding = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.pBufferInfo = &scene_constants_descriptor},
{.dstSet = depths[i].gather_descriptor_set,
.dstBinding = 1,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &depth_texture_descriptor}}};
device.updateDescriptorSets(write_descriptor_sets, {});
}
vk::DescriptorImageInfo background_texture_descriptor{background.texture.sampler,
background.texture.image->get_vk_image_view().get_handle(),
vk::ImageLayout::eShaderReadOnlyOptimal};
std::array<vk::DescriptorImageInfo, kLayerMaxCount> layer_texture_descriptor;
for (uint32_t i = 0; i < kLayerMaxCount; ++i)
{
layer_texture_descriptor[i].sampler = point_sampler;
layer_texture_descriptor[i].imageView = layers[i].image_view->get_handle();
layer_texture_descriptor[i].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
}
std::array<vk::WriteDescriptorSet, 3> write_descriptor_sets = {{{.dstSet = combinePass.descriptor_set,
.dstBinding = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.pBufferInfo = &scene_constants_descriptor},
{.dstSet = combinePass.descriptor_set,
.dstBinding = 1,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &background_texture_descriptor},
{.dstSet = combinePass.descriptor_set,
.dstBinding = 2,
.descriptorCount = static_cast<uint32_t>(layer_texture_descriptor.size()),
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = layer_texture_descriptor.data()}}};
device.updateDescriptorSets(write_descriptor_sets, {});
}
void HPPOITDepthPeeling::update_scene_constants()
{
SceneConstants constants = {};
constants.model_view_projection = camera.matrices.perspective * camera.matrices.view * glm::scale(glm::mat4(1.0f), glm::vec3(0.08));
constants.background_grayscale = gui.background_grayscale;
constants.object_opacity = gui.object_opacity;
constants.front_layer_index = gui.layer_index_front;
constants.back_layer_index = gui.layer_index_back;
scene_constants->convert_and_update(constants);
}
////////////////////////////////////////////////////////////////////////////////
std::unique_ptr<vkb::VulkanSampleCpp> create_hpp_oit_depth_peeling()
{
return std::make_unique<HPPOITDepthPeeling>();
}
@@ -0,0 +1,187 @@
/* Copyright (c) 2024, Google
* Copyright (c) 2024, NVIDIA
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "hpp_api_vulkan_sample.h"
class HPPOITDepthPeeling : public HPPApiVulkanSample
{
public:
HPPOITDepthPeeling();
~HPPOITDepthPeeling();
private:
// from vkb::Application
bool prepare(const vkb::ApplicationOptions &options) override;
bool resize(const uint32_t width, const uint32_t height) override;
// from vkb::VulkanSample
void request_gpu_features(vkb::core::HPPPhysicalDevice &gpu) override;
// from HPPApiVulkanSample
void build_command_buffers() override;
void on_update_ui_overlay(vkb::Drawer &drawer) override;
void render(float delta_time) override;
void create_background_pipeline();
void create_combine_pass();
void create_combine_pass_pipeline();
void create_descriptor_pool();
void create_gather_pass();
void create_gather_pass_depth_descriptor_sets();
void create_gather_pass_descriptor_set_layout();
void create_gather_pass_framebuffers(const uint32_t width, const uint32_t height);
void create_gather_pass_pipelines();
void create_gather_pass_render_pass();
void create_images(const uint32_t width, const uint32_t height);
void create_point_sampler();
void create_scene_constants_buffer();
void load_assets();
void prepare_camera();
void update_descriptors();
void update_scene_constants();
private:
static constexpr uint32_t kLayerMaxCount = 8;
static constexpr uint32_t kDepthCount = 2;
static constexpr float kBackgroundGrayscaleMin = 0.0f;
static constexpr float kBackgroundGrayscaleMax = 1.0f;
static constexpr float kObjectAlphaMin = 0.0f;
static constexpr float kObjectAlphaMax = 1.0f;
struct Background
{
vk::Pipeline pipeline = {};
HPPTexture texture = {};
void destroy(vk::Device device)
{
device.destroyPipeline(pipeline);
device.destroySampler(texture.sampler);
}
};
struct CombinePass
{
vk::DescriptorSet descriptor_set = {};
vk::DescriptorSetLayout descriptor_set_layout = {};
vk::Pipeline pipeline = {};
vk::PipelineLayout pipeline_layout = {};
void destroy(vk::Device device)
{
// descriptor_set is implicitly destroyed when the managing descriptor_pool is destroyed
device.destroyDescriptorSetLayout(descriptor_set_layout);
device.destroyPipeline(pipeline);
device.destroyPipelineLayout(pipeline_layout);
descriptor_set_layout = nullptr;
pipeline = nullptr;
pipeline_layout = nullptr;
}
};
struct Depth
{
vk::DescriptorSet gather_descriptor_set = {};
std::unique_ptr<vkb::core::HPPImage> image = {};
std::unique_ptr<vkb::core::HPPImageView> image_view = {};
void destroy()
{
image_view.reset();
image.reset();
}
};
struct GatherPass
{
vk::DescriptorSetLayout descriptor_set_layout = {};
vk::Pipeline first_pipeline = {};
vk::Pipeline pipeline = {};
vk::PipelineLayout pipeline_layout = {};
vk::RenderPass render_pass = {};
void destroy(vk::Device device)
{
device.destroyDescriptorSetLayout(descriptor_set_layout);
device.destroyPipeline(first_pipeline);
device.destroyPipeline(pipeline);
device.destroyPipelineLayout(pipeline_layout);
device.destroyRenderPass(render_pass);
descriptor_set_layout = nullptr;
first_pipeline = nullptr;
pipeline = nullptr;
pipeline_layout = nullptr;
render_pass = nullptr;
}
};
struct GUI
{
float background_grayscale = 0.3f;
int32_t camera_auto_rotation = false;
int32_t layer_index_back = kLayerMaxCount - 1;
int32_t layer_index_front = 0;
float object_opacity = 0.5f;
};
struct Layer
{
vk::Framebuffer gather_framebuffer = {};
std::unique_ptr<vkb::core::HPPImage> image = {};
std::unique_ptr<vkb::core::HPPImageView> image_view = {};
void destroy(vk::Device device)
{
device.destroyFramebuffer(gather_framebuffer);
image_view.reset();
image.reset();
gather_framebuffer = nullptr;
}
};
struct SceneConstants
{
glm::mat4 model_view_projection = {};
glm::f32 background_grayscale = {};
glm::f32 object_opacity = {};
glm::int32 front_layer_index = {};
glm::int32 back_layer_index = {};
};
private:
Background background = {};
CombinePass combinePass = {};
std::array<Depth, kDepthCount> depths = {};
vk::DescriptorPool descriptor_pool = {};
GatherPass gatherPass = {};
GUI gui = {};
std::array<Layer, kLayerMaxCount> layers = {};
std::unique_ptr<vkb::scene_graph::components::HPPSubMesh> model = {};
vk::Sampler point_sampler = {};
std::unique_ptr<vkb::core::BufferCpp> scene_constants = {};
};
std::unique_ptr<vkb::VulkanSampleCpp> create_hpp_oit_depth_peeling();