概述
首先,来看一下实现效果:

ShadowMap是一种游戏常用的阴影技术,效果好,且实现起来比较简单,具有较好的性能。
它的思路如下:首先,把灯光当成摄像机,渲染一张灯光视角下物体的深度图,然后在摄像机渲染的阶段对这张深度图进行采样,比较当前片元和深度图里存储的对应位置片元的深度差异,如果前者深度更小,就说明它的位置没有阴影遮蔽。

那么,对于一个多光源的场景来说,选择哪一盏灯光进行阴影生成呢?我个人认为应该对于每一盏灯都设置一个是否生成阴影的属性,所有需要生成阴影的灯光都独立地生成深度图。不过,在我的小引擎中为了简化,只对一盏灯光生成了阴影。
下一个问题是:如果把灯光作为相机,那么应该选择透视相机还是正交相机?如何设置相机的各个参数?
透视投影更常用在点光源和聚光灯上,正交投影则在定向光上使用较多。
在本项目中,我采用了透视投影。
对于透视投影中参数的设置,则并不是固定的。而且,还可以对距离摄像机远近不同的区域设置不同的光源相机参数,实现更好的效果,这是级联阴影技术(Cascaded Shadow Maps)。
在本项目中,我根据调参结果选择了几个固定的常数作为光源相机参数。
流程

上图是阴影的渲染流程(忽略了最后一个把图像渲染到Imgui Image的Render Pass)。
需要重点注意ShadowPass以及它对应的RenderTarget和管线的创建配置,不能和普通的Render Pass保持完全一致。
Shadow Map对应Render Target创建
Render Target只包含一个深度图,该深度图的Image Sampler创建参数如下:
depthMapCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
depthMapCreateInfo.maxAnisotropy = 1.0f;
depthMapCreateInfo.magFilter = VK_FILTER_NEAREST;
depthMapCreateInfo.minFilter = VK_FILTER_NEAREST;
depthMapCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
depthMapCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
depthMapCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
depthMapCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
depthMapCreateInfo.mipLodBias = 0.0f;
depthMapCreateInfo.maxAnisotropy = 1.0f;
depthMapCreateInfo.minLod = 0.0f;
depthMapCreateInfo.maxLod = 1.0f;
depthMapCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
需要重点关注addressModeU和addressModeV参数,不要把它们设置为Repeat的模式。
创建Image代码如下:
std::shared_ptr<VulkanImage>depthImage = std::make_shared<VulkanImage>(
extent.width,
extent.height,
depthFormat,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
0
);
这里注意,需要指定VK_IMAGE_USAGE枚举值,不要出错。
创建ImageView代码如下:
depthAttachments[i] = std::make_shared<VulkanImageView>(depthImage,
depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT);
depthAttachments[i]->SetDescriptorImageInfo(VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
//descriptor info
void VulkanImageView::setDescriptorImageInfo(VkImageLayout layout)
{
imageInfo.reset();
imageInfo = std::make_shared<VkDescriptorImageInfo>();
imageInfo->imageLayout = layout;
imageInfo->imageView = imageView;
imageInfo->sampler = sampler->GetSampler();
}
创建Image View的代码和普通的图像差异不大,只是要注意,因为这张深度图将要用于采样,所以需要为它设置DescriptorImageInfo,注意ImageLayout的拼写不要拼错了,Vulkan中有一个枚举值和VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL非常相似。
Shadow Pass创建
接下来,需要创建Shadow Pass。
Shadow Pass创建首先需要指定Attachment类型,注意finalLayout一定要和深度图Descriptor Info里的layout保持一致;其次,要添加一个SubPass,并添加一个外部进入该SubPass的Dependency和从SubPass离开的Dependency。
代码如下:
//shaodow pass
shadowRenderPass = std::make_shared<VulkanRenderPass>();
uint32_t depthIndex1, subPassIndex1;
shadowRenderPass->AddAttachment(depthIndex1,
VulkanDevice::Get()->findSupportedFormat(
{
VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
VK_IMAGE_TILING_OPTIMAL,
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT),
VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
);
shadowRenderPass->AddSubPass(
subPassIndex1,
VK_PIPELINE_BIND_POINT_GRAPHICS,
{
},
depthIndex1
);
shadowRenderPass->AddDependency(
VK_SUBPASS_EXTERNAL,
subPassIndex1,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
shadowRenderPass->AddDependency

本文详细介绍了使用Vulkan技术实现ShadowMap阴影效果的过程,包括深度图的创建、RenderPass和ShadowPass的配置,以及顶点和片元着色器的编写,重点讲解了渲染管线的配置和级联阴影技术的应用。
最低0.47元/天 解锁文章
1550

被折叠的 条评论
为什么被折叠?



