Skybox 渲染的 VkRenderPass 加载操作问题

Posted

技术标签:

【中文标题】Skybox 渲染的 VkRenderPass 加载操作问题【英文标题】:Issue with VkRenderPass load operation for Skybox rendering 【发布时间】:2018-07-17 04:49:35 【问题描述】:

关于 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,情况就解决了,但是,只有天空盒被渲染......我想知道我的渲染通道是否没有做它需要在英特尔硬件上做的事情,或者我正在做将天空盒绘制到我渲染的场景上都错了吗?

非常感谢您的帮助。

* 更新 * 问题已修复,@Ekzuzy 提供的解决方案如下。

修复后英特尔硬件上的最终图像:

【问题讨论】:

英特尔结果的链接对其他人无效。 我仍然不清楚你为什么使用 LOAD_OP_LOAD。看起来你是在绘制 to 天空盒立方体贴图的渲染通道中这样做的?为什么要重用/修改以前的天空盒而不是清除立方体贴图以便绘制新的天空盒? @JesseHall 哎呀,我不是很清楚那部分。使用 LOAD_OP_LOAD 的渲染通道用于保存绘制场景的帧缓冲区,以便在顶部绘制(已经渲染的)天空盒。每当太阳方向发生变化时,我都会使用另一个渲染通道在天空盒上进行绘制。当天空盒需要渲染时,它会按照你说的去做,它会在场景中使用之前清除并绘制新的天空盒。由于某种原因,我没有将该渲染通道添加到帖子中,我会更新。 我也会更新不起作用的图像。 【参考方案1】:

您始终为您的所有渲染通道和所有附件中的初始布局提供未定义的布局。从 UNDEFINED 布局到任何其他布局的布局转换不保证保留图像内容。因此,如果您为加载操作创建具有 LOAD 值的渲染通道,则需要在渲染通道开始之前提供给定图像的实际布局。这也适用于其他布局转换(通过内存屏障)。

至于清除,一些图像应该在一帧或渲染通道的开头被清除。所以对他们来说,您可以将 UNDEFINED 保留为初始布局,但您应该将加载操作更改为清除。

至于为什么这适用于 Nvidia 而不适用于 Intel - 布局转换对 Nvidia 的硬件没有任何影响,但它们对 Intel 的平台(以及 AMD 的平台)很重要。所以跳过(或设置不正确的)布局转换,即使它违反了规范,它仍然应该在 Nvidia 上工作。但不要仅仅因为它有效就这样做。这种做法是无效的。未来的平台,即使来自同一供应商,也可能表现不同。

【讨论】:

太棒了,我完全没有意识到这一点,因为我认为图像会被保留,好像 VK_IMAGE_LAYOUT_UNDEFINED 应该用于与旧图像布局有关的任何东西。也许这只是 Nvidia 的魔力让我相信它“有效”。这绝对解决了问题。我还认为我真的需要更仔细地阅读 vulkan 规范。无论如何,非常感谢@Ekzuzy!我将使用修复后生成的图像更新这篇文章 @wubw 规范还不错,但是关于一个主题的许多信息都放在规范的各个部分。教程虽然没有那么透彻和完整,但更加简洁易懂。 是的,确实,我认为我从教程中学到了很多关于 vulkan 的知识,特别是从 Alexander Overvoorde、Sasha Willems、在工作中,甚至从您的教程中(Vulkan 食谱,没有秘密的 API)!当然,偶尔阅读一下规范不会有什么坏处,但我确实很欣赏你们为使 Vulkan 的学习不那么痛苦而付出的努力,以及更多有趣的 API 使用 :) @wubw 哇,感谢您阅读我的书!我一直希望它对人们有用,而且随着越来越多的人阅读它,它确实可能有用,我仍然感到非常震惊! :-) 如果您有任何其他问题,请询问 ;-)。

以上是关于Skybox 渲染的 VkRenderPass 加载操作问题的主要内容,如果未能解决你的问题,请参考以下文章

Unity3D中Lens Flare 如何添加在skybox中的太阳旁边?

一步步学OpenGL 25-《Skybox天空盒子》

XNA 3D Skybox模拟问题,包括相机位置和模型转换

OGER SDK研究之四 Demo_SkyBox 天空盒

使用 Cardboard 在 Unity 3D 中带有 alpha 的 Skybox

OpenGL Skybox 可见边框