文章

VulkanTutorial(17`Loading models,Mipmaps)

Loading models

我们将使用tinyobjloader库从OBJ文件加载顶点和索引,它速度快,易于集成,因为它是一个像stb_image一样的单一文件库 因为我们没有学习光照,使用照明烘焙的纹理 在程序中添加两个新的配置变量来定义模型和纹理路径: 并更新createTextureImage 以使用此路径变量加载纹理数据

Loading vertices and indices

模型加载和简单的图形没什么太大区别,主要就是vertices and indices的变更 我们现在要从模型文件中加载顶点和索引,所以你现在应该删除全局顶点和索引数组。将它们替换为非常量容器作为类成员 因为这次的索引很多,因此将索引的类型从uint16_t改为uint32_t,也不要忘记更改vkCmdBindIndexBuffer中的类型

LoadObj

包含tiny_obj_loader.h文件 添加新的loadModel函数,通过 if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) 加载模型路径,获取所有的顶点和索引数据,放到刚刚的vertices and indices数据结构中 OBJ文件由位置、法线、纹理坐标和面组成

  • attrib容器包括attrib.vertices、attrib.normals和attrib.texcoords的属性
  • shapes包含所有单独的面(每个面有三个顶点),每个面由一个顶点数组组成,每个顶点包含位置、法线和纹理坐标属性的索引
  • materials材质
  • err字符串包含错误
  • warn字符串包含加载文件时发生的警告

我们要循环所有的shapes对象,以便循环所有的对象网格索引shape.mesh.indices,以便查找attrib数组中的实际顶点属性: shape.mesh.indices其中包含vertex_index、normal_index和texcoord_index成员的索引(首个顶点索引,3个顶点依次递增),还需要将索引乘以3(因为存储顺序可能是x0, x1, x2, y0, y1, y2, z0, z1, z2…) 但是还有一个问题,OBJ格式假设一个坐标系,其中垂直坐标为0表示图像的底部,但是到Vulkan中,其中0表示图像的顶部,因此通过翻转纹理坐标的垂直分量来解决此问题 1730967593839

Mipmaps

多级贴图 / 多级渐远纹理,每个新图像的宽度和高度都是前一个图像的一半,Mipmap用作细节级别或LOD(细节级别)的一种形式。远离相机的物体将从较小的mip图像中采样其纹理。使用较小的图像可提高渲染速度并避免摩尔纹等伪影

miplevels

首先指定miplevels的级别数量,这个值是根据图像的尺寸计算,在stbi_load获得了(texWidth, texHeight)之后计算 mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1; max函数选择最大的维度。log2函数计算该维度可以被2整除的次数。floor函数处理最大维度不是2的幂的情况。1,使得原始图像具有mip级别 我们需要更改mip Image、mip ImageView和transitionImageLayout函数,将miplevels作为参数,并更改内部info的levelCount成员,以及所有调用这些函数的地方(对textureImage使用mipLevels ,对depthimage使用1) 这样image缓冲中就可以存储所有mip级别图像

Generating Mipmaps

我们的暂存stagingBuffer只能通过vkCmdCopyBufferToImage填充miplevels== 0 的纹理层,对于其他级别需要调用vkCmdBlitImage(位块传输)命令执行复制、缩放和筛选操作,以便将数据传输到纹理图像的每一层 我们现在打算从miplevels==0级别的纹理传输到其他级别图像中,因此要指定VK_IMAGE_USAGE_TRANSFER_SRC_BIT 标志 我们依旧要考虑图像转换时的布局,但这次不再是一个图像转换,要考虑miplevels个图像转换,因此我们需要编写更多的vkCmdPipelineBarrier命令转换每个图像以便被着色器使用, 删除现有到 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL的transitionImageLayout过渡,编写新的生成mipmaps的函数 对于VkImageMemoryBarrier结构体的部分成员,每次转换都保持不变,因此不需要放在for中,subresourceRange.miplevel、oldLayout、newLayout、srcallbackMask和dstallback Mask将针对每个转换进行更改(i从1开始)

实例

我们举例miplevels == 0时: 首先通过vkCmdPipelineBarrier命令将布局转换为VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,它可以等待从上一个vkCmdBlitImage命令或从vkCmdCopyBufferToImage填充(转换为源可以填充DST) 接下来创建VkImageBlit

  • 源mip级别是i - 1,目的地mip级别是i
  • srcOffsets数组的两个元素决定了数据将从哪个3D区域进行位块传输,{ mipWidth, mipHeight, 1 };
  • dstOffsets确定数据将被传输到的区域,dstOffsets[1]的X和Y维度被除以2,因为每个mip级别是前一级别大小的一半 { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
  • 其中Z维度必须为1,因为2D图像的深度为1

使用vkCmdBlitImage命令传输数据,srcImage和dstImage的布局分别为SRC和DST(刚才转换的布局),最后一个参数时filter过滤选项 再次使用vkCmdPipelineBarrier命令将布局转换为VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,以便供着色器使用 在每次循环结束,我们将当前的mip维度除以2,除法之前检查每个维度,以确保维度永远不会小于1 在for结束之前,我们再插入一个vkCmdPipelineBarrier转换为 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 。这不是由循环处理的,因为最后一个mip级别永远不会被blitted。

Linear filtering support

使用vkCmdBlitImage内置函数来生成所有的mip级别不能保证在所有平台上都支持它,需要图像格式支持线性过滤 vkGetPhysicalDeviceFormatProperties获取图像格式(包括3种linearTilingFeatures、optimalTilingFeatures和bufferFeatures),对线性过滤功能的支持可以使用 optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT 检查

Sampler

当VkImage缓冲保存mipmap数据时,VkSampler控制渲染时如何读取该数据允许我们指定,修改VkSamplerCreateInfo结构体成员 如果mipmapMode为 VK_SAMPLER_MIPMAP_MODE_NEAREST,则采样一个mip级别,如果mipmap模式为VK_SAMPLER_MIPMAP_MODE_LINEAR,则采样的两个mip级别,并将结果线性混合 如果对象靠近相机,则使用magFilter作为滤镜。如果对象距离相机较远,则使用minFilter mipLodBias允许我们强制Vulkan使用比正常情况下更低的lod和level samplerInfo.minLod = static_cast< float >(mipLevels / 2); 1731137215264

本文由作者按照 CC BY 4.0 进行授权
本页总访问量