英文:
GLFW/Vulkan Transparent Framebuffer does not respect swapchain source image alpha channel?
问题
我有一个启用了透明度的GLFW窗口,使用了VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
的Vulkan交换链,以及使用普通混合 (Src * One) + (Dst * One_Minus_Src)
的图形管线。
这可以分为三个部分:窗口/交换链/图形管线的创建。有关更多信息,请参阅项目的GitHub链接:https://github.com/Yaazarai/TinyVulkan-Dynamic
问题是:透明的帧缓冲区工作正常,但它不尊重Alpha通道,因为唯一支持的交换链复合Alpha模式是 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
,这使得帧缓冲区在合成多个窗口表面时始终具有Alpha分量为1。
我已经检查了每个复合模式,并且当未使用 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
时,Vulkan总是报错。更改混合模式也不起作用,因为帧缓冲区的Alpha分量始终为1。
Validation Layer: Validation Error: [ VUID-VkSwapchainCreateInfoKHR-compositeAlpha-01280 ] Object 0: handle = 0x2655f873250, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xb0051a12 | vkCreateSwapchainKHR() called with a non-supported pCreateInfo->compositeAlpha (i.e. VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR). Supported values are:
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
. The Vulkan spec states: compositeAlpha must be one of the bits present in the supportedCompositeAlpha member of the VkSurfaceCapabilitiesKHR structure returned by vkGetPhysicalDeviceSurfaceCapabilitiesKHR for the surface
// 使用传递给VkPhysicalDevice的GLFW Vulkan窗口表面,根据支持的Surface Capabilities创建Vulkan交换链。
void CreateSwapChainImages(uint32_t width = 0, uint32_t height = 0) {
// 在此处放置创建交换链图像的代码
}
// 创建具有适当Normal Blend模式的图形管线
void CreateGraphicsPipeline() {
// 在此处放置创建图形管线的代码
}
// 创建启用透明帧缓冲区的GLFW窗口
TinyVkWindow(std::string title, int width, int height, bool resizable, bool transparentFramebuffer = false, bool hasMinSize = false, int minWidth = 200, int minHeight = 200) {
// 在此处放置创建GLFW窗口的代码
}
// 关于透明帧缓冲区的问题,可以看到它工作正常,VkClearColor也有效,但如上所述,忽略了Alpha分量。
希望这些信息对你有所帮助。如果你需要进一步的帮助或有其他问题,请随时提出。
英文:
I have a transparent enabled GLFW window, Vulkan Swapchain with VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
and a Graphics Pipeline using Normal Blend (Src * One) + (Dst * One_Minus_Src)
.
This is broken up into three parts, window/swapchain/graphicspipeline creation. See
also the Github for the project for more information: https://github.com/Yaazarai/TinyVulkan-Dynamic
The problem is this: The transparent framebuffer works, but it doesn't respect the alpha channel because the only supported swapchain composite alpha mode is VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
which makes the framebuffer always have an alpha component of one when compositing multiple window surfaces.
I've gone through checked each composite mode and Vulkan always errors when VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
isn't used. Changing the blend mode doesn't work either because well the framebuffer alpha component is always 1.
Validation Layer: Validation Error: [ VUID-VkSwapchainCreateInfoKHR-compositeAlpha-01280 ] Object 0: handle = 0x2655f873250, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xb0051a12 | vkCreateSwapchainKHR() called with a non-supported pCreateInfo->compositeAlpha (i.e. VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR). Supported values are:
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
. The Vulkan spec states: compositeAlpha must be one of the bits present in the supportedCompositeAlpha member of the VkSurfaceCapabilitiesKHR structure returned by vkGetPhysicalDeviceSurfaceCapabilitiesKHR for the surface
// Creates a Vulkan Swapchain with the supported Surface Capabilities based
// on the GLFW Vulkan Window surface passed to the VkPhysicalDevice.
void CreateSwapChainImages(uint32_t width = 0, uint32_t height = 0) {
TinyVkSwapChainSupporter swapChainSupport = QuerySwapChainSupport(renderDevice.physicalDevice);
VkSurfaceFormatKHR surfaceFormat = QuerySwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = QuerySwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = QuerySwapExtent(swapChainSupport.capabilities);
uint32_t imageCount = std::min(swapChainSupport.capabilities.maxImageCount, std::max(swapChainSupport.capabilities.minImageCount, static_cast<uint32_t>(bufferingMode)));
if (width != 0 && height != 0) {
extent = {
std::min(std::max((uint32_t)width, swapChainSupport.capabilities.minImageExtent.width), swapChainSupport.capabilities.maxImageExtent.width),
std::min(std::max((uint32_t)height, swapChainSupport.capabilities.minImageExtent.height), swapChainSupport.capabilities.maxImageExtent.height)
};
} else {
extent = QuerySwapExtent(swapChainSupport.capabilities);
}
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount)
imageCount = swapChainSupport.capabilities.maxImageCount;
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = renderDevice.presentationSurface;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1; // Change when developing VR or other 3D stereoscopic applications
createInfo.imageUsage = imageUsage;
TinyVkQueueFamily indices = TinyVkQueueFamily::FindQueueFamilies(renderDevice.physicalDevice, renderDevice.presentationSurface);
uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
if (indices.graphicsFamily != indices.presentFamily) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0; // Optional
createInfo.pQueueFamilyIndices = nullptr; // Optional
}
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = swapChain;
if (vkCreateSwapchainKHR(renderDevice.logicalDevice, &createInfo, nullptr, &swapChain) != VK_SUCCESS)
throw std::runtime_error("TinyVulkan: Failed to create swap chain!");
vkGetSwapchainImagesKHR(renderDevice.logicalDevice, swapChain, &imageCount, nullptr);
images.resize(imageCount);
vkGetSwapchainImagesKHR(renderDevice.logicalDevice, swapChain, &imageCount, images.data());
imageFormat = surfaceFormat.format;
imageExtent = extent;
}
// Creates the graphics pipeline with the appropriate Normal Blend mod
void CreateGraphicsPipeline() {
///////////////////////////////////////////////////////////////////////////////////////////////////////
/////////// This section specifies that MvkVertex provides the vertex layout description ///////////
const VkVertexInputBindingDescription bindingDescription = vertexDescription.binding;
const std::vector<VkVertexInputAttributeDescription>& attributeDescriptions = vertexDescription.attributes;
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.pushConstantRangeCount = 0;
uint32_t pushConstantRangeCount = static_cast<uint32_t>(pushConstantRanges.size());
if (pushConstantRangeCount > 0) {
pipelineLayoutInfo.pushConstantRangeCount = pushConstantRangeCount;
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
}
if (descriptorBindings.size() > 0) {
VkDescriptorSetLayoutCreateInfo descriptorCreateInfo{};
descriptorCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorCreateInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
descriptorCreateInfo.bindingCount = static_cast<uint32_t>(descriptorBindings.size());
descriptorCreateInfo.pBindings = descriptorBindings.data();
if (vkCreateDescriptorSetLayout(renderDevice.logicalDevice, &descriptorCreateInfo, nullptr, &descriptorLayout) != VK_SUCCESS)
throw std::runtime_error("TinyVulkan: Failed to create push descriptor bindings! ");
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorLayout;
}
if (vkCreatePipelineLayout(renderDevice.logicalDevice, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
throw std::runtime_error("TinyVulkan: Failed to create graphics pipeline layout!");
///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = vertexTopology;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
viewportState.flags = 0;
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = polgyonTopology;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY;
colorBlending.attachmentCount = 1;
VkPipelineColorBlendAttachmentState blendDescription = colorBlendState;
colorBlending.pAttachments = &blendDescription;
colorBlending.blendConstants[0] = 0.0f;
colorBlending.blendConstants[1] = 0.0f;
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;
const std::array<VkDynamicState, 2> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.flags = 0;
dynamicState.pDynamicStates = dynamicStateEnables.data();
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStateEnables.size());
dynamicState.pNext = nullptr;
VkPipelineRenderingCreateInfoKHR renderingCreateInfo{};
renderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
renderingCreateInfo.colorAttachmentCount = 1;
renderingCreateInfo.pColorAttachmentFormats = &imageFormat;
renderingCreateInfo.depthAttachmentFormat = QueryDepthFormat();
VkPipelineDepthStencilStateCreateInfo depthStencilInfo{};
depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencilInfo.depthTestEnable = enableDepthTesting;
depthStencilInfo.depthWriteEnable = enableDepthTesting;
depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
depthStencilInfo.depthBoundsTestEnable = VK_FALSE;
depthStencilInfo.minDepthBounds = 0.0f; // Optional
depthStencilInfo.maxDepthBounds = 1.0f; // Optional
depthStencilInfo.stencilTestEnable = VK_FALSE;
depthStencilInfo.front = {}; // Optional
depthStencilInfo.back = {}; // Optional
///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.shaderCreateInfo.size());
pipelineInfo.pStages = shaderStages.shaderCreateInfo.data();
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDepthStencilState = &depthStencilInfo;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.pNext = &renderingCreateInfo;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = nullptr;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
if (vkCreateGraphicsPipelines(renderDevice.logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS)
throw std::runtime_error("TinyVulkan: Failed to create graphics pipeline!");
}
// Creates the GLFW transparent framebuffer enabled window.
TinyVkWindow(std::string title, int width, int height, bool resizable, bool transparentFramebuffer = false, bool hasMinSize = false, int minWidth = 200, int minHeight = 200) {
onDispose.hook(callback<bool>([this](bool forceDispose){this->Disposable(forceDispose); }));
onWindowResized.hook(callback<GLFWwindow*, int, int>([this](GLFWwindow* hwnd, int width, int height) { if (hwnd != hwndWindow) return; hwndWidth = width; hwndHeight = height; }));
onWindowPositionMoved.hook(callback<GLFWwindow*, int, int>([this](GLFWwindow* hwnd, int xpos, int ypos) { if (hwnd != hwndWindow) return; hwndXpos = xpos; hwndYpos = ypos; }));
hwndWindow = InitiateWindow(title, width, height, resizable, transparentFramebuffer);
glfwSetWindowUserPointer(hwndWindow, this);
glfwSetFramebufferSizeCallback(hwndWindow, TinyVkWindow::OnFrameBufferNotifyReSizeCallback);
glfwSetWindowPosCallback(hwndWindow, TinyVkWindow::OnWindowPositionCallback);
if (hasMinSize) glfwSetWindowSizeLimits(hwndWindow, minWidth, minHeight, GLFW_DONT_CARE, GLFW_DONT_CARE);
InitGLFWInput();
}
So here's the weird bit, the transparent framebuffer works and so does the VkClearColor for the framebuffer to clear the window surface, but as stated the alpha component is ignored. Which means that the alpha component during surface compositing is determined by the value of the RGB components of the clear color, as seen below, given a clear color of dark purple (RGBA) 0.1, 0.0, 0.1, 0.0
. The correct result should be clear with transparency and black with without due to a 0.0 alpha which isn't respected, so we get purple instead.
答案1
得分: 0
I understand. Here's the translated text without the code:
好的,我不完全理解预乘 alpha 颜色数学,但一切都按预期工作(感谢 Discord 图形编程)。问题在于,使用预乘 alpha 时,零 alpha 分量不是有效的颜色,因此在使用 SwapChain 渲染时,颜色合成器的行为是意外的,如之前所述,这是因为我不能在 SwapChain 上使用 VK_COMPOSITE_PREMULITPLIED_ALPHA
。因此,解决方案是使用渲染到纹理模型进行渲染,然后将其输出到 SwapChain 以获得所期望的结果。
但是,如果您需要明确的透明窗口(例如 GIF 录制应用程序等),那么预乘 alpha 实际上是可取的,因为现在您的背景清除颜色(如下面的图像 2 中所示)已与合成的清除背景进行了预乘。而在使用渲染到纹理然后渲染到 SwapChain(直接下面的图像 1)时,您会注意到输出图像较暗,因为它已与图像渲染器预乘为黑色。
这个答案来自于图形编程/Vulkan Discord 社区。
参考链接:https://ciechanow.ski/alpha-compositing/
英文:
Okay so I don't fully understand pre-multiplied alpha color math, but everything is actually working as expected (thanks Discord Graphics Programming). The problem is that with pre-multiplied alpha a zero-alpha component is NOT a valid color so the behavior is unexpected by the color compositer when rendering with the SwapChain--which as stated prior is because I cannot use VK_COMPOSITE_PREMULITPLIED_ALPHA
on the SwapChain. The solution then is to use a render-to-texture model for rendering then output that to the SwapChain for the desired result.
However, if you want explicitly transparent windows (Idk for GIF recording apps or something), then premultiplied alpha is actually desirable because now your background clear color (as seen in image 2 below is premultiplied with the compositing clear background. Which is not the case when using render-to-texture then render-to-swapchain (image 1 directly below)--you'll notice the output image is darker because it was premultiplied with black with an image renderer.
This answer is courtesy of the Graphics Programming/Vulkan Discord community.
Reference: https://ciechanow.ski/alpha-compositing/
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论