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),不同的佈局支持不同的圖象操作。此次是先將圖象轉化爲傳輸佈局的圖象(支持並加速拷貝),拷貝後恢復原佈局。