天空盒渲染的 VkRenderPass 加载操作问题
Issue with VkRenderPass load operation for Skybox rendering
关于 Vulkan 中的渲染通道,我似乎还有另一个问题。
绘制我的场景时,我首先提交命令缓冲区以使用大气散射将天空渲染到立方体贴图上,然后我将其用于前向传递以绘制出天空和太阳。
绘制天空盒并存储到立方体贴图中进行采样时使用的渲染通道:
m_pFrameBuffer = rhi->CreateFrameBuffer();
VkImageView attachment = m_RenderTexture->View();
VkAttachmentDescription attachDesc = CreateAttachmentDescription(
m_RenderTexture->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
m_RenderTexture->Samples()
);
VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
std::array<VkSubpassDependency, 2> dependencies;
dependencies[0] = CreateSubPassDependency(
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
dependencies[1] = CreateSubPassDependency(
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
VkSubpassDescription subpassDesc = { };
subpassDesc.colorAttachmentCount = 1;
subpassDesc.pColorAttachments = &colorRef;
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
VkRenderPassCreateInfo renderpassCi = { };
renderpassCi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderpassCi.attachmentCount = 1;
renderpassCi.pAttachments = &attachDesc;
renderpassCi.dependencyCount = static_cast<u32>(dependencies.size());
renderpassCi.pDependencies = dependencies.data();
renderpassCi.subpassCount = 1;
renderpassCi.pSubpasses = &subpassDesc;
VkFramebufferCreateInfo framebufferCi = { };
framebufferCi.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferCi.height = kTextureSize;
framebufferCi.width = kTextureSize;
framebufferCi.attachmentCount = 1;
framebufferCi.layers = 1;
framebufferCi.pAttachments = &attachment;
m_pFrameBuffer->Finalize(framebufferCi, renderpassCi);
渲染天空盒并将其存储到立方体贴图中后,我使用以下渲染通道将天空采样到渲染场景中。此通道使用 VK_LOAD_OP_LOAD 以便在绘制天空盒时不会清除渲染场景:
// Create a renderpass for the pbr overlay.
Texture* pbrColor = gResources().GetRenderTexture(PBRColorAttachStr);
Texture* pbrNormal = gResources().GetRenderTexture(PBRNormalAttachStr);
Texture* pbrPosition = gResources().GetRenderTexture(PBRPositionAttachStr);
Texture* pbrRoughMetal = gResources().GetRenderTexture(PBRRoughMetalAttachStr);
Texture* pbrDepth = gResources().GetRenderTexture(PBRDepthAttachStr);
Texture* RTBright = gResources().GetRenderTexture(RenderTargetBrightStr);
std::array<VkAttachmentDescription, 6> attachmentDescriptions;
VkSubpassDependency dependenciesNative[2];
attachmentDescriptions[0] = CreateAttachmentDescription(
pbrColor->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrColor->Samples()
);
attachmentDescriptions[1] = CreateAttachmentDescription(
pbrNormal->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrNormal->Samples()
);
attachmentDescriptions[2] = CreateAttachmentDescription(
RTBright->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
RTBright->Samples()
);
attachmentDescriptions[3] = CreateAttachmentDescription(
pbrPosition->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrPosition->Samples()
);
attachmentDescriptions[4] = CreateAttachmentDescription(
pbrRoughMetal->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrRoughMetal->Samples()
);
attachmentDescriptions[5] = CreateAttachmentDescription(
pbrDepth->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrDepth->Samples()
);
dependenciesNative[0] = CreateSubPassDependency(
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
dependenciesNative[1] = CreateSubPassDependency(
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
std::array<VkAttachmentReference, 5> attachmentColors;
VkAttachmentReference attachmentDepthRef = { static_cast<u32>(attachmentColors.size()), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
attachmentColors[0].attachment = 0;
attachmentColors[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[1].attachment = 1;
attachmentColors[1].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[2].attachment = 2;
attachmentColors[2].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[3].attachment = 3;
attachmentColors[3].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[4].attachment = 4;
attachmentColors[4].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = static_cast<u32>(attachmentColors.size());
subpass.pColorAttachments = attachmentColors.data();
subpass.pDepthStencilAttachment = &attachmentDepthRef;
VkRenderPassCreateInfo renderpassCI = CreateRenderPassInfo(
static_cast<u32>(attachmentDescriptions.size()),
attachmentDescriptions.data(),
2,
dependenciesNative,
1,
&subpass
);
VkResult result =
vkCreateRenderPass(rhi->LogicDevice()->Native(), &renderpassCI, nullptr, &m_SkyboxRenderPass);
这是将天空渲染到场景中的命令缓冲区。我在渲染场景后提交此命令缓冲区以利用早期 z 拒绝:
if (m_pSkyboxCmdBuffer) {
m_pRhi->DeviceWaitIdle();
m_pSkyboxCmdBuffer->Reset(VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
}
VkCommandBufferBeginInfo beginInfo = { };
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
CommandBuffer* buf = m_pSkyboxCmdBuffer;
FrameBuffer* skyFrameBuffer = gResources().GetFrameBuffer(PBRFrameBufferStr);
GraphicsPipeline* skyPipeline = gResources().GetGraphicsPipeline(SkyboxPipelineStr);
DescriptorSet* global = m_pGlobal->Set();
DescriptorSet* skybox = gResources().GetDescriptorSet(SkyboxDescriptorSetStr);
VkDescriptorSet descriptorSets[] = {
global->Handle(),
skybox->Handle()
};
buf->Begin(beginInfo);
std::array<VkClearValue, 6> clearValues;
clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[1].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[2].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[3].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[4].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[5].depthStencil = { 1.0f, 0 };
VkViewport viewport = {};
viewport.height = (r32)m_pWindow->Height();
viewport.width = (r32)m_pWindow->Width();
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
viewport.y = 0.0f;
viewport.x = 0.0f;
VkRenderPassBeginInfo renderBegin = { };
renderBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderBegin.framebuffer = skyFrameBuffer->Handle();
renderBegin.renderPass = m_pSky->GetSkyboxRenderPass();
renderBegin.clearValueCount = static_cast<u32>(clearValues.size());
renderBegin.pClearValues = clearValues.data();
renderBegin.renderArea.offset = { 0, 0 };
renderBegin.renderArea.extent = m_pRhi->SwapchainObject()->SwapchainExtent();
// Start the renderpass.
buf->BeginRenderPass(renderBegin, VK_SUBPASS_CONTENTS_INLINE);
buf->SetViewPorts(0, 1, &viewport);
buf->BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline->Pipeline());
buf->BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline->Layout(), 0, 2, descriptorSets, 0, nullptr);
VertexBuffer* vertexbuffer = m_pSky->GetSkyboxVertexBuffer();
IndexBuffer* idxBuffer = m_pSky->GetSkyboxIndexBuffer();
VkDeviceSize offsets[] = { 0 };
VkBuffer vert = vertexbuffer->Handle()->NativeBuffer();
VkBuffer ind = idxBuffer->Handle()->NativeBuffer();
buf->BindVertexBuffers(0 , 1, &vert, offsets);
buf->BindIndexBuffer(ind, 0, VK_INDEX_TYPE_UINT32);
buf->DrawIndexed(idxBuffer->IndexCount(), 1, 0, 0, 0);
buf->EndRenderPass();
buf->End();
最后,我在我的渲染函数中提交它:
// TODO(): Need to clean this up.
VkCommandBuffer offscreenCmd = m_Offscreen._CmdBuffers[m_Offscreen._CurrCmdBufferIndex]->Handle();
VkCommandBuffer skyBuffers[] = { m_Offscreen._CmdBuffers[m_Offscreen._CurrCmdBufferIndex]->Handle(), m_pSky->CmdBuffer()->Handle() };
VkSemaphore skyWaits[] = { m_Offscreen._Semaphore->Handle(), m_pSky->SignalSemaphore()->Handle() };
VkSemaphore waitSemas[] = { m_pRhi->SwapchainObject()->ImageAvailableSemaphore() };
VkSemaphore signalSemas[] = { m_Offscreen._Semaphore->Handle() };
VkSemaphore shadowSignal[] = { m_Offscreen._ShadowSema->Handle() };
VkPipelineStageFlags waitFlags[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT };
VkSubmitInfo offscreenSI = {};
offscreenSI.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
offscreenSI.pCommandBuffers = &offscreenCmd;
offscreenSI.commandBufferCount = 1;
offscreenSI.signalSemaphoreCount = 1;
offscreenSI.pSignalSemaphores = signalSemas;
offscreenSI.waitSemaphoreCount = 1;
offscreenSI.pWaitSemaphores = waitSemas;
offscreenSI.pWaitDstStageMask = waitFlags;
VkSubmitInfo skyboxSI = offscreenSI;
VkSemaphore skyboxWaits[] = { m_Offscreen._Semaphore->Handle() };
VkSemaphore skyboxSignal[] = { m_SkyboxFinished->Handle() };
VkCommandBuffer skyboxCmd = m_pSkyboxCmdBuffer->Handle();
skyboxSI.commandBufferCount = 1;
skyboxSI.pCommandBuffers = &skyboxCmd;
skyboxSI.pSignalSemaphores = skyboxSignal;
skyboxSI.pWaitSemaphores = skyboxWaits;
VkSubmitInfo hdrSI = offscreenSI;
VkSemaphore hdrWaits[] = { m_SkyboxFinished->Handle() };
VkSemaphore hdrSignal[] = { m_HDR._Semaphore->Handle() };
VkCommandBuffer hdrCmd = m_HDR._CmdBuffers[m_HDR._CurrCmdBufferIndex]->Handle();
hdrSI.pCommandBuffers = &hdrCmd;
hdrSI.pSignalSemaphores = hdrSignal;
hdrSI.pWaitSemaphores = hdrWaits;
VkSemaphore waitSemaphores = m_HDR._Semaphore->Handle();
if (!m_HDR._Enabled) waitSemaphores = m_Offscreen._Semaphore->Handle();
// Update materials before rendering the frame.
UpdateMaterials();
// begin frame. This is where we start our render process per frame.
BeginFrame();
while (m_Offscreen._CmdBuffers[m_HDR._CurrCmdBufferIndex]->Recording() || !m_pRhi->CmdBuffersComplete()) {}
// Render shadow map here. Primary shadow map is our concern.
if (m_pLights->PrimaryShadowEnabled()) {
VkCommandBuffer shadowbuf[] = { m_Offscreen._ShadowCmdBuffers[m_Offscreen._CurrCmdBufferIndex]->Handle() };
VkSubmitInfo shadowSubmit = { };
shadowSubmit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
shadowSubmit.pCommandBuffers = shadowbuf;
shadowSubmit.commandBufferCount = 1;
shadowSubmit.signalSemaphoreCount = 1;
shadowSubmit.waitSemaphoreCount = 1;
shadowSubmit.pWaitSemaphores = waitSemas;
shadowSubmit.pSignalSemaphores = shadowSignal;
shadowSubmit.pWaitDstStageMask = waitFlags;
// Submit shadow rendering.
m_pRhi->GraphicsSubmit(shadowSubmit);
offscreenSI.pWaitSemaphores = shadowSignal;
}
// Check if sky needs to update it's cubemap.
if (m_pSky->NeedsRendering()) {
skyboxSI.waitSemaphoreCount = 2;
skyboxSI.pWaitSemaphores = skyWaits;
offscreenSI.commandBufferCount = 2;
offscreenSI.signalSemaphoreCount = 2;
offscreenSI.pSignalSemaphores = skyWaits;
offscreenSI.pCommandBuffers = skyBuffers;
m_pSky->MarkClean();
}
// Offscreen PBR Forward Rendering Pass.
m_pRhi->GraphicsSubmit(offscreenSI);
// Render Sky onto our render textures.
m_pRhi->GraphicsSubmit(skyboxSI);
// High Dynamic Range and Gamma Pass.
if (m_HDR._Enabled) m_pRhi->GraphicsSubmit(hdrSI);
// Before calling this cmd buffer, we want to submit our offscreen buffer first, then
// sent our signal to our swapchain cmd buffers.
// TODO(): We want to hold off on signalling GraphicsFinished Semaphore, and instead
// have it signal the SignalUI semaphore instead. UI Overlay will be the one to use
// GraphicsFinished Semaphore to signal end of frame rendering.
VkSemaphore signal = m_pRhi->GraphicsFinishedSemaphore();
VkSemaphore uiSig = m_pUI->Signal()->Handle();
m_pRhi->SubmitCurrSwapchainCmdBuffer(1, &waitSemaphores, 1, &signal);
// Render the Overlay.
RenderOverlay();
EndFrame();
在 Nvidia GTX 870M 上,结果似乎符合预期,
然而,使用 Intel HD Graphics 620,我得到了这张截图,不幸的是我不能在这里显示,因为它太大了:https://github.com/CheezBoiger/Recluse-Game/blob/master/Regression/Shaders/ForwardPass.png
似乎前几帧的场景没有清除到颜色附件上,就好像它正在渲染到一个单独的表面上并使用它来代替,但应该在渲染开始时每一帧都清除它...
删除 VK_LOAD_OP_LOAD 并替换为 VK_LOAD_OP_CLEAR,情况就清楚了,但是,只有天空盒被渲染了……我想知道我的渲染通道是否没有做它需要做的事情在 Intel 硬件上做,还是我要把天空盒绘制到我渲染的场景上完全错误?
非常感谢您的帮助。
* 更新 *
问题已解决,下面由@Ekzuzy 提供解决方案。
修复后英特尔硬件上的最终图像:
您始终为所有渲染通道和所有附件中的初始布局提供未定义的布局。从 UNDEFINED 布局到任何其他布局的布局转换不能保证保留图像内容。因此,如果您为加载操作创建具有 LOAD 值的渲染通道,则需要在渲染通道开始之前提供给定图像的实际布局。这也适用于其他布局转换(通过内存屏障)。
至于清除,一些图像应该在帧或渲染过程开始时清除。所以对他们来说,您可以将 UNDEFINED 保留为初始布局,但您应该将加载操作更改为清除。
至于为什么这适用于 Nvidia 而不适用于 Intel - 布局转换对 Nvidia 的硬件没有任何影响,但它们在 Intel 的平台(以及 AMD 的平台)上很重要。所以跳过(或设置不当的)布局转换,即使它违反规范,它仍然应该适用于 Nvidia。但不要仅仅因为它有效就这样做。这种做法是无效的。未来的平台,即使来自同一供应商,也可能表现不同。
关于 Vulkan 中的渲染通道,我似乎还有另一个问题。
绘制我的场景时,我首先提交命令缓冲区以使用大气散射将天空渲染到立方体贴图上,然后我将其用于前向传递以绘制出天空和太阳。
绘制天空盒并存储到立方体贴图中进行采样时使用的渲染通道:
m_pFrameBuffer = rhi->CreateFrameBuffer();
VkImageView attachment = m_RenderTexture->View();
VkAttachmentDescription attachDesc = CreateAttachmentDescription(
m_RenderTexture->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
m_RenderTexture->Samples()
);
VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
std::array<VkSubpassDependency, 2> dependencies;
dependencies[0] = CreateSubPassDependency(
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
dependencies[1] = CreateSubPassDependency(
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
VkSubpassDescription subpassDesc = { };
subpassDesc.colorAttachmentCount = 1;
subpassDesc.pColorAttachments = &colorRef;
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
VkRenderPassCreateInfo renderpassCi = { };
renderpassCi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderpassCi.attachmentCount = 1;
renderpassCi.pAttachments = &attachDesc;
renderpassCi.dependencyCount = static_cast<u32>(dependencies.size());
renderpassCi.pDependencies = dependencies.data();
renderpassCi.subpassCount = 1;
renderpassCi.pSubpasses = &subpassDesc;
VkFramebufferCreateInfo framebufferCi = { };
framebufferCi.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferCi.height = kTextureSize;
framebufferCi.width = kTextureSize;
framebufferCi.attachmentCount = 1;
framebufferCi.layers = 1;
framebufferCi.pAttachments = &attachment;
m_pFrameBuffer->Finalize(framebufferCi, renderpassCi);
渲染天空盒并将其存储到立方体贴图中后,我使用以下渲染通道将天空采样到渲染场景中。此通道使用 VK_LOAD_OP_LOAD 以便在绘制天空盒时不会清除渲染场景:
// Create a renderpass for the pbr overlay.
Texture* pbrColor = gResources().GetRenderTexture(PBRColorAttachStr);
Texture* pbrNormal = gResources().GetRenderTexture(PBRNormalAttachStr);
Texture* pbrPosition = gResources().GetRenderTexture(PBRPositionAttachStr);
Texture* pbrRoughMetal = gResources().GetRenderTexture(PBRRoughMetalAttachStr);
Texture* pbrDepth = gResources().GetRenderTexture(PBRDepthAttachStr);
Texture* RTBright = gResources().GetRenderTexture(RenderTargetBrightStr);
std::array<VkAttachmentDescription, 6> attachmentDescriptions;
VkSubpassDependency dependenciesNative[2];
attachmentDescriptions[0] = CreateAttachmentDescription(
pbrColor->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrColor->Samples()
);
attachmentDescriptions[1] = CreateAttachmentDescription(
pbrNormal->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrNormal->Samples()
);
attachmentDescriptions[2] = CreateAttachmentDescription(
RTBright->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
RTBright->Samples()
);
attachmentDescriptions[3] = CreateAttachmentDescription(
pbrPosition->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrPosition->Samples()
);
attachmentDescriptions[4] = CreateAttachmentDescription(
pbrRoughMetal->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrRoughMetal->Samples()
);
attachmentDescriptions[5] = CreateAttachmentDescription(
pbrDepth->Format(),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
pbrDepth->Samples()
);
dependenciesNative[0] = CreateSubPassDependency(
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
dependenciesNative[1] = CreateSubPassDependency(
0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_SUBPASS_EXTERNAL,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_DEPENDENCY_BY_REGION_BIT
);
std::array<VkAttachmentReference, 5> attachmentColors;
VkAttachmentReference attachmentDepthRef = { static_cast<u32>(attachmentColors.size()), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
attachmentColors[0].attachment = 0;
attachmentColors[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[1].attachment = 1;
attachmentColors[1].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[2].attachment = 2;
attachmentColors[2].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[3].attachment = 3;
attachmentColors[3].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachmentColors[4].attachment = 4;
attachmentColors[4].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = static_cast<u32>(attachmentColors.size());
subpass.pColorAttachments = attachmentColors.data();
subpass.pDepthStencilAttachment = &attachmentDepthRef;
VkRenderPassCreateInfo renderpassCI = CreateRenderPassInfo(
static_cast<u32>(attachmentDescriptions.size()),
attachmentDescriptions.data(),
2,
dependenciesNative,
1,
&subpass
);
VkResult result =
vkCreateRenderPass(rhi->LogicDevice()->Native(), &renderpassCI, nullptr, &m_SkyboxRenderPass);
这是将天空渲染到场景中的命令缓冲区。我在渲染场景后提交此命令缓冲区以利用早期 z 拒绝:
if (m_pSkyboxCmdBuffer) {
m_pRhi->DeviceWaitIdle();
m_pSkyboxCmdBuffer->Reset(VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
}
VkCommandBufferBeginInfo beginInfo = { };
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
CommandBuffer* buf = m_pSkyboxCmdBuffer;
FrameBuffer* skyFrameBuffer = gResources().GetFrameBuffer(PBRFrameBufferStr);
GraphicsPipeline* skyPipeline = gResources().GetGraphicsPipeline(SkyboxPipelineStr);
DescriptorSet* global = m_pGlobal->Set();
DescriptorSet* skybox = gResources().GetDescriptorSet(SkyboxDescriptorSetStr);
VkDescriptorSet descriptorSets[] = {
global->Handle(),
skybox->Handle()
};
buf->Begin(beginInfo);
std::array<VkClearValue, 6> clearValues;
clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[1].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[2].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[3].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[4].color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValues[5].depthStencil = { 1.0f, 0 };
VkViewport viewport = {};
viewport.height = (r32)m_pWindow->Height();
viewport.width = (r32)m_pWindow->Width();
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
viewport.y = 0.0f;
viewport.x = 0.0f;
VkRenderPassBeginInfo renderBegin = { };
renderBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderBegin.framebuffer = skyFrameBuffer->Handle();
renderBegin.renderPass = m_pSky->GetSkyboxRenderPass();
renderBegin.clearValueCount = static_cast<u32>(clearValues.size());
renderBegin.pClearValues = clearValues.data();
renderBegin.renderArea.offset = { 0, 0 };
renderBegin.renderArea.extent = m_pRhi->SwapchainObject()->SwapchainExtent();
// Start the renderpass.
buf->BeginRenderPass(renderBegin, VK_SUBPASS_CONTENTS_INLINE);
buf->SetViewPorts(0, 1, &viewport);
buf->BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline->Pipeline());
buf->BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, skyPipeline->Layout(), 0, 2, descriptorSets, 0, nullptr);
VertexBuffer* vertexbuffer = m_pSky->GetSkyboxVertexBuffer();
IndexBuffer* idxBuffer = m_pSky->GetSkyboxIndexBuffer();
VkDeviceSize offsets[] = { 0 };
VkBuffer vert = vertexbuffer->Handle()->NativeBuffer();
VkBuffer ind = idxBuffer->Handle()->NativeBuffer();
buf->BindVertexBuffers(0 , 1, &vert, offsets);
buf->BindIndexBuffer(ind, 0, VK_INDEX_TYPE_UINT32);
buf->DrawIndexed(idxBuffer->IndexCount(), 1, 0, 0, 0);
buf->EndRenderPass();
buf->End();
最后,我在我的渲染函数中提交它:
// TODO(): Need to clean this up.
VkCommandBuffer offscreenCmd = m_Offscreen._CmdBuffers[m_Offscreen._CurrCmdBufferIndex]->Handle();
VkCommandBuffer skyBuffers[] = { m_Offscreen._CmdBuffers[m_Offscreen._CurrCmdBufferIndex]->Handle(), m_pSky->CmdBuffer()->Handle() };
VkSemaphore skyWaits[] = { m_Offscreen._Semaphore->Handle(), m_pSky->SignalSemaphore()->Handle() };
VkSemaphore waitSemas[] = { m_pRhi->SwapchainObject()->ImageAvailableSemaphore() };
VkSemaphore signalSemas[] = { m_Offscreen._Semaphore->Handle() };
VkSemaphore shadowSignal[] = { m_Offscreen._ShadowSema->Handle() };
VkPipelineStageFlags waitFlags[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT };
VkSubmitInfo offscreenSI = {};
offscreenSI.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
offscreenSI.pCommandBuffers = &offscreenCmd;
offscreenSI.commandBufferCount = 1;
offscreenSI.signalSemaphoreCount = 1;
offscreenSI.pSignalSemaphores = signalSemas;
offscreenSI.waitSemaphoreCount = 1;
offscreenSI.pWaitSemaphores = waitSemas;
offscreenSI.pWaitDstStageMask = waitFlags;
VkSubmitInfo skyboxSI = offscreenSI;
VkSemaphore skyboxWaits[] = { m_Offscreen._Semaphore->Handle() };
VkSemaphore skyboxSignal[] = { m_SkyboxFinished->Handle() };
VkCommandBuffer skyboxCmd = m_pSkyboxCmdBuffer->Handle();
skyboxSI.commandBufferCount = 1;
skyboxSI.pCommandBuffers = &skyboxCmd;
skyboxSI.pSignalSemaphores = skyboxSignal;
skyboxSI.pWaitSemaphores = skyboxWaits;
VkSubmitInfo hdrSI = offscreenSI;
VkSemaphore hdrWaits[] = { m_SkyboxFinished->Handle() };
VkSemaphore hdrSignal[] = { m_HDR._Semaphore->Handle() };
VkCommandBuffer hdrCmd = m_HDR._CmdBuffers[m_HDR._CurrCmdBufferIndex]->Handle();
hdrSI.pCommandBuffers = &hdrCmd;
hdrSI.pSignalSemaphores = hdrSignal;
hdrSI.pWaitSemaphores = hdrWaits;
VkSemaphore waitSemaphores = m_HDR._Semaphore->Handle();
if (!m_HDR._Enabled) waitSemaphores = m_Offscreen._Semaphore->Handle();
// Update materials before rendering the frame.
UpdateMaterials();
// begin frame. This is where we start our render process per frame.
BeginFrame();
while (m_Offscreen._CmdBuffers[m_HDR._CurrCmdBufferIndex]->Recording() || !m_pRhi->CmdBuffersComplete()) {}
// Render shadow map here. Primary shadow map is our concern.
if (m_pLights->PrimaryShadowEnabled()) {
VkCommandBuffer shadowbuf[] = { m_Offscreen._ShadowCmdBuffers[m_Offscreen._CurrCmdBufferIndex]->Handle() };
VkSubmitInfo shadowSubmit = { };
shadowSubmit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
shadowSubmit.pCommandBuffers = shadowbuf;
shadowSubmit.commandBufferCount = 1;
shadowSubmit.signalSemaphoreCount = 1;
shadowSubmit.waitSemaphoreCount = 1;
shadowSubmit.pWaitSemaphores = waitSemas;
shadowSubmit.pSignalSemaphores = shadowSignal;
shadowSubmit.pWaitDstStageMask = waitFlags;
// Submit shadow rendering.
m_pRhi->GraphicsSubmit(shadowSubmit);
offscreenSI.pWaitSemaphores = shadowSignal;
}
// Check if sky needs to update it's cubemap.
if (m_pSky->NeedsRendering()) {
skyboxSI.waitSemaphoreCount = 2;
skyboxSI.pWaitSemaphores = skyWaits;
offscreenSI.commandBufferCount = 2;
offscreenSI.signalSemaphoreCount = 2;
offscreenSI.pSignalSemaphores = skyWaits;
offscreenSI.pCommandBuffers = skyBuffers;
m_pSky->MarkClean();
}
// Offscreen PBR Forward Rendering Pass.
m_pRhi->GraphicsSubmit(offscreenSI);
// Render Sky onto our render textures.
m_pRhi->GraphicsSubmit(skyboxSI);
// High Dynamic Range and Gamma Pass.
if (m_HDR._Enabled) m_pRhi->GraphicsSubmit(hdrSI);
// Before calling this cmd buffer, we want to submit our offscreen buffer first, then
// sent our signal to our swapchain cmd buffers.
// TODO(): We want to hold off on signalling GraphicsFinished Semaphore, and instead
// have it signal the SignalUI semaphore instead. UI Overlay will be the one to use
// GraphicsFinished Semaphore to signal end of frame rendering.
VkSemaphore signal = m_pRhi->GraphicsFinishedSemaphore();
VkSemaphore uiSig = m_pUI->Signal()->Handle();
m_pRhi->SubmitCurrSwapchainCmdBuffer(1, &waitSemaphores, 1, &signal);
// Render the Overlay.
RenderOverlay();
EndFrame();
在 Nvidia GTX 870M 上,结果似乎符合预期,
然而,使用 Intel HD Graphics 620,我得到了这张截图,不幸的是我不能在这里显示,因为它太大了:https://github.com/CheezBoiger/Recluse-Game/blob/master/Regression/Shaders/ForwardPass.png
似乎前几帧的场景没有清除到颜色附件上,就好像它正在渲染到一个单独的表面上并使用它来代替,但应该在渲染开始时每一帧都清除它...
删除 VK_LOAD_OP_LOAD 并替换为 VK_LOAD_OP_CLEAR,情况就清楚了,但是,只有天空盒被渲染了……我想知道我的渲染通道是否没有做它需要做的事情在 Intel 硬件上做,还是我要把天空盒绘制到我渲染的场景上完全错误?
非常感谢您的帮助。
* 更新 * 问题已解决,下面由@Ekzuzy 提供解决方案。
修复后英特尔硬件上的最终图像:
您始终为所有渲染通道和所有附件中的初始布局提供未定义的布局。从 UNDEFINED 布局到任何其他布局的布局转换不能保证保留图像内容。因此,如果您为加载操作创建具有 LOAD 值的渲染通道,则需要在渲染通道开始之前提供给定图像的实际布局。这也适用于其他布局转换(通过内存屏障)。
至于清除,一些图像应该在帧或渲染过程开始时清除。所以对他们来说,您可以将 UNDEFINED 保留为初始布局,但您应该将加载操作更改为清除。
至于为什么这适用于 Nvidia 而不适用于 Intel - 布局转换对 Nvidia 的硬件没有任何影响,但它们在 Intel 的平台(以及 AMD 的平台)上很重要。所以跳过(或设置不当的)布局转换,即使它违反规范,它仍然应该适用于 Nvidia。但不要仅仅因为它有效就这样做。这种做法是无效的。未来的平台,即使来自同一供应商,也可能表现不同。