Matrizes MVP e uniform buffer
Quando preparamos uma geometria que será desenhada em um aplicativo 3D, a geometria geralmente é modelada no sistema de coordenadas local – aquele em que é mais conveniente para o artista criar o modelo. No entanto, o pipeline gráfico espera que os vértices sejam transformados em um espaço de recorte (ou projetivo), pois é mais fácil (e mais rápido) executar muitas operações nesse sistema de coordenadas. Geralmente é o vertex shader que realiza essa transformação. Para isso, precisamos preparar uma matriz que represente uma perspectiva ou projeção ortogonal. A transformação do espaço local para o espaço de recorte é realizada apenas multiplicando a matriz pela posição de um vértice.
Além da matriz de projeção, temos as matrizes de visão e modelo. A matriz de visão tem a função de transformar o espaço do modelo para o espaço da câmera. A matriz de modelo transforma o espaço coordenado local de um objeto para o espaço do mundo.
Se compusermos todas as três, podemos usar o resultado para mapear todo o espaço de objetos para o espaço de tela, fazendo com que consigamos descobrir o que precisa passar para o próximo estágio do pipeline gráfico a partir das posições dos vértices de entrada. O conjunto dessas matrizes nós damos o nome de matrizes MVP e as transformações realizadas por elas, de transformações MVP.
Poderíamos incluir essas matrizes como dados de vértice, mas isso seria um desperdício de memória e exigiria que atualizássemos o buffer de vértice sempre que as transformações fossem alteradas. As transformações poderiam facilmente mudar em praticamente todos os quadros.
Uma das formas de resolver isso no Vulkan é usar objetos de uniform1 buffer (UBO, do inglês uniform buffer object). Um uniform buffer é um buffer que é acessível somente para leitura pelos shaders, para que os shaders possam ler dados constantes dos parâmetros.
1 Uma variável uniform é uma variável global do shader declarada com o qualificador
uniform
. Variáveis uniform atuam como parâmetros que o usuário de um programa shader pode transmitir para esse programa. Variáveis uniform têm esse nome porque não mudam de uma chamada de shader para a seguinte em uma chamada de renderização específica. Isso as diferencia das entradas e saídas do estágio de shader, que geralmente são diferentes para cada chamada de um estágio de shader (KHRONOS, 2019b).
Digamos que temos os dados que queremos e que são armazenados em nosso aplicativo em uma estrutura como esta:
struct UniformBufferObject {
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 proj;
};
Então, podemos copiar esses dados para um VkBuffer
e acessá-los através de um descritor de UBO a partir do vertex shader, assim:
layout(set = 0, binding = 1) 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;
fragTexCoord = inTexCoord;
}
O primeiro passo é definir o UBO no código C++. Vamos criar uma nova estrutura em renderer.h
chamada UniformBufferObject
:
struct UniformBufferObject {
QMatrix4x4 model;
QMatrix4x4 view;
QMatrix4x4 proj;
};
Atualizando o vertex shader
O próximo passo é modificar o vertex shader para incluir o UBO como foi especificado acima. Assumiremos que o leitor esteja familiarizado com as transformações MVP.
layout(set = 0, binding = 1) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord;
void main() {
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
fragColor = inColor;
fragTexCoord = inTexCoord;
}
A linha que calcula gl_Position
é alterada para que agora o vertex shader leve em conta a matriz MVP. O último componente do vetor não será mais 0
, pois divide as outras coordenadas com base na distância da câmera para criar um efeito de profundidade.