Introduction

我們現(xiàn)在可以將任意屬性傳遞給每個頂點(diǎn)的頂點(diǎn)著色器使用。但是全局變量呢?我們將會從本章開始介紹3D圖形相關(guān)的內(nèi)容,并需要一個模型視圖投影矩陣。我們確實(shí)可以將它一頂點(diǎn)的方式包含,但是這非常浪費(fèi)帶寬、內(nèi)存,并且需要我們在變換的時候更新頂點(diǎn)緩沖區(qū)的數(shù)據(jù)。這種變換通常發(fā)生在每一幀。

 

在Vulkan中正確處理此問題的途徑是使用資源描述符。描述符是著色器資源訪問諸如緩沖區(qū)和圖像這樣資源的一種方式。我們需要設(shè)置一個包含轉(zhuǎn)換矩陣的緩沖區(qū),并使頂點(diǎn)著色器通過描述符訪問它們。描述符的使用由三部分組成:

  • 在管線創(chuàng)建時指定描述符的布局結(jié)構(gòu)

  • 從描述符對象池中分配描述符集合

  • 在渲染階段綁定描述符集合

描述符布局指定了管線訪問的資源的類型,就像渲染通道指定附件的類型一樣。描述符集合指定將綁定到描述符的實(shí)際緩沖區(qū)或映射資源。就像幀緩沖區(qū)為綁定渲染通道的附件而指定實(shí)際的圖像視圖一樣。描述符集合就像頂點(diǎn)緩沖區(qū)和幀緩沖區(qū)一樣被綁定到繪制命令。

 

有許多類型的描述符,但在本章中,我們將使用統(tǒng)一的緩沖區(qū)對象uniform buffer objects(UBO)。我們將會在后面的章節(jié)中討論其他類型的描述符,但基本過程是一樣的。假設(shè)我們有一個數(shù)據(jù),我們希望頂點(diǎn)著色器擁有一個這樣的C結(jié)構(gòu)體:

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

我們可以拷貝數(shù)據(jù)到VkBuffer并在頂點(diǎn)著色器中通過uniform buffer object描述訪問它們:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

我們會在每一幀更新模型,視圖和投影矩陣,使前一章的矩形以3D旋轉(zhuǎn)。

Vertex shader


修改頂點(diǎn)著色器包含像上面指定的統(tǒng)一緩沖區(qū)對象uniform buffer object。假設(shè)大家比較熟悉MVP變換。如果不是這樣,可以看第一章中提到的內(nèi)容了解。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

#version 450#extension GL_ARB_separate_shader_objects : enable

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;

layout(location = 0) out vec3 fragColor;out gl_PerVertex {
    vec4 gl_Position;
};void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

需要注意的是uniformin 和 out它們聲明的順序無關(guān)緊要。binding指令與location屬性指令類似。我們將在描述符布局中引用此綁定。gl_Position使用變換矩陣計算裁剪坐標(biāo)中最終的位置信息。

Descriptor set layout


下一步在C++應(yīng)用層定義UBO數(shù)據(jù)結(jié)構(gòu),并告知Vulkan在頂點(diǎn)著色器使用該描述符。

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

我們可以使用GLM中的與著色器中結(jié)構(gòu)體完全匹配的數(shù)據(jù)類型。矩陣中的數(shù)據(jù)與著色器預(yù)期的二進(jìn)制數(shù)據(jù)兼容,所以我們可以稍后將一個UniformBufferObject通過memcpy拷貝到VkBuffer中。

 

我們需要在管線創(chuàng)建時,為著色器提供關(guān)于每個描述符綁定的詳細(xì)信息,就像我們?yōu)槊總€頂點(diǎn)屬性和location索引做的一樣。我們添加一個新的函數(shù)來定義所有這些名為createDescritorSetLayout的信息??紤]到我們會在管線中使用,它應(yīng)該在管線創(chuàng)建函數(shù)之前調(diào)用。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

void initVulkan() {
    ...
    createDescriptorSetLayout();
    createGraphicsPipeline();
    ...
}

...void createDescriptorSetLayout() {

}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

每個綁定都會通過VkDescriptorSetLayoutBinding結(jié)構(gòu)體描述。

void createDescriptorSetLayout() {
    VkDescriptorSetLayoutBinding uboLayoutBinding = {};
    uboLayoutBinding.binding = 0;
    uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    uboLayoutBinding.descriptorCount = 1;
}

前兩個字段指定著色器中使用的binding和描述符類型,它是一個UBO。著色器變量可以表示UBO數(shù)組,descriptorCount指定數(shù)組中的數(shù)值。比如,這可以用于骨骼動畫中的每個骨骼變換。我們的MVP 變換是一個單UBO對象,所以我們使用descriptorCount1。

uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

我們也需要指定描述符在著色器哪個階段被引用。stageFlags字段可以是VkShaderStage標(biāo)志位或VK_SHADER_STAGE_ALL_GRAPHICS的組合。在我們的例子中,我們僅僅在頂點(diǎn)著色器中使用描述符。

uboLayoutBinding.pImmutableSamplers = nullptr; // Optional

pImmutableSamplers字段僅僅與圖像采樣的描述符有關(guān),我們會在后面的內(nèi)容討論?,F(xiàn)在可以設(shè)置為默認(rèn)值。

 

所有的描述符綁定都會被組合在一個單獨(dú)的VkDescriptorSetLayout對象。定義一個新的類成員變量pipelineLayout

VkDescriptorSetLayout descriptorSetLayout;
VkPipelineLayout pipelineLayout;

我們使用vkCreateDescriptorSetLayout創(chuàng)建。這個函數(shù)接受一個簡單的結(jié)構(gòu)體VkDescriptorSetLayoutCreateInfo,該結(jié)構(gòu)體持有一個綁定數(shù)組。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {    throw std::runtime_error("failed to create descriptor set layout!");
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

我們需要在創(chuàng)建管線的時候指定描述符集合的布局,用以告知Vulkan著色器將要使用的描述符。描述符布局在管線布局對象中指定。修改VkPipelineLayoutCreateInfo引用布局對象:

VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;

到這里可能會有疑問,為什么可以在這里指定那么多的描述符布局集合,因?yàn)橐粋€包含了所有的綁定。我們將在下一章回顧一下,我們將在其中查看描述符對象池和描述符集合。

 

描述符布局應(yīng)該在程序退出前始終有效:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

void cleanup() {
    cleanupSwapChain();

    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);

    ...
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 Uniform buffer


 

在下一章節(jié)我們會為著色器重點(diǎn)包含UBO的緩沖區(qū),但是首先要創(chuàng)建該緩沖區(qū)。在每一幀中我們需要拷貝新的數(shù)據(jù)到UBO緩沖區(qū),所以存在一個暫存緩沖區(qū)是沒有意義的。在這種情況下,它只會增加額外的開銷,并且可能降低性能而不是提升性能。

 

添加類成員uniformBufferuniformBufferMemory

VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;

VkBuffer uniformBuffer;
VkDeviceMemory uniformBufferMemory;

同樣需要添加新的函數(shù)createUniformBuffer來分配緩沖區(qū),并在createIndexBuffer之后調(diào)用。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

void initVulkan() {
    ...
    createVertexBuffer();
    createIndexBuffer();
    createUniformBuffer();
    ...
}

...void createUniformBuffer() {
    VkDeviceSize bufferSize = sizeof(UniformBufferObject);
    createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffer, uniformBufferMemory);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

我們要寫一個單獨(dú)的函數(shù)來更新uniform緩沖區(qū),確保每一幀都有更新,所以在這里不會有vkMapMemory。UBO的數(shù)據(jù)將被用于所有的繪制使用,所以包含它的緩沖區(qū)只能在最后銷毀:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

void cleanup() {
    cleanupSwapChain();

    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
    vkDestroyBuffer(device, uniformBuffer, nullptr);
    vkFreeMemory(device, uniformBufferMemory, nullptr);

    ...
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

Updating uniform data


添加新的函數(shù)updateUniformBuffer并在main loop中調(diào)用:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

void mainLoop() {    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        updateUniformBuffer();
        drawFrame();
    }

    vkDeviceWaitIdle(device);
}

...void updateUniformBuffer() {

}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

該函數(shù)會在每一幀中創(chuàng)建新的變換矩陣以確保幾何圖形旋轉(zhuǎn)。我們需要引入新的頭文件使用該功能:

#define GLM_FORCE_RADIANS#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <chrono>

glm/gtc/matrix_transform.cpp頭文件對外提供用于生成模型變換矩陣的gl::rotate,視圖變換矩陣的 glm:lookAt 和 投影變換矩陣的 glm::perspective。GLM_FORCE_RADIANS定義是必要的,它確保像 glm::rotate 這樣的函數(shù)使用弧度制作為參數(shù),以避免任何可能的混淆。

 

chrono標(biāo)準(zhǔn)庫的頭文件對外提供計時功能。我們將使用它來確保集合旋轉(zhuǎn)每秒90度,無論幀率如何。

void updateUniformBuffer() {    static auto startTime = std::chrono::high_resolution_clock::now();

    auto currentTime = std::chrono::high_resolution_clock::now();    float time = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count() / 1000.0f;
}

 updateUniformBuffer函數(shù)將有關(guān)時間計算的邏輯開始,它會以毫秒級的精度計算渲染開始后的時間(秒為單位)。如果需要更準(zhǔn)確的時間,則可以使用 std::chrono::microseconds 并除以 1e6f,這是 1000000.0 的縮寫。

 

我們在UBO中定義模型,視圖和投影變換矩陣。模型變換將會圍繞Z-axis旋轉(zhuǎn),并使用 time 變量更新旋轉(zhuǎn)角度:

UniformBufferObject ubo = {};
ubo.model = glm::rotate(glm::mat4(), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));

glm::rotate 函數(shù)對現(xiàn)有的變換矩陣進(jìn)行旋轉(zhuǎn),它使用旋轉(zhuǎn)角度和旋轉(zhuǎn)軸作為參數(shù)。glm::mat4() 默認(rèn)的構(gòu)造返回歸一化的矩陣。使用 time * glm::radians(90.0f) 可以實(shí)現(xiàn)每秒90度的旋轉(zhuǎn)目的。

ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));

對于視圖變換,我們決定以45度從上觀察幾何圖形。glm::lookAt 函數(shù)以眼睛位置,中心位置和上方向?yàn)閰?shù)。

ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f);

選擇使用FOV為45度的透視投影。其他參數(shù)是寬高比,近裁剪面和遠(yuǎn)裁剪面。重要的是使用當(dāng)前的交換鏈擴(kuò)展來計算寬高比,以便在窗體調(diào)整大小后參考最新的窗體寬度和高度。

ubo.proj[1][1] *= -1;

 GLM最初是為OpenGL設(shè)計的,它的裁剪坐標(biāo)的Y是反轉(zhuǎn)的。修正該問題的最簡單的方法是在投影矩陣中Y軸的縮放因子反轉(zhuǎn)。如果不這樣做圖像會被倒置。

 

現(xiàn)在定義了所有的變換,所以我們可以將UBO中的數(shù)據(jù)復(fù)制到uniform緩沖區(qū)。這與我們對頂點(diǎn)緩沖區(qū)的操作完全相同,除了沒有暫存緩沖區(qū):

void* data;
vkMapMemory(device, uniformBufferMemory, 0, sizeof(ubo), 0, &data);
    memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(device, uniformBufferMemory);

使用UBO將并不是經(jīng)常變化的值傳遞給著色器是非常有效的方式。相比傳遞一個更小的數(shù)據(jù)緩沖區(qū)到著色器中,更有有效的方式是使用常量。我們在未來的章節(jié)中會看到這些。

 

在下一章節(jié)我們會討論描述符集合,它會將VkBuffer綁定到uniform緩沖區(qū)描述符,最終著色器可以訪問變換矩陣等數(shù)據(jù)。

Introduction

我們現(xiàn)在可以將任意屬性傳遞給每個頂點(diǎn)的頂點(diǎn)著色器使用。但是全局變量呢?我們將會從本章開始介紹3D圖形相關(guān)的內(nèi)容,并需要一個模型視圖投影矩陣。我們確實(shí)可以將它一頂點(diǎn)的方式包含,但是這非常浪費(fèi)帶寬、內(nèi)存,并且需要我們在變換的時候更新頂點(diǎn)緩沖區(qū)的數(shù)據(jù)。這種變換通常發(fā)生在每一幀。

 

在Vulkan中正確處理此問題的途徑是使用資源描述符。描述符是著色器資源訪問諸如緩沖區(qū)和圖像這樣資源的一種方式。我們需要設(shè)置一個包含轉(zhuǎn)換矩陣的緩沖區(qū),并使頂點(diǎn)著色器通過描述符訪問它們。描述符的使用由三部分組成:

  • 在管線創(chuàng)建時指定描述符的布局結(jié)構(gòu)

  • 從描述符對象池中分配描述符集合

  • 在渲染階段綁定描述符集合

描述符布局指定了管線訪問的資源的類型,就像渲染通道指定附件的類型一樣。描述符集合指定將綁定到描述符的實(shí)際緩沖區(qū)或映射資源。就像幀緩沖區(qū)為綁定渲染通道的附件而指定實(shí)際的圖像視圖一樣。描述符集合就像頂點(diǎn)緩沖區(qū)和幀緩沖區(qū)一樣被綁定到繪制命令。

 

有許多類型的描述符,但在本章中,我們將使用統(tǒng)一的緩沖區(qū)對象uniform buffer objects(UBO)。我們將會在后面的章節(jié)中討論其他類型的描述符,但基本過程是一樣的。假設(shè)我們有一個數(shù)據(jù),我們希望頂點(diǎn)著色器擁有一個這樣的C結(jié)構(gòu)體:

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

我們可以拷貝數(shù)據(jù)到VkBuffer并在頂點(diǎn)著色器中通過uniform buffer object描述訪問它們:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

我們會在每一幀更新模型,視圖和投影矩陣,使前一章的矩形以3D旋轉(zhuǎn)。

Vertex shader


修改頂點(diǎn)著色器包含像上面指定的統(tǒng)一緩沖區(qū)對象uniform buffer object。假設(shè)大家比較熟悉MVP變換。如果不是這樣,可以看第一章中提到的內(nèi)容了解。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

#version 450#extension GL_ARB_separate_shader_objects : enable

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;

layout(location = 0) out vec3 fragColor;out gl_PerVertex {
    vec4 gl_Position;
};void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

需要注意的是uniformin 和 out它們聲明的順序無關(guān)緊要。binding指令與location屬性指令類似。我們將在描述符布局中引用此綁定。gl_Position使用變換矩陣計算裁剪坐標(biāo)中最終的位置信息。

Descriptor set layout


下一步在C++應(yīng)用層定義UBO數(shù)據(jù)結(jié)構(gòu),并告知Vulkan在頂點(diǎn)著色器使用該描述符。

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

我們可以使用GLM中的與著色器中結(jié)構(gòu)體完全匹配的數(shù)據(jù)類型。矩陣中的數(shù)據(jù)與著色器預(yù)期的二進(jìn)制數(shù)據(jù)兼容,所以我們可以稍后將一個UniformBufferObject通過memcpy拷貝到VkBuffer中。

 

我們需要在管線創(chuàng)建時,為著色器提供關(guān)于每個描述符綁定的詳細(xì)信息,就像我們?yōu)槊總€頂點(diǎn)屬性和location索引做的一樣。我們添加一個新的函數(shù)來定義所有這些名為createDescritorSetLayout的信息。考慮到我們會在管線中使用,它應(yīng)該在管線創(chuàng)建函數(shù)之前調(diào)用。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

void initVulkan() {
    ...
    createDescriptorSetLayout();
    createGraphicsPipeline();
    ...
}

...void createDescriptorSetLayout() {

}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

每個綁定都會通過VkDescriptorSetLayoutBinding結(jié)構(gòu)體描述。

void createDescriptorSetLayout() {
    VkDescriptorSetLayoutBinding uboLayoutBinding = {};
    uboLayoutBinding.binding = 0;
    uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    uboLayoutBinding.descriptorCount = 1;
}

前兩個字段指定著色器中使用的binding和描述符類型,它是一個UBO。著色器變量可以表示UBO數(shù)組,descriptorCount指定數(shù)組中的數(shù)值。比如,這可以用于骨骼動畫中的每個骨骼變換。我們的MVP 變換是一個單UBO對象,所以我們使用descriptorCount1

uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

我們也需要指定描述符在著色器哪個階段被引用。stageFlags字段可以是VkShaderStage標(biāo)志位或VK_SHADER_STAGE_ALL_GRAPHICS的組合。在我們的例子中,我們僅僅在頂點(diǎn)著色器中使用描述符。

uboLayoutBinding.pImmutableSamplers = nullptr; // Optional


http://www.cnblogs.com/heitao/p/7079055.html