我将为 generateIrradianceCube
函数提供逐行中文注释和分析。这个函数是用 C++ 编写的,使用 Vulkan API 生成用于物理基础渲染 (PBR) 的辐照度立方体贴图 (irradiance cube map)。该函数创建一个立方体贴图,设置离屏渲染管线,并将环境贴图渲染为具有多级渐进纹理 (mip levels) 的辐照度立方体贴图。以下是对每一行或逻辑块的详细分析和注释,解释其功能和作用。
void generateIrradianceCube()
{// 开始计时,使用高精度时钟记录函数执行的开始时间。auto tStart = std::chrono::high_resolution_clock::now();
- 目的:记录函数开始时间,用于测量生成辐照度立方体贴图的总耗时。
- 细节:使用
std::chrono::high_resolution_clock
提供高精度的时间测量。
const VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;const int32_t dim = 64;const uint32_t numMips = static_cast<uint32_t>(floor(log2(dim))) + 1;
- 注释:
format
:定义图像格式为VK_FORMAT_R32G32B32A32_SFLOAT
,表示每个像素使用 32 位浮点数存储 RGBA 四个通道,提供高精度颜色数据,适合辐照度计算。dim
:设置立方体贴图的尺寸为 64x64 像素。numMips
:计算mipmaps(多级渐进纹理)的数量。使用log2(dim)
计算最大mip级别(例如,64 -> 6 级),并加 1 包含基础级别(0 级)。
// 预过滤的立方体贴图// 图像VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();imageCI.imageType = VK_IMAGE_TYPE_2D;imageCI.format = format;imageCI.extent.width = dim;imageCI.extent.height = dim;imageCI.extent.depth = 1;imageCI.mipLevels = numMips;imageCI.arrayLayers = 6;imageCI.samples = VK_SAMPLE_COUNT_1_BIT;imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.irradianceCube.image));
- 目的:创建用于存储辐照度立方体贴图的 Vulkan 图像对象。
- 逐行分析:
VkImageCreateInfo imageCI
:初始化图像创建信息结构体。imageType = VK_IMAGE_TYPE_2D
:指定图像类型为 2D,尽管是立方体贴图,但 Vulkan 使用 2D 图像数组表示。format
:使用之前定义的浮点格式。extent.width/height = dim
:设置图像宽高为 64 像素,深度为 1。mipLevels = numMips
:设置mip级别数量。arrayLayers = 6
:立方体贴图有 6 个面(正负 X、Y、Z 方向)。samples = VK_SAMPLE_COUNT_1_BIT
:禁用多重采样,单次采样即可。tiling = VK_IMAGE_TILING_OPTIMAL
:使用最佳平铺模式,优化 GPU 访问。usage
:图像用于采样(VK_IMAGE_USAGE_SAMPLED_BIT
)和作为传输目标(VK_IMAGE_USAGE_TRANSFER_DST_BIT
)。flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT
:标记图像为立方体贴图兼容。vkCreateImage
:调用 Vulkan API 创建图像对象,存储到textures.irradianceCube.image
。
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();VkMemoryRequirements memReqs;vkGetImageMemoryRequirements(device, textures.irradianceCube.image, &memReqs);memAlloc.allocationSize = memReqs.size;memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.irradianceCube.deviceMemory));VK_CHECK_RESULT(vkBindImageMemory(device, textures.irradianceCube.image, textures.irradianceCube.deviceMemory, 0));
- 目的:为立方体贴图图像分配显存并绑定。
- 逐行分析:
VkMemoryAllocateInfo memAlloc
:初始化显存分配信息。vkGetImageMemoryRequirements
:获取图像的显存需求(大小、对齐方式等)。memAlloc.allocationSize = memReqs.size
:设置分配的显存大小。memAlloc.memoryTypeIndex
:选择支持设备本地内存(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
)的显存类型。vkAllocateMemory
:分配显存,存储到textures.irradianceCube.deviceMemory
。vkBindImageMemory
:将分配的显存绑定到图像对象。
// 图像视图VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE;viewCI.format = format;viewCI.subresourceRange = {};viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;viewCI.subresourceRange.levelCount = numMips;viewCI.subresourceRange.layerCount = 6;viewCI.image = textures.irradianceCube.image;VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.irradianceCube.view));
- 目的:创建图像视图,用于访问立方体贴图的图像数据。
- 逐行分析:
VkImageViewCreateInfo viewCI
:初始化图像视图创建信息。viewType = VK_IMAGE_VIEW_TYPE_CUBE
:指定视图类型为立方体贴图。format
:使用与图像相同的格式。subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT
:指定访问颜色数据。subresourceRange.levelCount = numMips
:包括所有mip级别。subresourceRange.layerCount = 6
:包括立方体贴图的 6 个面。viewCI.image
:关联到之前创建的图像。vkCreateImageView
:创建图像视图,存储到textures.irradianceCube.view
。
// 采样器VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();samplerCI.magFilter = VK_FILTER_LINEAR;samplerCI.minFilter = VK_FILTER_LINEAR;samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;samplerCI.minLod = 0.0f;samplerCI.maxLod = static_cast<float>(numMips);samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.irradianceCube.sampler));
- 目的:创建采样器,用于在着色器中采样立方体贴图。
- 逐行分析:
VkSamplerCreateInfo samplerCI
:初始化采样器创建信息。magFilter/minFilter = VK_FILTER_LINEAR
:使用线性过滤进行放大和缩小,提供平滑的纹理采样。mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR
:启用线性 mip 过渡。addressModeU/V/W = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE
:纹理坐标超出范围时,钳位到边缘,避免重复或镜像。minLod/maxLod
:设置 mip 级别的范围,从 0 到最大 mip 级别。borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE
:设置边界颜色为不透明白色(当使用钳位时)。vkCreateSampler
:创建采样器,存储到textures.irradianceCube.sampler
。
textures.irradianceCube.descriptor.imageView = textures.irradianceCube.view;textures.irradianceCube.descriptor.sampler = textures.irradianceCube.sampler;textures.irradianceCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;textures.irradianceCube.device = vulkanDevice;
- 目的:设置描述符,用于在着色器中绑定立方体贴图资源。
- 逐行分析:
descriptor.imageView
:绑定图像视图。descriptor.sampler
:绑定采样器。descriptor.imageLayout
:设置图像布局为只读最优,适合在着色器中采样。device = vulkanDevice
:记录 Vulkan 设备指针。
// 帧缓冲、附件、渲染通道等VkAttachmentDescription attDesc = {};attDesc.format = format;attDesc.samples = VK_SAMPLE_COUNT_1_BIT;attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
- 目的:定义渲染通道的颜色附件。
- 逐行分析:
VkAttachmentDescription attDesc
:初始化附件描述结构体。format
:使用与立方体贴图相同的格式。samples = VK_SAMPLE_COUNT_1_BIT
:单次采样。loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR
:加载时清除附件内容。storeOp = VK_ATTACHMENT_STORE_OP_STORE
:存储渲染结果。stencilLoadOp/stencilStoreOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE
:不使用模板缓冲。initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
:初始布局未定义。finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
:最终布局为颜色附件最优。VkAttachmentReference colorReference
:定义颜色附件引用,索引为 0,布局为颜色附件最优。
VkSubpassDescription subpassDescription = {};subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;subpassDescription.colorAttachmentCount = 1;subpassDescription.pColorAttachments = &colorReference;
- 目的:定义子通道,指定渲染管线类型和颜色附件。
- 逐行分析:
VkSubpassDescription subpassDescription
:初始化子通道描述。pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS
:指定图形管线。colorAttachmentCount = 1
:一个颜色附件。pColorAttachments = &colorReference
:绑定颜色附件引用。
// 使用子通道依赖来管理布局转换std::array<VkSubpassDependency, 2> dependencies;dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;dependencies[0].dstSubpass = 0;dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;dependencies[1].srcSubpass = 0;dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
- 目的:定义子通道依赖,确保正确的图像布局转换和同步。
- 逐行分析:
std::array<VkSubpassDependency, 2>
:创建两个子通道依赖。- 第一个依赖(外部 -> 子通道 0):
srcSubpass = VK_SUBPASS_EXTERNAL
:来源是外部子通道。dstSubpass = 0
:目标是第一个子通道。srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT
:等待管线底部完成。dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
:目标阶段为颜色附件输出。srcAccessMask = VK_ACCESS_MEMORY_READ_BIT
:来源访问为内存读取。dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
:目标访问为颜色附件读写。dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
:按区域依赖,优化性能。
- 第二个依赖(子通道 0 -> 外部):
- 与第一个依赖相反,确保子通道完成后,数据可以被外部访问。
// 渲染通道VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();renderPassCI.attachmentCount = 1;renderPassCI.pAttachments = &attDesc;renderPassCI.subpassCount = 1;renderPassCI.pSubpasses = &subpassDescription;renderPassCI.dependencyCount = 2;renderPassCI.pDependencies = dependencies.data();VkRenderPass renderpass;VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
- 目的:创建渲染通道,用于离屏渲染。
- 逐行分析:
VkRenderPassCreateInfo renderPassCI
:初始化渲染通道创建信息。attachmentCount = 1
:一个附件(颜色附件)。pAttachments = &attDesc
:绑定附件描述。subpassCount = 1
:一个子通道。pSubpasses = &subpassDescription
:绑定子通道描述。dependencyCount = 2
:两个依赖。pDependencies = dependencies.data()
:绑定依赖数组。vkCreateRenderPass
:创建渲染通道,存储到renderpass
。
struct {VkImage image;VkImageView view;VkDeviceMemory memory;VkFramebuffer framebuffer;} offscreen;
- 目的:定义离屏渲染的帧缓冲结构,包含图像、视图、显存和帧缓冲。
- 细节:
offscreen
结构体用于临时存储离屏渲染的资源。
// 离屏帧缓冲{VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;imageCreateInfo.format = format;imageCreateInfo.extent.width = dim;imageCreateInfo.extent.height = dim;imageCreateInfo.extent.depth = 1;imageCreateInfo.mipLevels = 1;imageCreateInfo.arrayLayers = 1;imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image));
- 目的:创建离屏渲染的颜色附件图像。
- 逐行分析:
imageCreateInfo
:初始化图像创建信息。imageType = VK_IMAGE_TYPE_2D
:2D 图像。format
:使用相同的浮点格式。extent.width/height = dim
:尺寸为 64x64。mipLevels = 1
:离屏渲染不需要 mip 级别。arrayLayers = 1
:单层图像。samples = VK_SAMPLE_COUNT_1_BIT
:单次采样。tiling = VK_IMAGE_TILING_OPTIMAL
:优化 GPU 访问。initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
:初始布局未定义。usage
:用作颜色附件(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
)和传输源(VK_IMAGE_USAGE_TRANSFER_SRC_BIT
)。sharingMode = VK_SHARING_MODE_EXCLUSIVE
:独占模式。vkCreateImage
:创建图像,存储到offscreen.image
。
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();VkMemoryRequirements memReqs;vkGetImageMemoryRequirements(device, offscreen.image, &memReqs);memAlloc.allocationSize = memReqs.size;memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory));VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0));
- 目的:为离屏图像分配显存并绑定。
- 逐行分析:与之前立方体贴图的显存分配类似,分配设备本地显存并绑定到
offscreen.image
。
VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;colorImageView.format = format;colorImageView.flags = 0;colorImageView.subresourceRange = {};colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;colorImageView.subresourceRange.baseMipLevel = 0;colorImageView.subresourceRange.levelCount = 1;colorImageView.subresourceRange.baseArrayLayer = 0;colorImageView.subresourceRange.layerCount = 1;colorImageView.image = offscreen.image;VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view));
- 目的:创建离屏图像的视图。
- 逐行分析:
viewType = VK_IMAGE_VIEW_TYPE_2D
:2D 视图。format
:与图像格式一致。subresourceRange
:访问颜色数据,单 mip 级别,单层。image = offscreen.image
:关联到离屏图像。vkCreateImageView
:创建视图,存储到offscreen.view
。
VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();fbufCreateInfo.renderPass = renderpass;fbufCreateInfo.attachmentCount = 1;fbufCreateInfo.pAttachments = &offscreen.view;fbufCreateInfo.width = dim;fbufCreateInfo.height = dim;fbufCreateInfo.layers = 1;VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer));
- 目的:创建离屏帧缓冲。
- 逐行分析:
renderPass = renderpass
:关联到之前创建的渲染通道。attachmentCount = 1
:一个附件(颜色附件)。pAttachments = &offscreen.view
:绑定离屏图像视图。width/height = dim
:帧缓冲尺寸为 64x64。layers = 1
:单层。vkCreateFramebuffer
:创建帧缓冲,存储到offscreen.framebuffer
。
VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);vks::tools::setImageLayout(layoutCmd,offscreen.image,VK_IMAGE_ASPECT_COLOR_BIT,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
- 目的:将离屏图像的布局转换为颜色附件最优。
- 逐行分析:
createCommandBuffer
:创建一个主命令缓冲区,开启录制。setImageLayout
:将offscreen.image
的布局从未定义转换为颜色附件最优。flushCommandBuffer
:提交并执行命令缓冲区,完成布局转换。
// 描述符VkDescriptorSetLayout irrdescriptorsetlayout;std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),};VkDescriptorSetLayoutCreateInfo irrdescriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &irrdescriptorsetlayoutCI, nullptr, &irrdescriptorsetlayout));
- 目的:创建描述符集布局,用于在着色器中绑定环境贴图。
- 逐行分析:
setLayoutBindings
:定义一个绑定,类型为组合图像采样器(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
),用于片段着色器,绑定点为 0。irrdescriptorsetlayoutCI
:初始化描述符集布局创建信息。vkCreateDescriptorSetLayout
:创建描述符集布局,存储到irrdescriptorsetlayout
。
// 描述符池std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };VkDescriptorPoolCreateInfo irrdescriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);VkDescriptorPool irrdescriptorpool;VK_CHECK_RESULT(vkCreateDescriptorPool(device, &irrdescriptorPoolCI, nullptr, &irrdescriptorpool));
- 目的:创建描述符池,用于分配描述符集。
- 逐行分析:
poolSizes
:指定池支持一个组合图像采样器。irrdescriptorPoolCI
:初始化描述符池创建信息,最大支持 2 个描述符集。vkCreateDescriptorPool
:创建描述符池,存储到irrdescriptorpool
。
// 描述符集VkDescriptorSet irrdescriptorset;VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(irrdescriptorpool, &irrdescriptorsetlayout, 1);VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &irrdescriptorset));VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(irrdescriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor);vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
- 目的:分配并更新描述符集,绑定环境立方体贴图。
- 逐行分析:
allocInfo
:初始化描述符集分配信息,关联到描述符池和布局。vkAllocateDescriptorSets
:分配描述符集,存储到irrdescriptorset
。writeDescriptorSet
:定义描述符写入操作,绑定环境立方体贴图的描述符(textures.environmentCube.descriptor
)。vkUpdateDescriptorSets
:更新描述符集,完成绑定。
// 管线布局struct PushBlock {glm::mat4 mvp;float deltaPhi = (2.0f * float(M_PI)) / 180.0f;float deltaTheta = (0.5f * float(M_PI)) / 64.0f;} pushBlock;
- 目的:定义推送常量块,包含 MVP 矩阵和采样角度增量。
- 逐行分析:
PushBlock
:结构体包含:mvp
:模型-视图-投影矩阵,用于变换。deltaPhi
:水平采样角度增量 (2π/180,约 2 度)。deltaTheta
:垂直采样角度增量 (π/2 / 64,约 1.4 度)。
VkPipelineLayout irrpipelinelayout;std::vector<VkPushConstantRange> pushConstantRanges = {vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0),};VkPipelineLayoutCreateInfo irrpipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&irrdescriptorsetlayout, 1);irrpipelineLayoutCI.pushConstantRangeCount = 1;irrpipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();VK_CHECK_RESULT(vkCreatePipelineLayout(device, &irrpipelineLayoutCI, nullptr, &irrpipelinelayout));
- 目的:创建管线布局,包含描述符集布局和推送常量。
- 逐行分析:
pushConstantRanges
:定义推送常量范围,适用于顶点和片段着色器,大小为PushBlock
。irrpipelineLayoutCI
:初始化管线布局创建信息,绑定描述符集布局。pushConstantRangeCount = 1
:一个推送常量范围。pPushConstantRanges
:绑定推送常量范围。vkCreatePipelineLayout
:创建管线布局,存储到irrpipelinelayout
。
// 管线VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
- 目的:定义图形管线的各个状态。
- 逐行分析:
inputAssemblyState
:使用三角形列表拓扑,无原始重启。rasterizationState
:填充多边形,无背面剔除,逆时针为正面。blendAttachmentState
:禁用颜色混合。colorBlendState
:绑定一个颜色混合附件状态。depthStencilState
:禁用深度和模板测试。viewportState
:支持一个视口和裁剪矩形。multisampleState
:禁用多重采样。dynamicStateEnables
:启用动态视口和裁剪矩形。shaderStages
:定义两个着色器阶段(顶点和片段)。
VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(irrpipelinelayout, renderpass);pipelineCI.pInputAssemblyState = &inputAssemblyState;pipelineCI.pRasterizationState = &rasterizationState;pipelineCI.pColorBlendState = &colorBlendState;pipelineCI.pMultisampleState = &multisampleState;pipelineCI.pViewportState = &viewportState;pipelineCI.pDepthStencilState = &depthStencilState;pipelineCI.pDynamicState = &dynamicState;pipelineCI.stageCount = 2;pipelineCI.pStages = shaderStages.data();pipelineCI.renderPass = renderpass;pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
- 目的:初始化图形管线创建信息。
- 逐行分析:
pipelineCI
:绑定管线布局和渲染通道。- 绑定各个管线状态(输入装配、光栅化、颜色混合等)。
stageCount = 2
:两个着色器阶段。pStages = shaderStages.data()
:绑定着色器阶段数组。pVertexInputState
:定义顶点输入状态,包含位置、法线和 UV 坐标。
shaderStages[0] = loadShader(getShadersPath() + "lightprobesh/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);shaderStages[1] = loadShader(getShadersPath() + "lightprobesh/irradiancecube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);VkPipeline pipeline;VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
- 目的:加载着色器并创建图形管线。
- 逐行分析:
shaderStages[0]
:加载顶点着色器 (filtercube.vert.spv
)。shaderStages[1]
:加载片段着色器 (irradiancecube.frag.spv
),用于辐照度计算。vkCreateGraphicsPipelines
:创建图形管线,存储到pipeline
。
// 渲染VkClearValue clearValues[1];clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
- 目的:定义清除颜色,用于渲染开始时清除帧缓冲。
- 细节:清除颜色为深蓝色 (RGB: 0, 0, 0.2, Alpha: 0)。
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();renderPassBeginInfo.renderPass = renderpass;renderPassBeginInfo.framebuffer = offscreen.framebuffer;renderPassBeginInfo.renderArea.extent.width = dim;renderPassBeginInfo.renderArea.extent.height = dim;renderPassBeginInfo.clearValueCount = 1;renderPassBeginInfo.pClearValues = clearValues;
- 目的:初始化渲染通道开始信息。
- 逐行分析:
renderPass = renderpass
:绑定离屏渲染通道。framebuffer = offscreen.framebuffer
:绑定离屏帧缓冲。renderArea.extent
:渲染区域为 64x64。clearValueCount = 1
:一个清除值。pClearValues = clearValues
:绑定清除颜色。
std::vector<glm::mat4> matrices = {glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)),};
- 目的:定义立方体贴图六个面的视图矩阵。
- 细节:每个矩阵对应一个面(+X, -X, +Y, -Y, +Z, -Z),通过旋转矩阵调整视角方向。
VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);vkCmdSetViewport(cmdBuf, 0, 1, &viewport);vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
- 目的:创建命令缓冲区并设置初始视口和裁剪矩形。
- 逐行分析:
createCommandBuffer
:创建主命令缓冲区并开始录制。viewport
:设置视口为 64x64。scissor
:设置裁剪矩形为 64x64。vkCmdSetViewport/Scissor
:应用视口和裁剪设置。
VkImageSubresourceRange subresourceRange = {};subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;subresourceRange.baseMipLevel = 0;subresourceRange.levelCount = numMips;subresourceRange.layerCount = 6;vks::tools::setImageLayout(cmdBuf,textures.irradianceCube.image,VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,subresourceRange);
- 目的:将立方体贴图的布局转换为传输目标。
- 逐行分析:
subresourceRange
:指定颜色数据,所有 mip 级别和 6 个面。setImageLayout
:将图像布局从未定义转换为传输目标最优,准备接收渲染数据。
for (uint32_t m = 0; m < numMips; m++) {for (uint32_t f = 0; f < 6; f++) {viewport.width = static_cast<float>(dim * std::pow(0.5f, m));viewport.height = static_cast<float>(dim * std::pow(0.5f, m));vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
- 目的:为每个 mip 级别和每个面进行渲染,动态调整视口大小。
- 逐行分析:
- 双重循环遍历所有 mip 级别和 6 个面。
viewport.width/height
:根据 mip 级别缩小尺寸(每级减半)。vkCmdSetViewport
:更新视口大小。
vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f];vkCmdPushConstants(cmdBuf, irrpipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock);vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, irrpipelinelayout, 0, 1, &irrdescriptorset, 0, NULL);models.skybox.draw(cmdBuf);vkCmdEndRenderPass(cmdBuf);
- 目的:执行渲染,生成每个面的辐照度贴图。
- 逐行分析:
vkCmdBeginRenderPass
:开始渲染通道,内联执行子通道内容。pushBlock.mvp
:计算投影矩阵(90 度 FOV,1:1 宽高比,近裁剪 0.1,远裁剪 512)与视图矩阵的乘积。vkCmdPushConstants
:推送常量数据到着色器。vkCmdBindPipeline
:绑定图形管线。vkCmdBindDescriptorSets
:绑定描述符集,包含环境贴图。models.skybox.draw
:绘制天空盒,执行辐照度计算。vkCmdEndRenderPass
:结束渲染通道。
vks::tools::setImageLayout(cmdBuf,offscreen.image,VK_IMAGE_ASPECT_COLOR_BIT,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
- 目的:将离屏图像布局转换为传输源,准备复制数据。
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 = f;copyRegion.dstSubresource.mipLevel = m;copyRegion.dstSubresource.layerCount = 1;copyRegion.dstOffset = { 0, 0, 0 };copyRegion.extent.width = static_cast<uint32_t>(viewport.width);copyRegion.extent.height = static_cast<uint32_t>(viewport.height);copyRegion.extent.depth = 1;vkCmdCopyImage(cmdBuf,offscreen.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,textures.irradianceCube.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,©Region);
- 目的:将离屏渲染结果复制到立方体贴图的对应面和 mip 级别。
- 逐行分析:
copyRegion
:定义图像复制区域。srcSubresource
:源为离屏图像,单 mip 级别,单层。dstSubresource
:目标为立方体贴图的第 f 面,第 m 个 mip 级别。extent
:复制区域大小与当前视口一致。vkCmdCopyImage
:执行图像复制操作。
vks::tools::setImageLayout(cmdBuf,offscreen.image,VK_IMAGE_ASPECT_COLOR_BIT,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
- 目的:将离屏图像布局转换回颜色附件最优,为下一次渲染做准备。
vks::tools::setImageLayout(cmdBuf,textures.irradianceCube.image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,subresourceRange);
- 目的:将立方体贴图的布局转换为着色器只读最优,完成渲染。
vulkanDevice->flushCommandBuffer(cmdBuf, queue);
- 目的:提交并执行命令缓冲区,完成所有渲染和复制操作。
vkDestroyRenderPass(device, renderpass, nullptr);vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr);vkFreeMemory(device, offscreen.memory, nullptr);vkDestroyImageView(device, offscreen.view, nullptr);vkDestroyImage(device, offscreen.image, nullptr);vkDestroyDescriptorPool(device, irrdescriptorpool, nullptr);vkDestroyDescriptorSetLayout(device, irrdescriptorsetlayout, nullptr);vkDestroyPipeline(device, pipeline, nullptr);vkDestroyPipelineLayout(device, irrpipelinelayout, nullptr);
- 目的:清理所有临时 Vulkan 资源。
- 逐行分析:销毁渲染通道、帧缓冲、显存、图像视图、图像、描述符池、描述符集布局、管线和管线布局,释放资源。
auto tEnd = std::chrono::high_resolution_clock::now();auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();std::cout << "Generating irradiance cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl;
}
- 目的:记录结束时间,计算并输出生成辐照度立方体贴图的耗时。
- 逐行分析:
tEnd
:记录结束时间。tDiff
:计算时间差(毫秒)。std::cout
:输出 mip 级别数量和总耗时。
总结
generateIrradianceCube
函数通过 Vulkan API 创建并渲染一个辐照度立方体贴图,用于 PBR 光照计算。主要步骤包括:
- 创建立方体贴图图像、视图和采样器。
- 设置离屏渲染的帧缓冲和渲染通道。
- 配置描述符和管线,加载着色器。
- 为每个 mip 级别和每个面渲染天空盒,生成辐照度数据。
- 将渲染结果复制到立方体贴图。
- 清理临时资源并输出执行时间。
每个步骤都通过 Vulkan 的资源管理和渲染管线精心组织,确保高效生成高质量的辐照度贴图。