【游戏引擎开发日志4】ShadowMap生成阴影

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

概述

首先,来看一下实现效果:
在这里插入图片描述
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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值