Vulkan-點光源陰影映射

Vulkan-點光源陰影映射

  Cubemap是一種特殊數據結構,以其特殊的採樣方式實現了球空間數據的採集。經典的使用是實現點光源的陰影映射。

  在openGL中實現cubemap紋理的生成,有兩種方式:

  • 連續使用六次渲染,每次渲染cubemap的一面
  • 在幾何着色器中直接投影六個方向,一次渲染

  兩種渲染方式的性能在openL中差異不大。此次vulkan實現cubemap採用第一種方式。

  vulkan實現較爲複雜,因爲vulkan不支持直接將cubemap格式的紋理直接作爲幀緩衝(FrameBuffer)的顏色附件(Color Attachment)。所以,只能將每一次渲染的結果拷貝到cubemap的一個面。拷貝指令需要記錄到vulkan的commanBuffer中。但是由於CommandBuffer的不可修改(可連續記錄,記錄後不可修改)和塊提交(爲提高性能,所有六次渲染會被全部記錄到commandBuffer中,一次全部執行),常用的uniform變量不能滿足每次渲染一個cubemap面時,相機的參數改變。因此,此次實現關鍵分爲兩部分:

  • 使用push constant變量保存相機變換矩陣
  • 複製幀緩衝到cuemap的一個面

1 push constant使用

  簡單介紹一下push constant,push constant是在管線佈局(Pipeline Layout)中定義的着色器推送常量(shader push constants)。該常量並不通過在CPU端的內存拷貝更新(uniform變量是通過內存拷貝完成更新),而是通過vulkan指令(vkCmdPushConstants)實現。除卻數據更新速度的差異,該常量對應的是在管線級別的數據更新。因此可以被用作每一次渲染,相機矩陣view矩陣數據的更新(投影矩陣不需要更新)。

  使用push constant,在vulkan中實現如下:

  • 在shader中定義代碼如下:

    layout(push_constant) uniform ViewVec
    {
    	vec3 lookDir;
    	vec3 lookUp;
    } viewVec;
    
  • 在vulkan中需要配置與shader中對應的資源類型。在vulkan中,與shader中對應的資源據需要自己配置。

VkPushConstantRange ConstantRange;
ConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
ConstantRange.size = sizeof(SLookParmter);//SLookParmter是ViewVec數據類型
ConstantRange.offset = 0;
  • 在管線中配置該數據類型,我認爲主要是分配數據空間:

    PipelineLayoutInfo.pushConstantRangeCount = vPushConstants.size();
    PipelineLayoutInfo.pPushConstantRanges = vPushConstants.data();
    //vpushConstants是對應VkPushConstantRange的數據
    
  • 使用vulkan指令更新push constant:

    switch (vFaceIndex)
    	{
    	case 0:
    		LookDir = glm::vec3(1.0f, 0.0f, 0.0f);//不能生成
    		LookUp = glm::vec3(0.0f, -1.0f, 0.0f);
    		break;
    	case 1:
    		LookDir = glm::vec3(-1.0f, 0.0f, 0.0f);
    		LookUp = glm::vec3(0.0f, -1.0f, 0.0f);
    		break;
    	case 2:
    		LookDir = glm::vec3(0.0f, 1.0f, 0.0f);
    		LookUp = glm::vec3(0.0f, 0.0f, 1.0f);
    		break;
    	case 3:
    		LookDir = glm::vec3(0.0f, -1.0f, 0.0f);
    		LookUp = glm::vec3(0.0f, 0.0f, -1.0f);
    		break;
    	case 4:
    		LookDir = glm::vec3(0.0f, 0.0f, 1.0f);
    		LookUp = glm::vec3(0.0f, -1.0f, 0.0f);
    		break;
    	default:
    		LookDir = glm::vec3(0.0f, 0.0f, -1.0f);
    		LookUp = glm::vec3(0.0f, -1.0f, 0.0f);
    		break;
    	}
    m_LookParmter.LookDir = LookDir;
    m_LookParmter.LookUp = LookUp;
    vkCmdPushConstants(vCommandBuffer, vPass.m_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(SLookParmter), &m_LookParmter);
    

 以上爲cubemap中每個face的配置方式(lookDir和lookUp矢量),爲了能夠動態的調整cubemap的中心位置,將每個表面的矢量通過putconstants常量傳入pipeline中,在shader內部動態計算ViewMatrix。計算方式與glm::lookAtRH()函數一致如下:

mat4 calcuteViewMatrix(vec3 vPosition,vec3 vCenter,vec3 vUp)
{
    mat4 ViewMatrix={//創建單位矩陣
	{1.0,0.0,0.0,0.0},
	{0.0,1.0,0.0,0.0},
	{0.0,0.0,1.0,0.0},
	{0.0,0.0,0.0,1.0}
	};
	vec3 Dir = normalize(vCenter-vPosition);
	vec3 Right = normalize(cross(Dir,vUp));
	vec3 Up = cross(Right,Dir);

	ViewMatrix[0][0]= Right.x;
	ViewMatrix[1][0]= Right.y;
	ViewMatrix[2][0]= Right.z;
	ViewMatrix[3][0]= -dot(Right,vPosition);

	ViewMatrix[0][1]= Up.x;
	ViewMatrix[1][1]= Up.y;
	ViewMatrix[2][1]= Up.z;
	ViewMatrix[3][1]= -dot(Up,vPosition);

	ViewMatrix[0][2] = -Dir.x;
	ViewMatrix[1][2] = -Dir.y;
	ViewMatrix[2][2] = -Dir.z;
	ViewMatrix[3][2] = dot(Dir,vPosition);

	return  ViewMatrix;
}

  使用此種方式建立ViewMatrix,默認使用opengl座標系(因爲glm::lookAtRH()函數爲openGL配置)配置。因此,在傳入ProjMatrix時,不需要執行Pro[-1][-1]*=-1操作,這樣生成的幀緩衝圖像不會倒置。(VulKan與OpenGl的投影矩陣y座標相反)。

2 cubemap圖像(紋理)使用

  • 創建CubeMap。實現函數的輸入參數,vExtent2D爲分辨率,vFroma爲圖象格式, vFlag標識圖象資源,CTextureInfo是自己封裝的數據結構
class CTextureInfo
{
public:
	uint32_t m_MipLevels;
	VkImage m_TextureImage;
	VkDeviceMemory m_TextureImageMemory;
	VkImageView m_TextureImageView;
	VkSampler m_TextureSampler;

	std::string m_TextureName;
	int m_BindindNum;
	VkFormat m_TextureFormat;

	bool IsDepthTexture = false;
};

void CMyVulKanApplication::__createCubemap(VkExtent2D vExtent2D,VkFormat vFormat,VkImageAspectFlags vFlag, CTextureInfo& voCubeMapTextureInfos, bool vUseMip)
{
	voCubeMapTextureInfos.m_MipLevels = 1;
	//voCubeMapTextureInfos.IsDepthTexture = true;
	VkImageCreateInfo ImageCreateInfo = {}; //初始化 否者報錯**
	ImageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
	ImageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
	ImageCreateInfo.format = vFormat;
	ImageCreateInfo.extent ={vExtent2D.width,vExtent2D.height, 1 };
	ImageCreateInfo.mipLevels = voCubeMapTextureInfos.m_MipLevels;
	ImageCreateInfo.arrayLayers = 6;
	ImageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
	ImageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
	ImageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
	ImageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
	ImageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
	ImageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;

    //創建cubemap Image
	if (vkCreateImage(m_Device, &ImageCreateInfo, nullptr, &voCubeMapTextureInfos.m_TextureImage) !=VK_SUCCESS)
		throw std::runtime_error("failed to create image!");
	voCubeMapTextureInfos.m_TextureFormat = vFormat;

	VkMemoryRequirements MemRequirements;
	vkGetImageMemoryRequirements(m_Device, voCubeMapTextureInfos.m_TextureImage, &MemRequirements);
	VkMemoryAllocateInfo AllocInfo = {};
	AllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
	AllocInfo.allocationSize = MemRequirements.size;
	AllocInfo.memoryTypeIndex = __findMemoryType(MemRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

	if (vkAllocateMemory(m_Device, &AllocInfo,nullptr, &voCubeMapTextureInfos.m_TextureImageMemory) != VK_SUCCESS)
		throw std::runtime_error("failed to allocate image memory!");

	vkBindImageMemory(m_Device, voCubeMapTextureInfos.m_TextureImage, voCubeMapTextureInfos.m_TextureImageMemory,0);
	
     //創建sampler
	VkSamplerCreateInfo SamplerCreateInfo = {};
	SamplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
	SamplerCreateInfo.magFilter = VK_FILTER_LINEAR;
	SamplerCreateInfo.minFilter = VK_FILTER_LINEAR;
	SamplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
	SamplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
	SamplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
	SamplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
	SamplerCreateInfo.mipLodBias = 0.0f;
	SamplerCreateInfo.maxAnisotropy = 1.0f;
	SamplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
	SamplerCreateInfo.minLod = 0.0f;
	SamplerCreateInfo.maxLod = 1.0f;
	SamplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
	if(vkCreateSampler(m_Device,&SamplerCreateInfo,nullptr,&voCubeMapTextureInfos.m_TextureSampler )!=VK_SUCCESS)
		throw std::runtime_error("failed to create image sampler!");
    
    //創建Imageview
	VkImageViewCreateInfo ImageViewCreateInfo = {};
	ImageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	ImageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
	ImageViewCreateInfo.format = vFormat;
	ImageViewCreateInfo.subresourceRange = { vFlag,0,1,0,1 };
	ImageViewCreateInfo.subresourceRange.layerCount = 6;
	ImageViewCreateInfo.image = voCubeMapTextureInfos.m_TextureImage;
	if (vkCreateImageView(m_Device, &ImageViewCreateInfo, nullptr, &voCubeMapTextureInfos.m_TextureImageView) != VK_SUCCESS)\
		throw std::runtime_error("failed to create image view!");
}

  cubemap紋理的生成跟尋常的紋理資源一致,需要生成對應的Image,ImageView以及可選的sampler。只是配置的數據類型差異而已。

  • 拷貝到Cubemap
vkCmdEndRenderPass(m_DrawCommandBuffers[i]);//確保幀緩衝已經生成完畢

			VkImageSubresourceRange ColorResourceRange = {};//用作layout轉換
			ColorResourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			ColorResourceRange.baseMipLevel = 0;
			ColorResourceRange.layerCount = 1;
			ColorResourceRange.levelCount = 1;
			ColorResourceRange.baseArrayLayer = 0;

			VkImageSubresourceRange CubefaceResourceRange = {};//用作layout轉換
			CubefaceResourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			CubefaceResourceRange.baseArrayLayer = m;
			CubefaceResourceRange.baseMipLevel = 0;
			CubefaceResourceRange.layerCount = 1;
			CubefaceResourceRange.levelCount = 1;
			__transitionImageLayout1(m_Passes[0].m_ColorAttachCubemaps[0].m_TextureImage, VK_IMAGE_LAYOUT_UNDEFINED , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, CubefaceResourceRange, m_DrawCommandBuffers[i]);
			__transitionImageLayout1((m_Passes[0].m_ColorAttacments[0])->m_TextureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, ColorResourceRange, m_DrawCommandBuffers[i]);

			VkImageCopy CopyRegion = {};
			CopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			CopyRegion.srcSubresource.baseArrayLayer = 0;
			CopyRegion.srcSubresource.mipLevel = 0;
			CopyRegion.srcSubresource.layerCount = 1;
			CopyRegion.srcOffset = { 0,0,0 };

			CopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
			CopyRegion.dstSubresource.baseArrayLayer = m;
			CopyRegion.dstSubresource.mipLevel = 0;
			CopyRegion.dstSubresource.layerCount = 1;
			CopyRegion.dstOffset = { 0,0,0 };

			CopyRegion.extent.width = m_Passes[0].m_Extent2D.width;
			CopyRegion.extent.height = m_Passes[0].m_Extent2D.height;
			CopyRegion.extent.depth = 1;

			vkCmdCopyImage(m_DrawCommandBuffers[i], (m_Passes[0].m_ColorAttacments[0])->m_TextureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
				m_Passes[0].m_ColorAttachCubemaps[0].m_TextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &CopyRegion);

			__transitionImageLayout1(m_TestTexture->m_TextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
				ColorResourceRange, m_DrawCommandBuffers[i]);
                __transitionImageLayout1(m_Passes[0].m_ColorAttachCubemaps[0].m_TextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, CubefaceResourceRange, m_DrawCommandBuffers[i]);
			__transitionImageLayout1((m_Passes[0].m_ColorAttacments[0])->m_TextureImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, ColorResourceRange, m_DrawCommandBuffers[i]);

  上訴的i值代表渲染的第i個面。vkCmdCopyImage是vulkan拷貝指令,需要的數據是VkImageCopy,負責制定拷貝的數據。爲加快拷貝的數據或者支持拷貝,需要更換圖象佈局(Layout),不同的佈局支持不同的圖象操作。此次是先將圖象轉化爲傳輸佈局的圖象(支持並加速拷貝),拷貝後恢復原佈局。

3結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章