Vulkan Tutorial 9 模型加载Mipmaps

news2024/11/24 16:46:02

目录

28 加载模型

Sample mesh

加载顶点和索引

​编辑

顶点去重


28 加载模型

我们将使用tinyobjloader库来从OBJ文件中加载顶点和面。它的速度很快,而且很容易集成,因为它是一个像stb_image一样的单文件库。将包含tiny_obj_loader.h的目录添加到Additional Include Directories路径中。

Sample mesh

在这一章中,我们还不会启用灯光,所以使用一个将灯光烘烤到纹理中的样本模型会有帮助。找到这种模型的一个简单方法是在Sketchfab上寻找3D扫描。该网站上的许多模型都是以OBJ格式提供的,并有许可权。

在这个教程中,我决定使用nigelgoh (CC BY 4.0)的Viking room模型。我调整了该模型的大小和方向,将其作为当前几何形状的替代品。

  • viking_room.obj
  • viking_room.png

模型文件放在一个新的models目录下,放在shaderstextures旁边,并把纹理图像放在textures目录下。 在你的程序中放两个新的配置变量来定义模型和纹理的路径。


const std::string MODEL_PATH = "models/viking_room.obj";
const std::string TEXTURE_PATH = "textures/viking_room.png";

stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);

加载顶点和索引

我们现在要从模型文件中加载顶点和索引,所以你现在应该删除全局的verticesindices数组。用非const容器代替它们作为类成员:

std::vector<Vertex> vertices;
//把索引的类型从uint16_t改为uint32_t,因为顶点会比65535多很多。
std::vector<uint32_t> indices;
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;

//改变vkCmdBindIndexBuffer参数:

vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32);

//在一个源文件中定义TINYOBJLOADER_IMPLEMENTATION以包括函数体并避免链接器错误:

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

现在要写一个loadModel函数,使用这个库将网格中的顶点数据填充到verticesindices容器中。它应该在创建顶点和索引缓冲区之前被调用: attrib容器在其attrib.verticesattrib.normalsattrib.texcoords向量中保存所有的位置、法线和纹理坐标。

void loadModel() {
//OBJ文件由位置、法线、纹理坐标和面组成。面由任意数量的顶点组成,
//其中每个顶点通过索引指向一个位置、法线和/或纹理坐标
    tinyobj::attrib_t attrib;
    std::vector<tinyobj::shape_t> shapes;
    std::vector<tinyobj::material_t> materials;
    std::string warn, err;//err字符串包含错误,warn字符串包含加载文件时出现的警告,比如缺少材质定义。

    if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) {
        throw std::runtime_error(warn + err);
    }

//我们将把文件中的所有面合并成一个单一的模型,所以只需遍历所有的形状:

for (const auto& shape : shapes) {
//三角化功能已经确保了每个面有三个顶点,所以我们现在可以直接迭代顶点,并将它们直接转入我们的vertices向量
 for (const auto& index : shape.mesh.indices) {
        Vertex vertex{};

            vertices.push_back(vertex);
//为了简单起见,我们将假设每个顶点都是唯一的,因此指数是简单的自动递增。
//index变量的类型是tinyobj::index_t,它包含vertex_index、normal_index和texcoord_index成员。
    vertex.pos = {
        attrib.vertices[3 * index.vertex_index + 0],
        attrib.vertices[3 * index.vertex_index + 1],
        attrib.vertices[3 * index.vertex_index + 2]
    };

    vertex.texCoord = {
        attrib.texcoords[2 * index.texcoord_index + 0],
        attrib.texcoords[2 * index.texcoord_index + 1]
    };
//attrib.vertices数组是一个float值的数组,而不是像glm::vec3那样,所以你需要将索引乘以3。
//同样地,每个条目有两个纹理坐标组件。
//0、1和2的偏移量用来访问X、Y和Z分量,或者在纹理坐标的情况下访问U和V分量。
    vertex.color = {1.0f, 1.0f, 1.0f};

        indices.push_back(indices.size());
    }
    
}

}

 现在运行你的程序并启用优化功能(例如Visual Studio的Release模式和GCC的O3编译器标志)。这是必要的,因为否则加载模型会非常慢。你应该看到类似以下的情况:\

几何图形看起来很正确,但是纹理是怎么回事?OBJ格式假定了一个坐标系统,其中垂直坐标为0意味着图像的底部,然而我们已经将我们的图像以从上到下的方向上传到了Vulkan,其中0意味着图像的顶部。通过翻转纹理坐标的垂直部分来解决这个问题:

vertex.texCoord = {
    attrib.texcoords[2 * index.texcoord_index + 0],
    1.0f - attrib.texcoords[2 * index.texcoord_index + 1]
};

顶点去重

不幸的是,我们还没有真正利用好索引缓冲区的优势。vertices向量包含很多重复的顶点数据,因为很多顶点被包含在多个三角形中。我们应该只保留唯一的顶点,并在它们出现的时候使用索引缓冲区来重用它们。实现这一目标的直接方法是使用mapunordered_map来跟踪唯一顶点和各自的索引:

#include <unordered_map>

...

std::unordered_map<Vertex, uint32_t> uniqueVertices{};

for (const auto& shape : shapes) {
    for (const auto& index : shape.mesh.indices) {
        Vertex vertex{};

        ...
//每次我们从OBJ文件中读取一个顶点时,我们都会检查我们之前是否已经看到过一个位置和纹理坐标完全相同的顶点。
//如果没有,我们将其添加到vertices中,并将其索引存储在uniqueVertices容器中
        if (uniqueVertices.count(vertex) == 0) {
            uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
            vertices.push_back(vertex);
        }

        indices.push_back(uniqueVertices[vertex]);
    }
}

现在程序将无法编译,因为使用用户定义的类型,如我们的Vertex结构作为哈希表的键,需要我们实现两个函数:平等测试和哈希计算。前者可以通过覆盖Vertex结构中的==操作符来轻松实现:

bool operator==(const Vertex& other) const {
    return pos == other.pos && color == other.color && texCoord == other.texCoord;
}

//Vertex的哈希函数是通过指定std::hash的模板专业化实现的。
namespace std {
    template<> struct hash<Vertex> {
        size_t operator()(Vertex const& vertex) const {
            return ((hash<glm::vec3>()(vertex.pos) ^
                   (hash<glm::vec3>()(vertex.color) << 1)) >> 1) ^
                   (hash<glm::vec2>()(vertex.texCoord) << 1);
        }
    };
}

//哈希函数是在gtx文件夹中定义的,这意味着它在技术上仍然是GLM的一个实验性扩展。
//因此你需要定义GLM_ENABLE_EXPERIMENTAL来使用它。
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>

 现在你应该能够成功地编译和运行你的程序。如果你检查一下`顶点’的大小:

29 生成Mipmaps

我们的程序现在可以加载和渲染3D模型。在本章中,我们将增加一个功能,即mipmap生成。Mipmaps在游戏和渲染软件中被广泛使用,而Vulkan让我们可以完全控制它们的创建方式。

Mipmaps是预先计算好的、缩小了的图像版本。每张新图像的宽度和高度都是前一张的一半。Mipmaps被用作Level of DetailLOD的一种形式。离摄像机较远的物体将从较小的Mip图像中提取其纹理。使用较小的图像可以提高渲染速度,并避免诸如摩尔纹这样的伪影。一个关于mipmaps外观的例子:

创建图像

在Vulkan中,每个mip图像被存储在一个VkImage的不同mip级别中。0级是原始图像,而0级之后的mip级通常被称为mip chain。在创建VkImage时,可以指定mip级的数量。到现在为止,我们一直将这个值设置为1。我们需要根据图像的尺寸来计算mip层的数量。首先,添加一个类成员来存储这个数字:

...
uint32_t mipLevels;
VkImage textureImage;
...

//一旦我们在createTextureImage中加载了纹理,就可以找到mipLevels的值:

mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;

这计算了mip链的层数。max函数选择最大的维度。log2函数计算该维度可以被2除以多少倍。floor函数处理最大维度不是2的幂的情况。1被添加,以便原始图像有一个mip级别。

为了使用这个值,我们需要改变createImagecreateImageViewtransitionImageLayout函数,以允许我们指定mip层的数量。在这些函数中添加一个mipLevels参数:

void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
    ...
    imageInfo.mipLevels = mipLevels;
    ...
}
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels) {
    ...
    viewInfo.subresourceRange.levelCount = mipLevels;
    ...
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) {
    ...
    barrier.subresourceRange.levelCount = mipLevels;
    ...


//更新对这些函数的所有调用,以使用正确的值:

createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory);
...
createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
...
depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1);
...
textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels);
transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1);
...
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);

生成Mipmaps

我们的纹理图像现在有多个mip级别,但是暂存缓冲区只能用来填充mip级别0。其他级别仍然没有定义。为了填充这些层次,我们需要从我们拥有的单一层次中生成数据。我们将使用vkCmdBlitImage命令。这个命令执行复制、缩放和过滤操作。我们将多次调用这个命令,以blit数据到我们纹理图像的每一层。

createImage(texWidth, texHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, 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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/596683.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT国内镜像站

免费国内镜像推荐&#xff08;超稳定&#xff09; 下面为大家收集了目前国内最稳定流畅的ChatGPT镜像网站 目录 机器人 博弈ai 泰cool辣 道合顺 二狗问答 核桃 WOChat GPT中文站 TomChat 利用ChatGPTMindShow三分钟生成PPT ChatGPT国内镜像是啥 ChatGPT 镜像是指…

Xpdf 阅读器源码编译后查看文件中文乱码问题解决

经查阅&#xff0c;是由于缺少中文字体包&#xff1a; 第一步&#xff1a;下载所需要的字体包 下载https://dl.xpdfreader.com/xpdf-t1fonts.tar.gz 包含下载中文字体包&#xff08;非嵌入字体&#xff09; http://ftp.gnu.org/gnu/non-gnu/chinese-fonts-truetype/gkai00mp…

pytorch-简单回归问题-手写数字识别

pytorch-简单回归问题-手写数字识别 线性回归添加噪声简单例子分类问题引入-手写数字识别数据集 训练推导手写数字识别1加载数据集编写网络训练网络计算正确率 线性回归添加噪声 使用均方差损失函数来衡量损失 简单例子 通过最小化损失函数&#xff0c;求解出参数w b 下图表示…

封装的函数停发/启动CAN报文,以及报文接收检测,高可用

🍅 我是蚂蚁小兵,专注于车载诊断领域,尤其擅长于对CANoe工具的使用🍅 寻找组织 ,答疑解惑,摸鱼聊天,博客源码,点击加入👉【相亲相爱一家人】🍅 玩转CANoe,博客目录大全,点击跳转👉 📘前言 🍅 在测试过程中,我们可能需要可控的停/发某些报文,今天博主给…

chatgpt赋能python:Python主页面的SEO分析及优化建议

Python主页面的SEO分析及优化建议 Python是一种高级编程语言&#xff0c;广泛应用于人工智能、数据分析、Web开发等领域。Python官方网站是Python社区的一个重要门户&#xff0c;为全球学习Python的开发者提供了全面、权威、可靠的信息。在这篇文章中&#xff0c;我们将分析Py…

Text to image论文精读SeedSelect: 使用SeedSelect微调扩散模型It’s all about where you start

随着文本到图像扩散模型的发展&#xff0c;很多模型已经可以合成各种新的概念和场景。然而&#xff0c;它们仍然难以生成结构化、不常见的概念、组合图像。今年4月巴伊兰大学和OriginAI发表《It’s all about where you start: Text-to-image generation with seed selection》…

软件外包开发项目原型图工具

项目原型图工具有非常重要的作用&#xff0c;尤其是在APP项目开发中&#xff0c;对于整体需求的表达是必不可少的工具。相比于传统的文档需求&#xff0c;图形文字的表达可以更清楚的表达需求&#xff0c;让客户清楚的明白软件功能有哪些&#xff0c;最后的界面是怎样的&#x…

微信海量数据查询如何从1000ms降到100ms?

&#x1f449;腾小云导读 微信的多维指标监控平台&#xff0c;具备自定义维度、指标的监控能力&#xff0c;主要服务于用户自定义监控。作为框架级监控的补充&#xff0c;它承载着聚合前 45亿/min、4万亿/天的数据量。当前&#xff0c;针对数据层的查询请求也达到了峰值 40万/m…

RL - 强化学习 上置信界算法 (UCB) 和 汤普森采样算法 (TS)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/130983835 上置信界算法和汤普森采样算法是两种解决多臂老虎机问题的经典方法。多臂老虎机问题是一种探索与利用的平衡问题&#xff0c;…

Java easypoi 导出excel 并合并相关列

在项目开发中经常会使用到合并列&#xff0c;格式如下&#xff1a; 1.引入easypoi <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-annotation</artifactId></dependency><dependency><groupId>cn.aftertur…

设计模式详解之策略模式

作者&#xff1a;刘文慧 策略模式是一种应用广泛的行为型模式&#xff0c;核心思想是对算法进行封装&#xff0c;委派给不同对象来管理&#xff0c;本文将着眼于策略模式进行分享。 一、概述 我们在进行软件开发时要想实现可维护、可扩展&#xff0c;就需要尽量复用代码&#x…

chatgpt赋能python:Python什么情况下用类

Python什么情况下用类 在Python编程中&#xff0c;类是一种重要的数据结构&#xff0c;它是面向对象编程的核心。类可定义数据类型&#xff0c;并把数据与操作数据的函数组合在一起。因此&#xff0c;通过使用类&#xff0c;我们可以将数据、函数和其他方法组合在一起&#xf…

OLAP系列:四、clickhouse分布式表使用指南

一、背景 ClickHouse中最强大的表引擎当属MergeTree&#xff08;合并树&#xff09;引擎及该系列&#xff08;*MergeTree&#xff09;中的其他引擎&#xff0c;支持索引和分区&#xff0c;地位可以相当于innodb之于Mysql。 而且基于MergeTree&#xff0c;还衍生出了很多小弟&a…

【量化分析】绘制指标线EWM和MACD(1)

目录 一、说明 二、使用mplfinance的前提 2.1 mplfinance生态圈 2.1 安装mplfinance 三、mplfinance绘图 3.1 单变量图 3.2 将用户自己生成的曲线添加到 mplfinance plot() 四、显示EWM和MACD 一、说明 在做量化分析的时候&#xff0c;需要有能力计算种种曲线&#xff…

ShowMeBug 持续升级,提供高信效度支撑的技术招聘方案

去年年底&#xff0c;全新升级版的 ShowMeBug ——一款支持实战编程的技术能力评估平台&#xff0c;首次揭开了它神秘的面纱。 而近日&#xff0c;ShowMeBug 再次迎来一系列产品更新&#xff0c;它将以全新的面貌&#xff0c;提供高信效度支撑的技术招聘方案&#xff0c;持续助…

chatgpt赋能python:Python人脸登录:这项技术将颠覆传统的登录方式

Python人脸登录&#xff1a;这项技术将颠覆传统的登录方式 简介 在互联网时代&#xff0c;登录是每个人使用网站或软件的第一步&#xff0c;但是传统的用户名和密码登录已经不能满足用户的需求。不断的爆出各种账户泄露事件、密码猜测和密码被盗等问题&#xff0c;导致用户的…

cleanmymac要不要下载装机?好不好用

当我们收到一台崭新的mac电脑&#xff0c;第一步肯定是找到一款帮助我们管理电脑运行的“电脑管家”&#xff0c;监控内存运行、智能清理系统垃圾、清理Mac大文件旧文件、消除恶意软件、快速卸载更新软件、隐私保护、监控系统运行状况等。基本在上mac电脑防护一款CleanMyMac就够…

生成程序片段(程序依赖图PDG)

生成程序片段(程序依赖图PDG) 生成程序片段 标准方法是&#xff1a; 基于依赖性分析的切片。 使用程序依赖图表示依赖。 从中生成切片。 我们将专注于这种方法。但是&#xff0c;还有其他选择。 程序依赖图 The Program Dependence Graph (PDG) 表示数据和控制依赖项&#xf…

Servlet的常用Api—HttpServletResponse

Servlet的常用Api—HttpServletResponse &#x1f50e;核心方法setContentType && setCharacterEncodingsendRedirect关于Keep-Alive关于状态码 && Body &#x1f50e;结尾 &#x1f50e;核心方法 方法描述(void) setStatus(int sc)为该响应设置状态码(void) s…

2023年4月和5月随笔

1. 回头看 为了不耽误学系列更新&#xff0c;4月随笔合并到5月。 日更坚持了151天&#xff0c;精读完《SQL进阶教程》&#xff0c;学系统集成项目管理工程师&#xff08;中项&#xff09;系列更新完成。 4月和5月两月码字114991字&#xff0c;日均码字数1885字&#xff0c;累…