更新uniform数据

添加一个叫做updateUniformBuffer的函数,然后在drawFrame函数中我们已经可以确定获取交换链图像是哪一个后调用它:

void drawFrame() {
        ...

    uint32_t imageIndex;
    VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);

        ...

    updateUniformBuffer(imageIndex);

    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

        ...
}

        ...

void updateUniformBuffer(uint32_t currentImage) {

}

调用updateUniformBuffer函数可以在每一帧产生一个新的变换矩阵。updateUniformBuffer函数的实现需要使用了下面这些头文件:

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

#include <chrono>

包含glm/gtc/matrix_transform.hpp头文件是为了使用glm::rotate之类的矩阵变换函数。GLM_FORCE_RADIANS宏定义用来使glm::rotate这些函数使用弧度作为参数的单位。

包含chrono头文件是为了使用计时函数。我们将通过计时函数实现每秒旋转90度的效果。

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

    auto currentTime = std::chrono::high_resolution_clock::now();
    float time = std::chrono::duration<float,
    std::chrono::seconds::period>(currentTime - startTime).count();
}

我们在uniform缓冲对象中定义我们的MVP变换矩阵。模型的渲染被我们设计成绕Z轴渲染time弧度。

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

glm::rotate函数以矩阵,旋转角度和旋转轴作为参数。glm::mat4(1.0f)用于构造单位矩阵。这里,我们通过time * glm::radians(90.0f)完成每秒旋转90度的操作。

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));

对于视图变换矩阵,我们使用上面代码中的定义。glm::lookAt函数以观察者位置,视点坐标和向上向量为参数生成视图变换矩阵。

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

对于透视变换矩阵,我们使用上面代码中的定义。glm::perspective函数以视域的垂直角度,视域的宽高比以及近平面和远平面距离为参数生成透视变换矩阵。特别需要注意在窗口大小改变后应该使用当前交换链范围来重新计算宽高比。

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

GLM库最初是为OpenGL设计的,它的裁剪坐标的Y轴和Vulkan是相反的。我们可以通过将投影矩阵的Y轴缩放系数符号取反来使投影矩阵和Vulkan的要求一致。如果不这样做,渲染出来的图像会被倒置。

定义完所有的变换矩阵,我们就可以将最后的变换矩阵数据复制到当前帧对应的uniform缓冲中。复制数据的方法和复制顶点数据到顶点缓冲一样,除了没有使用暂存缓冲:

void* data;
vkMapMemory(device, uniformBuffersMemory[currentImage], 0, sizeof(ubo), 0, &data);
memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(device, uniformBuffersMemory[currentImage]);

对于在着色器中使用的需要频繁修改的数据,这样使用UBO并非最佳方式。还有一种更加高效的传递少量数据到着色器的方法,我们会在之后的章节介绍它。

在下一章节,我们将会对绑定VkBuffer对象到uniform缓冲描述符的描述符集进行介绍。

本章节代码:

C++:

https://vulkan-tutorial.com/code/21_descriptor_layout.cpp

Vertex Shader:

https://vulkan-tutorial.com/code/21_shader_ubo.vert

Fragment Shader:

https://vulkan-tutorial.com/code/21_shader_ubo.frag