Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/vulkan/CarControlDemo/CarShadow.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#version 450

layout(push_constant) uniform ShadowTransform {
mat4 light_model_view_projection;
} transform;

layout(location = 0) in vec3 inPos;

void main() {
gl_Position = transform.light_model_view_projection * vec4(inPos, 1.0);
}
19 changes: 19 additions & 0 deletions examples/vulkan/CarControlDemo/Ground.frag
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,32 @@

layout(location = 0) in vec2 fragUV;
layout(location = 1) in vec2 fragWorldXZ;
layout(location = 2) in vec4 fragLightClip;

layout(binding = 1) uniform sampler2D groundTex;
layout(binding = 2) uniform sampler2DShadow shadowMap;

layout(location = 0) out vec4 outColor;

float ShadowVisibility() {
vec3 light = fragLightClip.xyz / fragLightClip.w;
vec2 uv = light.xy * 0.5 + 0.5;
if (light.z <= 0.0 || light.z >= 1.0) {
return 1.0;
}
vec2 texel = 1.0 / vec2(textureSize(shadowMap, 0));
float visibility = 0.0;
for (int y = -1; y <= 1; ++y) {
for (int x = -1; x <= 1; ++x) {
visibility += texture(shadowMap, vec3(uv + vec2(x, y) * texel, light.z));
}
}
return visibility / 9.0;
}

void main() {
vec3 sampled = texture(groundTex, fragUV).rgb;
sampled *= mix(0.45, 1.0, ShadowVisibility());

// Fade the textured plane toward the horizon color so its edge doesn't
// read as a hard seam against the skybox.
Expand Down
3 changes: 3 additions & 0 deletions examples/vulkan/CarControlDemo/Ground.vert
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
layout(binding = 0) uniform UniformBufferObject {
mat4 view;
mat4 project;
mat4 light_view_projection;
} ubo;

layout(location = 0) in vec3 inPos;

layout(location = 0) out vec2 fragUV;
layout(location = 1) out vec2 fragWorldXZ;
layout(location = 2) out vec4 fragLightClip;

void main() {
// One texture tile every 4 world units, so the asphalt feels grounded in
// real scale.
fragUV = inPos.xz * 0.25;
fragWorldXZ = inPos.xz;
fragLightClip = ubo.light_view_projection * vec4(inPos, 1.0);
gl_Position = ubo.project * ubo.view * vec4(inPos, 1.0);
}
3 changes: 2 additions & 1 deletion examples/vulkan/CarControlDemo/RenderCar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ const std::vector<uint32_t> RenderCar::LoadFragmentShader() const {
std::vector<VkVertexInputBindingDescription> RenderCar::GetVertexBindingDescriptions() const {
VkVertexInputBindingDescription b{};
b.binding = 0;
b.stride = sizeof(Vertex);
static_assert(sizeof(Vertex) == kVertexStride);
b.stride = kVertexStride;
b.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
return {b};
}
Expand Down
5 changes: 5 additions & 0 deletions examples/vulkan/CarControlDemo/RenderCar.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ class RenderCar : public core::vulkan::VulkanRender {

void UpdateUniformBuffer(const glm::mat4& model, const glm::mat4& view, const glm::mat4& project);

const core::vulkan::VulkanBuffer& vertex_buffer() const { return vertex_buffer_local_; }
const core::vulkan::VulkanBuffer& index_buffer() const { return index_buffer_local_; }
uint32_t index_count() const { return static_cast<uint32_t>(indices_.size()); }
static constexpr uint32_t kVertexStride = 9 * sizeof(float);

protected:
VkCullModeFlags SetCullMode() const override { return VK_CULL_MODE_BACK_BIT; }
VkFrontFace SetFrontFace() const override { return VK_FRONT_FACE_COUNTER_CLOCKWISE; }
Expand Down
184 changes: 184 additions & 0 deletions examples/vulkan/CarControlDemo/RenderCarShadow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#include "RenderCarShadow.h"

#include <stdexcept>

namespace core {

RenderCarShadow::RenderCarShadow(core::vulkan::VulkanContext* context, VkFormat depth_format,
uint32_t size)
: core::vulkan::VulkanRender(context, DepthOnlyInfo(depth_format)),
depth_format_(depth_format),
size_(size),
dynamic_rendering_(context) {
if (size_ == 0) {
throw std::invalid_argument("Shadow map size must be positive");
}
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(context_->physical_device, depth_format_, &properties);
const VkFormatFeatureFlags required =
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
if ((properties.optimalTilingFeatures & required) != required) {
throw std::runtime_error("Shadow map format cannot be rendered and sampled");
}
const bool linear_filter =
(properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;

shadow_map_ = core::vulkan::VulkanImage(
context_, size_, size_, depth_format_,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

const VkFilter filter = linear_filter ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
const VkSamplerCreateInfo sampler_info{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.magFilter = filter,
.minFilter = filter,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 1.0f,
.compareEnable = VK_TRUE,
.compareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
.minLod = 0.0f,
.maxLod = 0.0f,
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
.unnormalizedCoordinates = VK_FALSE,
};
VK_CHECK(vkCreateSampler(context_->logical_device, &sampler_info, nullptr, &sampler_));
}

RenderCarShadow::~RenderCarShadow() {
if (sampler_ != VK_NULL_HANDLE) {
vkDestroySampler(context_->logical_device, sampler_, nullptr);
}
}

void RenderCarShadow::Init() {
core::vulkan::VulkanRender::Init();

const core::vulkan::VulkanCommandBuffer command =
core::vulkan::VulkanCommandBuffer::BeginOneTimeCommands(context_);
const VkImageMemoryBarrier to_shader_read{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = shadow_map_.image,
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1},
};
vkCmdPipelineBarrier(command.buffer(), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1,
&to_shader_read);
command.EndOneTimeCommands();
}

void RenderCarShadow::Record(VkCommandBuffer command_buffer, const RenderCar& car,
const glm::mat4& light_model_view_projection) const {
const VkImageSubresourceRange range{.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1};
const VkImageMemoryBarrier to_attachment{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = shadow_map_.image,
.subresourceRange = range,
};
vkCmdPipelineBarrier(
command_buffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, 0, 0,
nullptr, 0, nullptr, 1, &to_attachment);

const VkExtent2D extent{.width = size_, .height = size_};
const VkClearValue clear{.depthStencil = {.depth = 1.0f, .stencil = 0}};
dynamic_rendering_.BeginDepthRendering(command_buffer, shadow_map_.image_view, extent, clear);
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
const VkViewport viewport{.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(size_),
.height = static_cast<float>(size_),
.minDepth = 0.0f,
.maxDepth = 1.0f};
vkCmdSetViewport(command_buffer, 0, 1, &viewport);
const VkRect2D scissor{.offset = {.x = 0, .y = 0}, .extent = extent};
vkCmdSetScissor(command_buffer, 0, 1, &scissor);
vkCmdPushConstants(command_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(glm::mat4), &light_model_view_projection);
const VkBuffer vertex_buffer = car.vertex_buffer().buffer;
const VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(command_buffer, 0, 1, &vertex_buffer, &offset);
vkCmdBindIndexBuffer(command_buffer, car.index_buffer().buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(command_buffer, car.index_count(), 1, 0, 0, 0);
dynamic_rendering_.EndDynamicRendering(command_buffer);

const VkImageMemoryBarrier to_shader_read{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = shadow_map_.image,
.subresourceRange = range,
};
vkCmdPipelineBarrier(
command_buffer,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &to_shader_read);
}

std::vector<VkPushConstantRange> RenderCarShadow::GetPushConstantRanges() const {
return {VkPushConstantRange{
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(glm::mat4)}};
}

const std::vector<uint32_t> RenderCarShadow::LoadVertexShader() const {
static const std::vector<uint32_t> shader_code =
#include "CarShadow.vert.spv"
;
return shader_code;
}

std::vector<VkVertexInputBindingDescription> RenderCarShadow::GetVertexBindingDescriptions()
const {
return {VkVertexInputBindingDescription{.binding = 0,
.stride = RenderCar::kVertexStride,
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX}};
}

std::vector<VkVertexInputAttributeDescription> RenderCarShadow::GetVertexAttributeDescriptions()
const {
return {VkVertexInputAttributeDescription{
.location = 0, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = 0}};
}

core::vulkan::DynamicRenderingInfo RenderCarShadow::DepthOnlyInfo(VkFormat depth_format) {
core::vulkan::DynamicRenderingInfo info{};
info.depth_format = depth_format;
return info;
}

}
56 changes: 56 additions & 0 deletions examples/vulkan/CarControlDemo/RenderCarShadow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#pragma once

#include <optional>
#include <vector>

#include "RenderCar.h"
#include "VulkanDynamicRendering.h"
#include "VulkanImage.h"
#include "VulkanRender.h"

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include "glm/glm.hpp"

namespace core {

class RenderCarShadow : public core::vulkan::VulkanRender {
public:
RenderCarShadow(core::vulkan::VulkanContext* context, VkFormat depth_format, uint32_t size);
~RenderCarShadow() override;
RenderCarShadow(const RenderCarShadow&) = delete;
RenderCarShadow& operator=(const RenderCarShadow&) = delete;

void Init() override;
void Record(VkCommandBuffer command_buffer, const RenderCar& car,
const glm::mat4& light_model_view_projection) const;

VkImageView shadow_map_view() const { return shadow_map_.image_view; }
VkSampler shadow_sampler() const { return sampler_; }

protected:
VkCullModeFlags SetCullMode() const override { return VK_CULL_MODE_NONE; }
VkBool32 SetDepthTesting() const override { return VK_TRUE; }
VkBool32 SetDepthWriting() const override { return VK_TRUE; }
std::optional<core::vulkan::DepthBiasInfo> SetDepthBias() const override {
return core::vulkan::DepthBiasInfo{.constant_factor = 1.25f, .slope_factor = 1.75f};
}

std::vector<core::vulkan::BindingInfo> GetBindingInfo() const override { return {}; }
std::vector<VkPushConstantRange> GetPushConstantRanges() const override;
const std::vector<uint32_t> LoadVertexShader() const override;
const std::vector<uint32_t> LoadFragmentShader() const override { return {}; }
std::vector<VkVertexInputBindingDescription> GetVertexBindingDescriptions() const override;
std::vector<VkVertexInputAttributeDescription> GetVertexAttributeDescriptions() const override;

private:
static core::vulkan::DynamicRenderingInfo DepthOnlyInfo(VkFormat depth_format);

VkFormat depth_format_;
uint32_t size_;
core::vulkan::VulkanDynamicRendering dynamic_rendering_;
core::vulkan::VulkanImage shadow_map_;
VkSampler sampler_ = VK_NULL_HANDLE;
};

}
24 changes: 19 additions & 5 deletions examples/vulkan/CarControlDemo/RenderGround.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ void RenderGround::Init() {

CreateUniformBufferDescriptorSet(0, uniform_buffer_);
CreateCombinedImageSamplerDescriptorSet(1, texture_image_.image_view, sampler_.sampler);
CreateCombinedImageSamplerDescriptorSet(2, shadow_map_view_, shadow_sampler_);
vkUpdateDescriptorSets(context_->logical_device, writes_.size(), writes_.data(), 0, nullptr);

vertex_buffer_staging_.MapData([this](void* data) {
Expand All @@ -29,12 +30,20 @@ void RenderGround::Init() {
index_buffer_staging_.CopyToBuffer(index_buffer_local_);

uniform_buffer_.MapData([](void* data) {
UniformBufferObject init{glm::mat4(1.0f), glm::mat4(1.0f)};
UniformBufferObject init{.view = glm::mat4(1.0f),
.project = glm::mat4(1.0f),
.light_view_projection = glm::mat4(1.0f)};
memcpy(data, &init, sizeof(UniformBufferObject));
});
}

void RenderGround::Init(const std::string& texture_path) {
void RenderGround::Init(const std::string& texture_path, VkImageView shadow_map_view,
VkSampler shadow_sampler) {
if (shadow_map_view == VK_NULL_HANDLE || shadow_sampler == VK_NULL_HANDLE) {
throw std::invalid_argument("Ground rendering requires a shadow map and sampler");
}
shadow_map_view_ = shadow_map_view;
shadow_sampler_ = shadow_sampler;
CreateTextureImage(texture_path);
CreateBuffers();
Init();
Expand Down Expand Up @@ -66,17 +75,22 @@ void RenderGround::Render(VkCommandBuffer command_buffer, VkExtent2D extent) {
vkCmdDrawIndexed(command_buffer, static_cast<uint32_t>(indices_.size()), 1, 0, 0, 0);
}

void RenderGround::UpdateUniformBuffer(const glm::mat4& view, const glm::mat4& project) {
void RenderGround::UpdateUniformBuffer(const glm::mat4& view, const glm::mat4& project,
const glm::mat4& light_view_projection) {
uniform_buffer_.MapData([&](void* data) {
UniformBufferObject ubo{view, project};
UniformBufferObject ubo{
.view = view, .project = project, .light_view_projection = light_view_projection};
ubo.project[1][1] *= -1; // Vulkan Y-flip
memcpy(data, &ubo, sizeof(UniformBufferObject));
});
}

std::vector<core::vulkan::BindingInfo> RenderGround::GetBindingInfo() const {
return {{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT},
{1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT}};
{1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT},
{.binding = 2,
.descriptor_type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.shader_stage = VK_SHADER_STAGE_FRAGMENT_BIT}};
}

const std::vector<uint32_t> RenderGround::LoadVertexShader() const {
Expand Down
Loading
Loading