前两篇的代码是在太过于冗杂,于是我做了一下简单的封装,现在的代码结构为:
VKApp 负责初始化窗口以及VkInstance、VkDevice
VKResourceCreator 负责创建各种资源(Image、ImageView、Buffer等)
VKScene 负责初始化Pipeline RenderPass等,在每一帧中执行渲染
VKCommand 负责创建所有可复用的命令(CopyBuffer等)
源码已经传到git上了:proj
导入纹理和模型会涉及到纹理图片的加载以及模型文件的加载,这两个都使用第三方库来完成,
stb:纹理加载库stb
tinyobjloader:模型加载库tinyobjloader
这两个库都非常地轻量,只需要把它们的头文件添加到工程中就可以了。
部署源码时请手动添加Vulkan、GLFW的头文件和静态链接库,GLM以及以上两个库的头文件。(CMakeList还没有完备地去写)
本节最终的效果为:
纹理的加载
加载纹理需要以下几步:
1.从纹理文件中读取数据到内存
2.创建一个staging buffer和一个VkImage
3.将读取后的数据转移到staging buffer中
4.对VkImage做Layout转移,将它转移到VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL中
以供数据拷贝
5.将staging buffer中的数据拷贝的VkImage中
6.对VkImage做Layout转移,将它转移到VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL供Shader读取使用(如果要生成Mipmap,则需要把这一步去掉,原因在后面会讲)
void VKResourceCreator::CreateTexture(const char * file , VkImage & textureImage , VkDeviceMemory & textureImageMemory , VKCommandManager * commandManager , VkQueue queue , VkCommandPool commandPool, int& width, int& height , uint32_t &mipLevels)
{
// 1. load data from file
int texWidth, texHeight, texChannels;
stbi_uc* pixels = stbi_load(file, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
VkDeviceSize imageSize = texWidth * texHeight * 4;
mipLevels = std::floor(std::log2(std::max(texWidth, texHeight))) + 1;
width = texWidth;
height = texHeight;
if (!pixels)
{
throw std::runtime_error("failed to load texture image . ");
}
// 2. create staging buffer and image
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
CreateBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
CreateImage(texWidth,
texHeight,
mipLevels,
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_TILING_OPTIMAL,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
textureImage,
textureImageMemory);
// 3. transition data into staging buffer
void* data;
vkMapMemory(logical_device_, stagingBufferMemory, 0, imageSize, 0, &data);
memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(logical_device_, stagingBufferMemory);
stbi_image_free(pixels);
// 4. transition layout 1
commandManager->TransitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, mipLevels, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, queue, commandPool);
// 5. copy data into image
commandManager->CopyBufferToImage(stagingBuffer, textureImage, texWidth, texHeight, queue, commandPool );
// 6. transition layout 2
commandManager->TransitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, mipLevels, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, queue, commandPool );
vkDestroyBuffer(logical_device_, stagingBuffer, NULL);
vkFreeMemory(logical_device_, stagingBufferMemory, NULL);
}
注意在创建Texture的时候给Usage添加了一个VK_IMAGE_USAGE_TRANSFER_SRC_BIT,因为它将在之后进行Mipmap,Mipmap也是通过内存转移来实现的,在这个时候Texture将成为它的子Mipmap的转移来源。
ImageLayout的转移要通过命令来实现
这里对ImageLayout转移的实现放在了commandManager中,因为这是一个可以被反复使用的一个命令。
它的具体实现为:
void VKCommandManager::TransitionImageLayout(VkImage image, VkFormat format, uint32_t mipLevels , VkImageLayout oldLayout, VkImageLayout newLayout , VkQueue graphicsQueue, VkCommandPool commandPool)
{
VkCommandBuffer commandBuffer = BeginSingleTimeCommand(commandPool);
VkI