1. 程式人生 > >Vulkan Tutorial 12 Fixed functions

Vulkan Tutorial 12 Fixed functions

spi set vid art 配置 logs 傳遞 對象 項目

操作系統:Windows8.1

顯卡:Nivida GTX965M

開發工具:Visual Studio 2017


早起的圖形API在圖形渲染管線的許多階段提供了默認的狀態。在Vulkan中,從viewport的大小到混色函數,需要凡事做到親歷親為。在本章節中我們會填充有關固有功能操作的所有結構體。

Vertex input


VkPipelineVertexInputStateCreateInfo結構體描述了頂點數據的格式,該結構體數據傳遞到vertex shader中。它以兩種方式進行描述:

  • Bindings:根據數據的間隙,確定數據是每個頂點或者是每個instance(instancing
    )
  • Attribute 描述:描述將要進行綁定及加載屬性的頂點著色器中的相關屬性類型。

因為我們將頂點數據硬編碼到vertex shader中,所以我們將要填充的結構體沒有頂點數據去加載。我們將會在vertex buffer章節中回來操作。

VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount 
= 0; vertexInputInfo.pVertexBindingDescriptions = nullptr; // Optional vertexInputInfo.vertexAttributeDescriptionCount = 0; vertexInputInfo.pVertexAttributeDescriptions = nullptr; // Optional

pVertexBindingDescriptionspVertexAttributeDescriptions成員指向結構體數組,用於進一步描述加載的頂點數據信息。在createGraphicsPipeline函數中的shaderStages

數組後添加該結構體。

Input assembly


VkPipelineInputAssemblyStateCreateInfo結構體描述兩件事情:頂點數據以什麽類型的幾何圖元拓撲進行繪制及是否啟用頂點索重新開始圖元。圖元的拓撲結構類型topology枚舉值如下:

  • VK_PRIMITIVE_TOPOLOGY_POINT_LIST:頂點到點
  • VK_PRIMITIVE_TOPOLOGY_LINE_LIST:兩點成線,頂點不共用
  • VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:兩點成線,次索引的頂點作為後一個頂點的開始頂點,即頂點共用
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: 三點成面,頂點不共用
  • VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:三點成面,每第三個頂點作為下一個三角形的首個頂點,即頂點共用

正常情況下,頂點數據按照緩沖區中的序列作為索引,但是也可以通過element buffer緩沖區自行指定頂點數據的索引。通過復用頂點數據提升性能。如果設置primitiveRestartEnable成員為VK_TRUE,可以通過0xFFFF或者0xFFFFFFFF作為特殊索引來分解線和三角形在_STRIP模式下的圖元拓撲結構。

通過本教程繪制三角形,所以我們堅持按照如下格式填充數據結構:

VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;

Viewports and scissors


viewport用於描述framebuffer作為渲染輸出結果目標區域。它的數值在本教程中總是設置在(0, 0)(width, height)

VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) swapChainExtent.width;
viewport.height = (float) swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;

記得交換鏈和它的images圖像大小WIDTHHEIGHT會根據不同的窗體而不同。交換鏈圖像將會在幀緩沖區framebuffers使用,所以我們應該堅持它們的大小。

minDepthmaxDepth數值指定framebuffer中深度的範圍。這些數值必須收斂在[0.0f, 1.0f]區間沖,但是minDepth可能會大於maxDepth。如果你不做任何指定,建議使用標準的數值0.0f和1.0f

viewports定義了image圖像到framebuffer幀緩沖區的轉換關系,裁剪矩形定義了哪些區域的像素被存儲。任何在裁剪巨型外的像素都會在光柵化階段丟棄。它們的功能更像過濾器而不是定義轉換關系。這個區別如下圖所示。需要註意的是,對於圖像比viewport尺寸大的情形,左側的裁剪矩形只是眾多可能的一個表現。

技術分享

在本教程中我們需要將圖像繪制到完整的幀緩沖區framebuffer中,所以我們定義裁剪矩形覆蓋到整體圖像:

VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;

viewport和裁剪矩形需要借助VkPipelineViewportStateCreateInfo結構體聯合使用。可以使用多viewports和裁剪矩形在一些圖形卡,通過數組引用。使用該特性需要GPU支持該功能,具體看邏輯設備的創建。

VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor; 

Rasterizer


光柵化通過頂點著色器及具體的幾何算法將頂點進行塑形,並將圖形傳遞到片段著色器進行著色工作。它也會執行深度測試depth testing、面裁切face culling和裁剪測試,它可以對輸出的片元進行配置,決定是否輸出整個圖元拓撲或者是邊框(線框渲染)。所有的配置通過VkPipelineRasterizationStateCreateInfo結構體定義。

VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;

它的depthClampEnable設置為VK_TRUE,超過遠近裁剪面的片元會進行收斂,而不是丟棄它們。它在特殊的情況下比較有用,像陰影貼圖。使用該功能需要得到GPU的支持。

rasterizer.rasterizerDiscardEnable = VK_FALSE;

如果rasterizerDiscardEnable設置為VK_TRUE,那麽幾何圖元永遠不會傳遞到光柵化階段。這是基本的禁止任何輸出到framebuffer幀緩沖區的方法。

rasterizer.polygonMode = VK_POLYGON_MODE_FILL;

polygonMode決定幾何產生圖片的內容。下列有效模式:

  • VK_POLYGON_MODE_FILL: 多邊形區域填充
  • VK_POLYGON_MODE_LINE: 多邊形邊緣線框繪制
  • VK_POLYGON_MODE_POINT: 多邊形頂點作為描點繪制

使用任何模式填充需要開啟GPU功能。

rasterizer.lineWidth = 1.0f;

lineWidth成員是直接填充的,根據片元的數量描述線的寬度。最大的線寬支持取決於硬件,任何大於1.0的線寬需要開啟GPU的wideLines特性支持。

rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;

cullMode變量用於決定面裁剪的類型方式。可以禁止culling,裁剪front faces,cull back faces 或者全部。frontFace用於描述作為front-facing面的頂點的順序,可以是順時針也可以是逆時針。

rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional

光柵化可以通過添加常量或者基於片元的斜率來更改深度值。一些時候對於陰影貼圖是有用的,但是我們不會在章節中使用,設置depthBiasEnableVK_FALSE

Multisampling


VkPipelineMultisampleStateCreateInfo結構體用於配置多重采樣。所謂多重采樣是抗鋸齒anti-aliasing的一種實現。它通過組合多個多邊形的片段著色器結果,光柵化到同一個像素。這主要方法在邊緣,這也是最醒目的鋸齒發生的地方。如果只有一個多邊形映射到像素是不需要多次運行片段著色器進行采樣的,相比高分辨率來說,它會花費較低的開銷。開啟該功能需要GPU支持。

VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional

在本教程中我們不會使用多重采樣,但是可以隨意的嘗試,具體的參數請參閱規範。

Depth and stencil testing


如果使用depth 或者 stencil緩沖區,需要使用VkPipelineDepthStencilStateCreateInfo配置。我們現在不需要使用,所以簡單的傳遞nullptr,關於這部分會專門在深度緩沖區章節中討論。

Color blending


片段著色器輸出具體的顏色,它需要與幀緩沖區framebuffer中已經存在的顏色進行混合。這個轉換的過程成為混色,它有兩種方式:

  • 將old和new顏色進行混合產出一個最終的顏色
  • 使用按位操作混合old和new顏色的值

有兩個結構體用於配置顏色混合。第一個結構體VkPipelineColorBlendAttachmentState包括了每個附加到幀緩沖區的配置。第二個結構體VkPipelineColorBlendStateCreateInfo包含了全局混色的設置。在我們的例子中僅使用第一種方式:

VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional

這種針對每個幀緩沖區配置混色的方式,使用如下偽代碼進行說明:

if (blendEnable) {
    finalColor.rgb = (srcColorBlendFactor * newColor.rgb) <colorBlendOp> (dstColorBlendFactor * oldColor.rgb);
    finalColor.a = (srcAlphaBlendFactor * newColor.a) <alphaBlendOp> (dstAlphaBlendFactor * oldColor.a);
} else {
    finalColor = newColor;
}

finalColor = finalColor & colorWriteMask;

如果blendEnable設置為VK_FALSE,那麽從片段著色器輸出的新顏色不會發生變化,否則兩個混色操作會計算新的顏色。所得到的結果與colorWriteMask進行AND運算,以確定實際傳遞的通道。

大多數的情況下使用混色用於實現alpha blending,新的顏色與舊的顏色進行混合會基於它們的opacity透明通道。finalColor作為最終的輸出:

finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor;
finalColor.a = newAlpha.a;

可以通過一下參數完成:

colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

可以在規範中找到所有有關VkBlendFactorVkBlendOp的枚舉值。

第二個結構體持有所有幀緩沖區的引用,它允許設置混合操作的常量,該常量可以作為後續計算的混合因子:

VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional

如果需要使用第二種方式設置混合操作(bitwise combination), 需要設置logicOpEnableVK_TURE。二進制位操作在logicOp字段中指定。在第一種方式中會自動禁止,等同於為每一個附加的幀緩沖區framebuffer關閉混合操作,blendEnableVK_FALSEcolorWriteMask掩碼會用確定幀緩沖區中具體哪個通道的顏色受到影響。它也可以在兩種方式下禁止,截至目前,片段緩沖區向幀緩沖區中輸出的顏色不會進行任何變化。

Dynamic state


之前創建的一些結構體的狀態可以在運行時動態修改,而不必重新創建。比如viewport的大小,line width和blend constants。如果需要進行這樣的操作,需要填充VkPipelineDynamicStateCreateInfo結構體:

VkDynamicState dynamicStates[] = {
    VK_DYNAMIC_STATE_VIEWPORT,
    VK_DYNAMIC_STATE_LINE_WIDTH
};

VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;

在繪制的過程中指定這些數據,這會導致忽略之前的相關數值。我們會在後續的章節中回過頭來討論。如果沒有任何需要動態修改的數值清設置為nullptr

Pipeline layout


可以在著色器中使用uniform,它是類似與動態狀態變量的全局變量,可以在繪畫時修改,可以更改著色器的行為而無需重新創建它們。它們通常用於將變換矩陣傳遞到頂點著色器或者在片段著色器沖創建紋理采樣器。

這些uniform數值需要在管線創建過程中,通過VkPipelineLayout對象指定。即使在後續內容中用到,我們也仍然需要創建一個空的pipeline layout。

創建類成員變量持有該對象,因為我們在後續章節中的函數中引用它:

VkPipelineLayout pipelineLayout;

createGraphicsPipeline函數中創建對象:

VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = 0; // Optional

if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
    throw std::runtime_error("failed to create pipeline layout!");
}

該結構體還指定了push常量,這是將動態值傳遞給著色器的拎一個方式。pipeline layout可以在整個程序的生命周期內引用,所以它在程序退出的時候進行銷毀。

void cleanup() {
    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
    ...
}

Conclusion


這就是所有有關fixed-function的內容,看起來有很多的工作去做,值得慶幸的是我們幾乎了解了所有有關渲染管線的內容。這個過程減少了因為不了解某些組件的默認狀態,而造成運行時碰到未知行為的可能性。

然而,在我們可以最終創建圖形管線之前,還有一個對象需要創建,它就是render pass。

項目代碼 GitHub 地址。

Vulkan Tutorial 12 Fixed functions