|
Grado en Ingeniería Informática
Animación por Ordenador
Curso 2025/2026
|
Práctica 4b
|
|
Introducción de un Index Buffer
|
|
Objetivos
|
|
Modificar la aplicación gráfica para que los vértices se lean de
forma indexada.
|
|
Las estructuras VkBuffer y
VkDeviceMemory
|
|
La forma de crear un buffer de índices es idéntica a la
forma en la que se crean los buffers de atributos de
vértices. Es decir, hay que crear un VkBuffer, reservar
memoria en un VkDeviceMemory, mapear la memoria para
almacenar los datos y vincular la memoria al buffer. La
única diferencia es que al crear el buffer hay que indicar
el valor VK_BUFFER_USAGE_INDEX_BUFFER_BIT en el campo usage
de la estructura VkBufferCreateInfo.
|
|
Comandos
|
|
Para
realizar un dibujo utilizando datos indexados se utiliza
el comando vkCmdDrawIndexed(). El parámetro
indexCount define el número de índices que se
leerán para dibujar las primitivas. El parámetro
firstIndex indica cual el el primer índice que se
va a leer del buffer de índices (lo normal es
que sea el 0). El parámetro vertexOffset es una
constante que se suma a los valores leidos del
buffer para obtener el índice de los vértices
(generalmente este desplazamiento es 0). El parámetro
instanceCount indica el número de instancias
(es decir, el número de veces que se dibujarán las
primitivas sobre el conjunto de vértices). El parámetro
firstInstance es el valor que se asignará a la
primera instancia. El Vertex Shader puede
acceder al número de instancia que se está ejecutando
por medio de la variable de entrada predefinida
gl_InstanceIndex.
void vkCmdDrawIndexed(
VkCommandBuffer commandBuffer,
uint32_t indexCount,
uint32_t instanceCount,
uint32_t firstIndex,
int32_t vertexOffset,
uint32_t firstInstance);
|
Para indicar cual es el buffer de índices a utilizar en el comando DrawIndexed
se utiliza la función vkCmdBindIndexBuffer(). El parámetro
indexType indica el tipo de
dato que utiliza el buffer. Puede utilizarse el valor VK_INDEX_TYPE_UINT16
para indicar que los índices
se almacenan como “short” (enteros de 16 bits) o el valor VK_INDEX_TYPE_UINT32 para indicar que
los índices se almacenan como "int" (enteros de 32 bits).
void vkCmdBindIndexBuffer(
VkCommandBuffer commandBuffer,
VkBuffer buffer,
VkDeviceSize offset,
VkIndexType indexType);
|
|
|
La clase GEIndexBuffer
|
|
La clase GEIndexBuffer contine la información
necesaria para construir un buffer de índices, es decir, un objeto VkBuffer para describir la estructura
del buffer y un objeto VkDeviceMemory para definir la zona
de memoria donde almacenar la información del buffer.
#pragma once
#include <vulkan/vulkan.h>
#include "GEGraphicsContext.h"
class GEIndexBuffer
{
public:
VkBuffer buffer;
VkDeviceMemory memory;
GEIndexBuffer(GEGraphicsContext* gc, size_t size, const void* data);
void destroy(GEGraphicsContext* gc);
};
|
Los métodos de la clase son los siguentes.
#include "GEIndexBuffer.h"
#include <iostream>
//
// FUNCIÓN: GEIndexBuffer::GEIndexBuffer()
//
// PROPÓSITO: Crea un Vertex Buffer
//
GEIndexBuffer::GEIndexBuffer(GEGraphicsContext* gc, size_t size, const void* data)
{
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(gc->device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
{
throw std::runtime_error("failed to create buffer!");
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(gc->device, buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = gc->findMemoryType(memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (vkAllocateMemory(gc->device, &allocInfo, nullptr, &memory) != VK_SUCCESS)
{
throw std::runtime_error("failed to allocate buffer memory!");
}
vkBindBufferMemory(gc->device, buffer, memory, 0);
void* gpudata;
vkMapMemory(gc->device, memory, 0, size, 0, &gpudata);
memcpy(gpudata, data, size);
vkUnmapMemory(gc->device, memory);
}
//
// FUNCIÓN: GEIndexBuffer::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Destruye los campos de un Index Buffer
//
void GEIndexBuffer::destroy(GEGraphicsContext* gc)
{
vkDestroyBuffer(gc->device, buffer, nullptr);
vkFreeMemory(gc->device, memory, nullptr);
}
|
|
|
Modificaciones de la clase GEFigure
|
|
La clase GEFigure se ha modificado para incluir el
contenido de los índices (la constante indices) y las
referencias al index buffer.
#pragma once
#include "GEGraphicsContext.h"
#include "GEVertex.h"
#include "GEVertexBuffer.h"
#include "GEIndexBuffer.h"
#include <vector>
const std::vector<GEVertex> vertices =
{
{{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};
const std::vector<uint16_t> indices =
{
0,1,2
};
class GEFigure
{
public:
GEFigure(GEGraphicsContext* gc);
void destroy(GEGraphicsContext* gc);
void addCommands(VkCommandBuffer commandBuffer, int index);
private:
GEVertexBuffer* vbo;
GEIndexBuffer* ibo;
};
|
El código de la clase modifica sus tres métodos. El constructor añade la creación del objeto
GEIndexBuffer. El
método destroy() añade la destrucción del buffer de
índices. El método addCommands() añade el
comando de selección del buffer de índices, vkCmdBindIndexBuffer, y
sustituye el comando vkCmdDraw por el comando
vkCmdDrawIndexed para indicar que se debe dibujar en modo
indexado.
#include "GEFigure.h"
#include "GEVertex.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
//
// FUNCIÓN: GEFigure::GEFigure(GEGraphicsContext* gc)
//
// PROPÓSITO: Crea el Vertex Buffer
//
GEFigure::GEFigure(GEGraphicsContext* gc)
{
size_t vertexSize = sizeof(GEVertex) * vertices.size();
vbo = new GEVertexBuffer(gc, vertexSize, vertices.data());
size_t indexSize = sizeof(indices[0]) * indices.size();
ibo = new GEIndexBuffer(gc, indexSize, indices.data());
}
//
// FUNCIÓN: GEFigure::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Libera los buffers de la figura
//
void GEFigure::destroy(GEGraphicsContext* gc)
{
vbo->destroy(gc);
delete vbo;
ibo->destroy(gc);
delete ibo;
}
//
// FUNCIÓN: CAFigure::addCommands(VkCommandBuffer commandBuffer, int index)
//
// PROPÓSITO: Añade los comandos de renderizado al command buffer
//
void GEFigure::addCommands(VkCommandBuffer commandBuffer, int index)
{
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(commandBuffer, 0, 1, &(vbo->buffer), &offset);
vkCmdBindIndexBuffer(commandBuffer, ibo->buffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(commandBuffer, (uint32_t)indices.size(), 1, 0, 0, 0);
}
|
|
|
Aspecto final
|
|
El aspecto final sigue siendo el mismo que en el proyecto anterior.
|
|