Link

Geometria 3D

Antes de podermos carregar vértices de um modelo 3D, precisamos atualizar a estrutura Vertex para que as coordenadas sejam vetores tridimensionais. Também devemos alterar o campo format na estrutura VkVertexInputAttributeDescription correspondente às coordenadas:

struct Vertex {
	QVector3D pos;
	QVector3D color;
	QVector2D texCoord;

	...

	static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
		std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions = {};

		attributeDescriptions[0].binding = 0;
		attributeDescriptions[0].location = 0;
		attributeDescriptions[0].format =
			VK_FORMAT_R32G32B32_SFLOAT;
		attributeDescriptions[0].offset = offsetof(Vertex, pos);

		...
	}
};

Atualizamos também a entrada do vertex shader que corresponde às coordenadas dos vértices. Não podemos nos esquecer de recompilar o shader depois.

layout(location = 0) in vec3 inPosition;

...

void main() {
	gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
	fragColor = inColor;
	fragTexCoord = inTexCoord;
}

Vamos carregar os vértices do arquivo de modelo agora, então devemos remover os vértices definidos no membro vertices de Model:

QVector<Vertex> vertices;

Queremos carregar modelos 3D dinamicamente, para isso, precisamos fazer algumas modificações no código.

Primeiro, vamos criar uma função auxiliar chamada drawObject em Renderer. Vamos mover parte do código que está em startNextFrame para essa função:

void Renderer::drawObject() {
	if (!m_object) {
		return;
	}

	if (m_object->vertexBuffer == VK_NULL_HANDLE) {
		initObject();
	}

	updateUniformBuffer();

	VkCommandBuffer commandBuffer = m_window->currentCommandBuffer();

	m_deviceFunctions->vkCmdBindPipeline(
		commandBuffer,
		VK_PIPELINE_BIND_POINT_GRAPHICS,
		m_graphicsPipeline
	);

	m_deviceFunctions->vkCmdBindDescriptorSets(
		commandBuffer,
		VK_PIPELINE_BIND_POINT_GRAPHICS,
		m_pipelineLayout,
		0,
		1,
		&m_object->descriptorSet,
		0,
		nullptr
	);

	VkBuffer vertexBuffers[] = {m_object->vertexBuffer};
	VkDeviceSize offsets[] = {0};
	m_deviceFunctions->vkCmdBindVertexBuffers(
		commandBuffer,
		0,
		1,
		vertexBuffers,
		offsets
	);

	m_deviceFunctions->vkCmdDraw(
		commandBuffer,
		static_cast<uint32_t>(m_object->model->vertices.size()),
		1,
		0,
		0
	);
}

Na função drawObject primeiro verificamos se o membro m_object já foi inicializado. Caso não tenha sido, simplesmente retornamos da função. Se m_object for diferente de nullptr mas o seu membro vertexBuffer for igual a VK_NULL_HANDLE, então chamamos a função initObject para inicializar o objeto 3D.

Nossa função startNextFrame fica assim:

void Renderer::startNextFrame() {
	VkRenderPassBeginInfo renderPassInfo = {};
	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	renderPassInfo.renderPass = m_window->defaultRenderPass();
	renderPassInfo.framebuffer = m_window->currentFramebuffer();
	renderPassInfo.renderArea.offset.x = 0;
	renderPassInfo.renderArea.offset.y = 0;
	const QSize swapChainImageSize = m_window->swapChainImageSize();
	renderPassInfo.renderArea.extent.width = swapChainImageSize.width();
	renderPassInfo.renderArea.extent.height = swapChainImageSize.height();

	std::array<VkClearValue, 2> clearValues = {};
	clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f};
	clearValues[1].depthStencil = {1.0f, 0};
	renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
	renderPassInfo.pClearValues = clearValues.data();

	VkCommandBuffer commandBuffer = m_window->currentCommandBuffer();
	m_deviceFunctions->vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

	VkViewport viewport;
	viewport.x = 0;
	viewport.y = 0;
	viewport.width = swapChainImageSize.width();
	viewport.height = swapChainImageSize.height();
	viewport.minDepth = 0.0f;
	viewport.maxDepth = 1.0f;
	m_deviceFunctions->vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

	VkRect2D scissor;
	scissor.offset.x = 0;
	scissor.offset.y = 0;
	scissor.extent.width = viewport.width;
	scissor.extent.height = viewport.height;
	m_deviceFunctions->vkCmdSetScissor(
		commandBuffer,
		0,
		1,
		&scissor
	);

	drawObject();

	m_deviceFunctions->vkCmdEndRenderPass(commandBuffer);

	m_window->frameReady();
	m_window->requestUpdate();
}

Agora removemos a chamada para função initObject em initResources. Simplesmente não precisaremos mais dela lá:

void Renderer::initResources() {
    VkDevice device = m_window->device();
    m_deviceFunctions = m_window->vulkanInstance()->deviceFunctions(device);
    createDescriptorSetLayout();
    initPipeline();
    createTextureSampler();
}

Também devemos remover as duas primeiras linhas de initObject. Com isso nossa função initObject ficará assim:

void Renderer::initObject() {
	createObjectVertexBuffer();

	createUniformBuffer();

	addTextureImage(":/textures/texture.png");
}

Em model.h criamos uma nova função auxiliar chamada isValid para indicar se o modelo é válido, ou seja, se possuí vértices carregados no membro vertices:

bool isValid() const { return vertices.size(); }

Para adicionarmos dinamicamente um modelo em Renderer criamos a função publica addObject que recebe como parâmetro um ponteiro para um objeto do tipo Model:

public:
	...
	void addObject(QSharedPointer<Model> model);


void Renderer::addObject(QSharedPointer<Model> model) {

}

Iniciamos a função verificando se o modelo é valido, para isso chamamos nossa função isValid. Caso seja válido, criamos um novo objeto 3D e armazenamos-o no membro m_object:

if (model->isValid()) {
	if (m_object)
		releaseObjectResources();

	m_object = new Object3D(model);

	m_window->requestUpdate();
}

Também verificamos se m_object já foi criado. Caso isso tenha ocorrido, chamamos nossa função releaseObjectResources para liberar os recursos já alocados para o objeto 3D anteriormente carregado, antes de criarmos um novo.


Anterior Próximo