Featured Article

Mastering Vulkan: Step-by-Step Shadow Tutorial

Cynthia Jul 13, 2026

Delving into the world of modern graphics rendering involves an understanding of the Vulkan API, a powerful, cross-platform, 3D graphics and computing API. Shadow mapping is a crucial aspect of rendering, allowing for realistic lighting simulations. This tutorial will guide you through implementing Vulkan shadow mapping, enabling you to enhance your applications' visuals with authentic, dynamic shadows.

the steps to drawing volcanos in adobe and photoshopped with text that reads, how
the steps to drawing volcanos in adobe and photoshopped with text that reads, how

Shadow mapping is a technique that involves rendering a scene from the perspective of each light source, creating a depth map or shadow map. The depth information in these maps is then used to determine whether points in the scene should be lit or in shadow. Mastering this technique can significantly improve your Vulkan graphics programming skills.

an image of lava in the sky with different colors and sizes, including oranges
an image of lava in the sky with different colors and sizes, including oranges

Understanding VulkanShadows

VulkanShadows is a comprehensive library that simplifies the process of implementing shadow mapping in Vulkan. It offers a plug-and-play solution, abstracting the low-level details of Vulkan, and allowing you to focus on the high-level aspects of graphics programming.

How to Draw a Volcano: Easy Steps + Pro Shading Tips
How to Draw a Volcano: Easy Steps + Pro Shading Tips

To begin, ensure you have VulkanShadows set up in your project. You can install it via vcpkg or clone the repository from GitHub. Once set up, you're ready to dive into the core concepts and APIs offered by VulkanShadows.

Shadow Map Generation

an image of a volcano made out of paper with the words make your own volcano
an image of a volcano made out of paper with the words make your own volcano

VulkanShadows offers a simple API for generating shadow maps. It provides a `VkShadowMapGenerator` class that encapsulates the process of rendering the scene from each light's perspective.

To generate a shadow map, create an instance of `VkShadowMapGenerator`, passing in your swapchain's extent, the number of cascade levels, and the light's projection matrix. Then, call the `generate` function, passing in the rendering device, command pool, and a function to handle recording the command buffer. Here's a simplified example:

```cpp #include int main() { // ... VkShadowMapGenerator mapGenerator(swapchainExtent, cascadeLevels, lightProjectionMatrix); mapGenerator.generate(renderDevice, commandPool, [&](VkCommandBuffer commandBuffer) { // Record commands for rendering. }); // ... } ```

Shadow Mapping Samplers

an image of different shapes and sizes of birds
an image of different shapes and sizes of birds

Once the shadow maps are generated, they need to be sampled during the fragment shader to determine whether a pixel is in shadow or not. VulkanShadows provides a utility function `createShadowSampler` that creates a shadow mapping sampler optimized for minimize aliasing artifacts.

Here's how you can create a shadow mapping sampler:

```cpp #include VkSampler createShadowSampler() { VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.compareEnable = VK_TRUE; samplerInfo.compareOp = VK_COMPARE_OP_LESS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; VkSampler shadowSampler; VkResult result = vkCreateSampler(device, &samplerInfo, nullptr, &shadowSampler); assert(result == VK_SUCCESS); return shadowSampler; } ```

Implementing Vulkan Shadow Mapping

How To Make A Volcano Diorama (Free Printable)
How To Make A Volcano Diorama (Free Printable)

Now that we've covered the basics of VulkanShadows, let's delve into how to integrate shadow mapping into your rendering pipeline.

First, you'll need to create a descriptor set layout that includes bindings for your shadow mapping sampler and texture. Then, allocate descriptor sets, and update them with the appropriate values during rendering. Lastly, use the shadow mapping sampler in your fragment shader to sample the shadow map and determine whether a pixel is in shadow or not.

an image of different facial expressions on a man's face with arrows pointing to his left and right sides
an image of different facial expressions on a man's face with arrows pointing to his left and right sides
a drawing of a man's head with the words how to paint shadows on it
a drawing of a man's head with the words how to paint shadows on it
two hands reaching for each other with their palms extended in front of the viewer's face
two hands reaching for each other with their palms extended in front of the viewer's face
an image of the face and head of a person with different facial shapes
an image of the face and head of a person with different facial shapes
Cre: maisonnookcoloring
Cre: maisonnookcoloring
Tutorial de sombra
Tutorial de sombra
15 Volcano Drawing Ideas - How To Draw Volcano
15 Volcano Drawing Ideas - How To Draw Volcano
-火山- 16マップセット - CLIP STUDIO ASSETS
-火山- 16マップセット - CLIP STUDIO ASSETS
a red line drawing of a volcano
a red line drawing of a volcano

Descriptor Set Layout

To create a descriptor set layout for shadow mapping, you'll need to define bindings for both the shadow mapping sampler (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`) and the shadow map texture (`VK_DESCRIPTOR_TYPE_STORAGE_IMAGE`). Here's an example layout:

```cpp #include void createDescriptorSetLayout(VkDevice device, VkDescriptorSetLayout* result) { VkDescriptorSetLayoutBinding bindings[2] = {}; bindings[0].binding = 0; bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; bindings[0].descriptorCount = 1; bindings[1].binding = 1; bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[1].descriptorCount = 1; VkDescriptorSetLayoutCreateInfo layoutInfo{}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = 2; layoutInfo.pBindings = bindings; VkDescriptorSetLayout layout; VkResult result = vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &layout); assert(result == VK_SUCCESS); *result = layout; } ```

Rendering with Shadows

Once you have your descriptor set layout and shadow mapping sampler set up, you can begin rendering your scene with dynamic shadows. First, update your descriptor sets with the appropriate shadow map and sampler. Then, record command buffers for rendering your scene, ensuring that you bind the correct descriptor set during the draw command.

Here's a simplified example of how you might update your descriptor sets and record a command buffer for rendering with Vulkan shadow mapping:

```cpp int main() { // ... // Update descriptor sets with shadow map and sampler // ... // Record command buffer for rendering VkCommandBuffer commandBuffer = beginRecordingSingleTimeCommands(renderDevice, commandPool); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdDraw(commandBuffer, static_cast(meshes.size()), 1, 0, 0); endRecordingSingleTimeCommands(renderDevice, graphicsQueue, commandBuffer); // ... } ```

The final paragraph of this tutorial is dedicated to a smooth, natural conclusion - a reminder that mastering Vulkan shadow mapping is a complex but rewarding task. With VulkanShadows, the process is simplified, allowing you to focus on the creative aspects of graphics programming. Keep experimenting, learning, and refining your skills, and soon, dynamic, realistic shadows will be a staple in your Vulkan applications.