Add slang shader for shadow mapping and shadow mapping cascades sample

This commit is contained in:
Sascha Willems 2025-04-28 21:03:37 +02:00
parent 126231756a
commit 153aa3b932
7 changed files with 436 additions and 8 deletions

View file

@ -1,10 +1,8 @@
/* /*
Vulkan Example - Cascaded shadow mapping for directional light sources Vulkan Example - Cascaded shadow mapping for directional light sources
Copyright by Sascha Willems - www.saschawillems.de Copyright (c) 2016-2025 by Sascha Willems - www.saschawillems.de
This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
/*
This example implements projective cascaded shadow mapping. This technique splits up the camera frustum into This example implements projective cascaded shadow mapping. This technique splits up the camera frustum into
multiple frustums with each getting its own full-res shadow map, implemented as a layered depth-only image. multiple frustums with each getting its own full-res shadow map, implemented as a layered depth-only image.
The shader then selects the proper shadow map layer depending on what split of the frustum the depth value The shader then selects the proper shadow map layer depending on what split of the frustum the depth value
@ -175,7 +173,7 @@ public:
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
// Floor // Floor
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
models.terrain.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout); models.terrain.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout);
// Trees // Trees
@ -189,7 +187,7 @@ public:
for (auto& position : positions) { for (auto& position : positions) {
pushConstBlock.position = glm::vec4(position, 0.0f); pushConstBlock.position = glm::vec4(position, 0.0f);
vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
// This will also bind the texture images to set 1 // This will also bind the texture images to set 1
models.tree.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout); models.tree.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout);
} }
@ -413,7 +411,7 @@ public:
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debugShadowMap); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debugShadowMap);
PushConstBlock pushConstBlock = {}; PushConstBlock pushConstBlock = {};
pushConstBlock.cascadeIndex = displayDepthMapCascadeIndex; pushConstBlock.cascadeIndex = displayDepthMapCascadeIndex;
vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0); vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
} }
@ -490,7 +488,7 @@ public:
// Shared pipeline layout (scene and depth map debug display) // Shared pipeline layout (scene and depth map debug display)
{ {
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0); VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 0);
std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage }; std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage };
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size())); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
@ -500,7 +498,7 @@ public:
// Depth pass pipeline layout // Depth pass pipeline layout
{ {
VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0); VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 0);
std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage }; std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage };
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size())); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pushConstantRangeCount = 1;

View file

@ -0,0 +1,23 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct UBO
{
float4x4 depthMVP;
};
ConstantBuffer<UBO> ubo;
[shader("vertex")]
float4 vertexMain(float3 Pos) : SV_POSITION
{
return mul(ubo.depthMVP, float4(Pos, 1.0));
}
[shader("fragment")]
float4 fragmentMain()
{
return float4(1.0, 0.0, 0.0, 1.0);
}

View file

@ -0,0 +1,48 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
float4x4 lightSpace;
float4 lightPos;
float zNear;
float zFar;
};
ConstantBuffer<UBO> ubo;
Sampler2D samplerColor;
float LinearizeDepth(float depth)
{
float n = ubo.zNear;
float f = ubo.zFar;
float z = depth;
return (2.0 * n) / (f + n - z * (f - n));
}
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float depth = samplerColor.Sample(input.UV).r;
return float4((1.0 - LinearizeDepth(depth)).xxx, 1.0);
}

View file

@ -0,0 +1,116 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float2 UV;
float3 Color;
float3 Normal;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float3 ViewVec;
float3 LightVec;
float4 ShadowCoord;
};
struct UBO
{
float4x4 projection;
float4x4 view;
float4x4 model;
float4x4 lightSpace;
float4 lightPos;
float zNear;
float zFar;
};
ConstantBuffer<UBO> ubo;
Sampler2D shadowMapSampler;
[SpecializationConstant] const int enablePCF = 0;
#define ambient 0.1
float textureProj(float4 shadowCoord, float2 off)
{
float shadow = 1.0;
if ( shadowCoord.z > -1.0 && shadowCoord.z < 1.0 )
{
float dist = shadowMapSampler.Sample(shadowCoord.xy + off).r;
if ( shadowCoord.w > 0.0 && dist < shadowCoord.z )
{
shadow = ambient;
}
}
return shadow;
}
float filterPCF(float4 sc)
{
int2 texDim;
shadowMapSampler.GetDimensions(texDim.x, texDim.y);
float scale = 1.5;
float dx = scale * 1.0 / float(texDim.x);
float dy = scale * 1.0 / float(texDim.y);
float shadowFactor = 0.0;
int count = 0;
int range = 1;
for (int x = -range; x <= range; x++)
{
for (int y = -range; y <= range; y++)
{
shadowFactor += textureProj(sc, float2(dx*x, dy*y));
count++;
}
}
return shadowFactor / count;
}
static const float4x4 biasMat = float4x4(
0.5, 0.0, 0.0, 0.5,
0.0, 0.5, 0.0, 0.5,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
[shader("vertex")]
VSOutput vertexMain(VSInput input)
{
VSOutput output = (VSOutput)0;
output.Color = input.Color;
output.Normal = input.Normal;
output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
output.Normal = mul((float3x3)ubo.model, input.Normal);
output.LightVec = normalize(ubo.lightPos.xyz - input.Pos);
output.ViewVec = -pos.xyz;
output.ShadowCoord = mul(biasMat, mul(ubo.lightSpace, mul(ubo.model, float4(input.Pos, 1.0))));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float shadow = (enablePCF == 1) ? filterPCF(input.ShadowCoord / input.ShadowCoord.w) : textureProj(input.ShadowCoord / input.ShadowCoord.w, float2(0.0, 0.0));
float3 N = normalize(input.Normal);
float3 L = normalize(input.LightVec);
float3 V = normalize(input.ViewVec);
float3 R = normalize(-reflect(L, N));
float3 diffuse = max(dot(N, L), ambient) * input.Color;
return float4(diffuse * shadow, 1.0);
}

View file

@ -0,0 +1,29 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
[[vk::binding(1,0)]] Sampler2DArray shadowMapSampler;
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
[shader("vertex")]
VSOutput vertexMain(uint VertexIndex: SV_VertexID)
{
VSOutput output;
output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input, uniform float4 meshPosition, uniform uint cascadeIndex)
{
float depth = shadowMapSampler.Sample(float3(input.UV, float(cascadeIndex))).r;
return float4(depth.xxx, 1.0);
}

View file

@ -0,0 +1,46 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
struct VSInput
{
float3 Pos;
float2 UV;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float2 UV;
};
// todo: pass via specialization constant
#define SHADOW_MAP_CASCADE_COUNT 4
struct UBO {
float4x4 cascadeViewProjMat[SHADOW_MAP_CASCADE_COUNT];
};
[[vk::binding(3, 0)]] ConstantBuffer<UBO> ubo;
[[vk::binding(0, 1)]] Sampler2D colorMapSampler : register(s0, space1);
[shader("vertex")]
VSOutput vertexMain(VSInput input, uniform float4 meshPosition, uniform uint cascadeIndex)
{
VSOutput output;
output.UV = input.UV;
float3 pos = input.Pos + meshPosition.xyz;
output.Pos = mul(ubo.cascadeViewProjMat[cascadeIndex], float4(pos, 1.0));
return output;
}
[shader("fragment")]
void fragmentMain(VSOutput input)
{
float alpha = colorMapSampler.Sample(input.UV).a;
if (alpha < 0.5) {
clip(-1);
}
}

View file

@ -0,0 +1,168 @@
/* Copyright (c) 2025, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
#define SHADOW_MAP_CASCADE_COUNT 4
struct VSInput
{
float3 Pos;
float2 UV;
float3 Color;
float3 Normal;
};
struct VSOutput
{
float4 Pos : SV_POSITION;
float3 Normal;
float3 Color;
float3 ViewPos;
float3 WorldPos;
float2 UV;
};
[SpecializationConstant] const int enablePCF = 0;
#define ambient 0.3
struct UBOScene {
float4x4 projection;
float4x4 view;
float4x4 model;
};
ConstantBuffer<UBOScene> uboScene;
[[vk::binding(1, 0)]] Sampler2DArray shadowMapSampler;
struct UBOCascades {
float4 cascadeSplits;
float4x4 inverseViewMat;
float3 lightDir;
float _pad;
int colorCascades;
};
[[vk::binding(2, 0)]] ConstantBuffer<UBOCascades> uboCascades;
struct CVPM {
float4x4 matrices[SHADOW_MAP_CASCADE_COUNT];
};
[[vk::binding(3, 0)]] ConstantBuffer<CVPM> cascadeViewProjMatrices;
[[vk::binding(0, 1)]] Sampler2D colorMapSampler;
static const float4x4 biasMat = float4x4(
0.5, 0.0, 0.0, 0.5,
0.0, 0.5, 0.0, 0.5,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
float textureProj(float4 shadowCoord, float2 offset, uint cascadeIndex)
{
float shadow = 1.0;
float bias = 0.005;
if (shadowCoord.z > -1.0 && shadowCoord.z < 1.0) {
float dist = shadowMapSampler.Sample(float3(shadowCoord.xy + offset, cascadeIndex)).r;
if (shadowCoord.w > 0 && dist < shadowCoord.z - bias) {
shadow = ambient;
}
}
return shadow;
}
float filterPCF(float4 sc, uint cascadeIndex)
{
int3 texDim;
shadowMapSampler.GetDimensions(texDim.x, texDim.y, texDim.z);
float scale = 0.75;
float dx = scale * 1.0 / float(texDim.x);
float dy = scale * 1.0 / float(texDim.y);
float shadowFactor = 0.0;
int count = 0;
int range = 1;
for (int x = -range; x <= range; x++) {
for (int y = -range; y <= range; y++) {
shadowFactor += textureProj(sc, float2(dx*x, dy*y), cascadeIndex);
count++;
}
}
return shadowFactor / count;
}
[shader("vertex")]
VSOutput vertexMain(VSInput input, uniform float4 meshPosition)
{
VSOutput output;
output.Color = input.Color;
output.Normal = input.Normal;
output.UV = input.UV;
float3 pos = input.Pos + meshPosition.xyz;
output.WorldPos = pos;
output.ViewPos = mul(uboScene.view, float4(pos.xyz, 1.0)).xyz;
output.Pos = mul(uboScene.projection, mul(uboScene.view, mul(uboScene.model, float4(pos.xyz, 1.0))));
return output;
}
[shader("fragment")]
float4 fragmentMain(VSOutput input)
{
float4 outFragColor;
float4 color = colorMapSampler.Sample(input.UV);
if (color.a < 0.5) {
clip(-1);
}
// Get cascade index for the current fragment's view position
uint cascadeIndex = 0;
for (uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) {
if (input.ViewPos.z < uboCascades.cascadeSplits[i]) {
cascadeIndex = i + 1;
}
}
// Depth compare for shadowing
float4 shadowCoord = mul(biasMat, mul(cascadeViewProjMatrices.matrices[cascadeIndex], float4(input.WorldPos, 1.0)));
float shadow = 0;
if (enablePCF == 1) {
shadow = filterPCF(shadowCoord / shadowCoord.w, cascadeIndex);
} else {
shadow = textureProj(shadowCoord / shadowCoord.w, float2(0.0, 0.0), cascadeIndex);
}
// Directional light
float3 N = normalize(input.Normal);
float3 L = normalize(-uboCascades.lightDir);
float3 H = normalize(L + input.ViewPos);
float diffuse = max(dot(N, L), ambient);
float3 lightColor = float3(1.0, 1.0, 1.0);
outFragColor.rgb = max(lightColor * (diffuse * color.rgb), float3(0.0, 0.0, 0.0));
outFragColor.rgb *= shadow;
outFragColor.a = color.a;
// Color cascades (if enabled)
if (uboCascades.colorCascades == 1) {
switch(cascadeIndex) {
case 0 :
outFragColor.rgb *= float3(1.0f, 0.25f, 0.25f);
break;
case 1 :
outFragColor.rgb *= float3(0.25f, 1.0f, 0.25f);
break;
case 2 :
outFragColor.rgb *= float3(0.25f, 0.25f, 1.0f);
break;
case 3 :
outFragColor.rgb *= float3(1.0f, 1.0f, 0.25f);
break;
}
}
return outFragColor;
}