Atualizando os dados uniform
Criamos uma nova função updateUniformBuffer
e adicionamos uma chamada a partir da função startNextFrame
antes da chamada para a função vkCmdDraw
:
void Renderer::startNextFrame() {
...
updateUniformBuffer();
m_deviceFunctions->vkCmdDraw(
commandBuffer,
static_cast<uint32_t>(m_object->model->vertices.size()),
1,
0,
0
);
...
}
void Renderer::updateUniformBuffer()
{
}
Esta função irá gerar uma nova transformação a cada quadro para fazer a geometria girar.
void Renderer::updateUniformBuffer() {
static QTime startTime(QTime::currentTime());
float time = static_cast<float>(startTime.elapsed())/1000.0f;
}
A função updateUniformBuffer
começará com alguma lógica para calcular o tempo em segundos desde que a renderização foi iniciada. Para isso, utilizamos a classe QTime
que expõe funções para realizar a cronometragem precisa. Usaremos isso para garantir que a geometria gire 90 graus por segundo, independentemente da taxa de quadros. Devemos nos certificar de incluir o cabeçalho <QTime>
em renderer.cpp
.
Vamos agora definir as transformações do modelo, visão e projeção no UBO. A rotação do modelo será uma rotação simples em torno do eixo Z
usando a variável de tempo:
UniformBufferObject ubo = {};
ubo.model.setToIdentity();
ubo.model.rotate(time * 90.0, QVector3D(0.0f, 0.0f, 1.0));
A função QMatrix4x4::setToIdentity
define a matriz como uma matriz identidade. A função QMatrix4x4::rotate
recebe como parâmetros o ângulo de rotação e o eixo de rotação. Usando um ângulo de rotação de time * 90.0
cumpre o objetivo de rotação de 90 graus por segundo.
QVector3D eye = QVector3D(1.0, 1.0, 1.0);
QVector3D center = QVector3D(0.0, 0.0, 0.0);
QVector3D up = QVector3D(0.0, 0.0, 1.0);
ubo.view.setToIdentity();
ubo.view.lookAt(eye, center, up);
Para a transformação da visão, decidimos observar a geometria de cima em um ângulo de 45 graus. A função QMatrix4x4::lookAt
recebe como parâmetros a posição do observador (eye
), a posição central (center
) e o eixo para cima (up
).
QSize swapChainImageSize = m_window->swapChainImageSize();
float aspectRatio = static_cast<float>(swapChainImageSize.width()) / static_cast<float>(swapChainImageSize.height());
ubo.proj = m_window->clipCorrectionMatrix();
ubo.proj.perspective(45.0f, aspectRatio, 0.01f, 100.0f);
A classe QMatrix4x4
foi originalmente projetada para OpenGL, onde a coordenada Y
das coordenadas de recorte é invertida. Ao pré-definir a matriz de projeção com QVulkanWindow::clipCorrectionMatrix()
, os aplicativos podem continuar assumindo que Y
está apontando para cima.
Usamos uma projeção em perspectiva. Para isso, utilizamos a função QMatrix4x4::perspective
que recebe como parâmetros, respectivamente, o ângulo de visão, a proporção de tela (aspect ratio) e os planos de visualização próximo e distante. É importante usar o tamanho atual das imagem do swap chain para calcular a proporção para levar em consideração a nova largura e a altura da janela após um redimensionamento. Essa informação é obtida da função QVulkanWindow::swapChainImageSize()
.
Todas as transformações estão definidas agora, para que possamos copiar os dados para o uniform buffer do objeto 3D. Isso acontece exatamente da mesma maneira que fizemos com os buffers de vértice, exceto sem um staging buffer:
quint8 *data;
VkDevice device = m_window->device();
m_deviceFunctions->vkMapMemory(device, m_object->uniformBufferMemory, 0, sizeof(UniformBufferObject), 0, reinterpret_cast<void **>(&data));
memcpy(data, ubo.model.constData(), 64);
memcpy(data + 64, ubo.view.constData(), 64);
memcpy(data + 128, ubo.proj.constData(), 64);
m_deviceFunctions->vkUnmapMemory(device, m_object->uniformBufferMemory);
Utilizamos aqui o tipo quint8*
, em vez de void**
, para usarmos aritmética de ponteiro para definir os valores das matrizes concatenados dentro da variável data
. Cada matriz possui o tamanho de 64 bytes.