QT+OPenGL模型加载 - Assimp

news2024/11/18 20:01:02

QT+OPenGL模型加载 - Assimp

本篇完整工程见gitee:QtOpenGL 对应点的tag,由turbolove提供技术支持,您可以关注博主或者私信博主

模型加载

先来张图:

在这里插入图片描述

我们不大可能手工定义房子、汽车或者人形角色这种复杂形状所有的顶点、法线和纹理坐标。我们想要的是将这些模型导入到程序当中。

但是不同种类的文件格式中,它们之间通常没有一个通用的结构,因此我们将使用到模型加载库Assimp

Assimp

  • 一个非常流行的模型导入库
  • 将所有的模型数据加载到Assimp的通用数据结构中

这里不介绍对应的编译,如果你熟悉CMake的话编译起来是非常简单的,项目中提供的是使用vs2019编译的64位的动态库和lib。如果您不好运行,可以将对应的开发环境更换成跟我一致的环境。或者您自己编译对应的库文件。

介绍:

  • 场景(Scene): 所有场景/模型数据(材质和网格)都包含在场景对象中。场景对象也包含了场景根节点的引用。

  • 根节点(Root Node): 场景根节点可能包含子节点(和其他节点一样),他会有一系列指向场景对象中的mMeshes数组中存储的网格数据索引。

    ​ Scene下 的mMeshes数组存储了真正的Mesh对象,节点中的mMeshes数组保存的知识场景中网格数组的索引

  • Mesh对象: 一个Mesh对象本身包含了渲染所需要的所有相关数据,像是顶点位置,法向向量、纹理坐标、面和物体的材质。

  • 面(Face): 一个网格包含了多个面。面代表的是物体的渲染图元(三角形、方形、点)。一个面包含了组成图元的顶点的索引

  • 材料(Material):一个网格也包含了一个Material对象,他包含了一些函数能让我们获取物体的材质属性,比如颜色和纹理贴图(比如漫反射和镜面光贴图)。

封装Mesh:

一个网格至少需要:

  • 顶点数据:至少包含一个位置向量、一个法向量和一个纹理坐标向量
  • 材质数据:漫反射/镜面反射
  • 索引数据:

mesh.h

#ifndef QTOPENGL_MESH_H
#define QTOPENGL_MESH_H

#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>

struct Vertex
{
    QVector3D position;
    QVector3D normal;
    QVector2D tex_coords;
};

struct Texture
{
    QOpenGLTexture *texture;
    std::string path;
    std::string type;
};

class Mesh
{
public:
    Mesh(QOpenGLFunctions_4_5_Core *glFn, const QVector<Vertex> &vertices,
         const QVector<unsigned int> &indices, const QVector<Texture> &textures);
    ~Mesh();
    void draw(QOpenGLShaderProgram &shader);

protected:
    void setupMesh();

private:
    unsigned int VAO,VBO,EBO;
    QOpenGLFunctions_4_5_Core *gl_fn_;
    QVector<Vertex> vertices_;
    QVector<unsigned int> indices_;
    QVector<Texture> textures_;
};

#endif //QTOPENGL_MESH_H

mesh.cpp

#include "mesh.h"
Mesh::Mesh(QOpenGLFunctions_4_5_Core *glFn, const QVector<Vertex> &vertices, const QVector<unsigned int> &indices,
           const QVector<Texture> &textures)
{
    gl_fn_ = glFn;
    vertices_ = vertices;
    indices_ = indices;
    textures_ = textures;
    setupMesh();
}

Mesh::~Mesh()
{

}

void Mesh::setupMesh()
{
    //创建VBO和VAO对象,并赋予ID
    gl_fn_->glGenVertexArrays(1, &VAO);
    gl_fn_->glGenBuffers(1, &VBO);
    gl_fn_->glGenBuffers(1,&EBO);
    //绑定VBO和VAO对象
    gl_fn_->glBindVertexArray(VAO);
    gl_fn_->glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //为当前绑定到target的缓冲区对象创建一个新的数据存储。
    //如果data不是NULL,则使用来自此指针的数据初始化数据存储
    gl_fn_->glBufferData(GL_ARRAY_BUFFER, vertices_.size()*sizeof(Vertex),
                           &vertices_[0], GL_STATIC_DRAW);
    gl_fn_->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    gl_fn_->glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                           indices_.size() * sizeof(unsigned int),&indices_[0], GL_STATIC_DRAW);
    //告知显卡如何解析缓冲里的属性值
    gl_fn_->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    gl_fn_->glEnableVertexAttribArray(0);
    gl_fn_->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
                                    (void*)offsetof(Vertex, normal));

    gl_fn_->glEnableVertexAttribArray(1);
    gl_fn_->glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
                                    (void*)offsetof(Vertex, tex_coords));
    gl_fn_->glEnableVertexAttribArray(2);
}

void Mesh::draw(QOpenGLShaderProgram &shader)
{
    unsigned int diffuseNum = 1;
    unsigned int specularNum = 1;
    for(int i = 0; i < textures_.size(); i++)
    {
        std::string name = textures_[i].type;
        shader.setUniformValue(("material." + name).c_str(), i);
        textures_[i].texture->bind(i);
    }

    gl_fn_->glBindVertexArray(VAO);
    gl_fn_->glDrawArrays(GL_TRIANGLES,0,36);
}

封装Model:

我们需要通过Assimp加载模型,并且将其转换成多个网格数据

Assimp里的结构: 每个节点包含一组网格索引,每个索引指向场景对象中的特定网络。

model.h

#ifndef QTOPENGL_MODEL_H
#define QTOPENGL_MODEL_H

#include "assimp/scene.h"
#include "assimp/postprocess.h"
#include "mesh.h"

class Model
{
public:
    Model(QOpenGLFunctions_4_5_Core *glFn, const std::string &path);
    ~Model();

    void draw(QOpenGLShaderProgram &shader)
    {
        for(unsigned int i = 0; i < meshes_.size(); i++)
        {
            meshes_[i].draw(shader);
        }
    }

protected:
    void loadModel(const std::string &path);
    void processNode(aiNode *node, const aiScene *scene);
    Mesh processMesh(aiMesh *mesh, const aiScene *scene);
    QVector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, std::string typeName);
    QOpenGLTexture *textureFromFile(const std::string &file);

private:
    QOpenGLFunctions_4_5_Core *gl_fn_;
    QVector<Texture> texture_loaded_;
    QVector<Mesh>    meshes_;
    std::string      directory_;
};


#endif //QTOPENGL_MODEL_H

model.cpp

#include "model.h"
#include "assimp/Importer.hpp"
#include <iostream>
using namespace std;
Model::Model(QOpenGLFunctions_4_5_Core *glFn, const std::string &path)
{
    gl_fn_ = glFn;
    loadModel(path);
}

Model::~Model()
{

}

void Model::loadModel(const std::string &path)
{
    Assimp::Importer importer;
    const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs);
    if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
    {
        cout << "ERROR::ASSIMP::" << importer.GetErrorString() <<endl;
        return;
    }
    directory_ = path.substr(0, path.find_last_of('/'));
    processNode(scene->mRootNode, scene);
}

void Model::processNode(aiNode *node, const aiScene *scene)
{
    for(unsigned int i = 0; i < node->mNumMeshes; i++)
    {
        aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        meshes_.push_back(processMesh(mesh, scene));
    }
    for(unsigned int i = 0; i < node->mNumChildren; i++)
    {
        processNode(node->mChildren[i], scene);
    }
}

Mesh Model::processMesh(aiMesh *mesh, const aiScene *scene)
{
    QVector<Vertex> vertices;
    QVector<unsigned int> indices;
    QVector<Texture> textures;
    // 顶点数据
    for(unsigned int i = 0; i < mesh->mNumVertices; i++)
    {
        Vertex vertex;
        QVector3D vector;
        vector.setX(mesh->mVertices[i].x);
        vector.setY(mesh->mVertices[i].y);
        vector.setZ(mesh->mVertices[i].z);
        vertex.position = vector;

        vector.setX(mesh->mNormals[i].x);
        vector.setY(mesh->mNormals[i].y);
        vector.setZ(mesh->mNormals[i].z);
        vertex.normal = vector;

        if(mesh->mTextureCoords[0])
        {
            QVector2D tex;
            tex.setX(mesh->mTextureCoords[0][i].x);
            tex.setY(mesh->mTextureCoords[0][i].y);
            vertex.tex_coords = tex;
        }
        else
        {
            vertex.tex_coords = QVector2D(0.0, 0.0);
        }
        vertices.push_back(vertex);
    }
    // 索引数据
    for(unsigned int i = 0; i < mesh->mNumFaces; i++)
    {
        aiFace face = mesh->mFaces[i];
        for(unsigned int j = 0; j < face.mNumIndices; j++)
        {
            indices.push_back(face.mIndices[j]);
        }
    }
    // 纹理数据
    if(mesh->mMaterialIndex >= 0)
    {
        aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
        QVector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "diffuse");
        textures.append(diffuseMaps);
        QVector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "specular");
        textures.append(specularMaps);
    }
    return Mesh(gl_fn_, vertices, indices, textures);
}

QVector<Texture> Model::loadMaterialTextures(aiMaterial *mat, aiTextureType type, std::string typeName)
{
    QVector<Texture> textures;
    for(unsigned int i = 0; i < mat->GetTextureCount(type); i++)
    {
        aiString str;
        mat->GetTexture(type, i, &str);
        bool skip = false;
        for(int j = 0; j < texture_loaded_.size(); j++)
        {
            if(std::strcmp(texture_loaded_[j].path.data(), str.C_Str()) == 0)
            {
                textures.push_back(texture_loaded_[j]);
                skip = true;
                break;
            }
        }
        if(!skip)
        {
            Texture texture;
            texture.texture = textureFromFile(directory_ + "/" + str.C_Str());
            texture.type = typeName;
            texture.path = str.C_Str();
            textures.push_back(texture);
            texture_loaded_.push_back(texture);
        }
    }
    return textures;
}

QOpenGLTexture *Model::textureFromFile(const std::string &file)
{
    QOpenGLTexture *texture = new QOpenGLTexture(QImage(file.c_str()).mirrored());
    if(!texture->isCreated())
    {
        cout << "texture load failed!" << endl;
    }
    return texture;
}

之后我们可以使用QT加载模型,该部分代码已经上传到gitee,请在gitee中使用。

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

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

相关文章

【surfaceflinger源码分析】surface与surfaceflinger之间的关系

本篇文章带着以下问题继续分析surfaceflinger的源码: 什么是surface ? surface与图形数据之间是什么关系&#xff1f;surface和surfaceflinger之间是什么关系&#xff1f; Surface定义 先看看Surface这个类的定义&#xff0c;主要是定义了很多与GraphicBuffer相关的操作。 …

k8s(存储)数据卷与数据持久卷

为什么需要数据卷&#xff1f; 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行比较重要的应用程序带来一些问题问题1&#xff1a;当容器升级或者崩溃时&#xff0c;kubelet会重建容器&#xff0c;容器内文件会丢失问题2&#xff1a;一个Pod中运行多个容器并需要共…

创邻科技荣获人行旗下《金融电子化》年度大奖

近日&#xff0c;创邻科技收到由中国人民银行旗下《金融电子化》杂志社寄来的奖牌。 在《金融电子化》杂志社主办的第十三届金融科技应用创新奖中&#xff0c;创邻科技凭借“原生分布式图数据库Galaxybase解决方案”&#xff0c;从近400个参报案例中脱颖而出&#xff0c;荣获“…

Linux下zabbix_proxy实施部署

简介 zabbix proxy 可以代替 zabbix server 收集性能和可用性数据,然后把数据汇报给 zabbix server,并且在一定程度上分担了zabbix server 的压力. zabbix-agent可以指向多个proxy或者server zabbix-proxy不能指向多个server zabbix proxy 使用场景: 1&#xff0c;监控远程区…

【React全家桶】reac组件通信

&#x1f39e;️&#x1f39e;️&#x1f39e;️ 博主主页&#xff1a; 糖 &#xff0d;O&#xff0d; &#x1f449;&#x1f449;&#x1f449; react专栏&#xff1a;react全家桶 &#x1f339;&#x1f339;&#x1f339;希望各位博主多多支持&#xff01;&#xff01;&a…

自媒体市场规模由2015年的296亿元增涨至2021年的2500亿元

自媒体&#xff0c;又称“个人媒体”&#xff0c;是指大众化、自主化的传播者以图文、音频或视频内容等各类形式向公众发布信息内容。随着&#xff15;&#xff27;时代的来临和智能设备的性能逐渐提高&#xff0c;网络基础环境得到很大的提升&#xff0c;自媒体开始了新的发展…

QT的下载和安装

这里介绍的是QT官方方式下载&#xff0c;每次都让我很糊涂&#xff0c;就记载一下。先是下载QT online installerhttps://www.qt.io/download 在下方有Go Open Sourcehttps://www.qt.io/download-open-source 在下方有Download the Qt Online installerhttps://www.qt.io/downl…

C#(NET Core3.1 MVC)生成站点地图(sitemap.xml)

要做SEO的肯定绕不开站点地图sitemap.xml。这玩意其实不难我也在搞写下来备忘一下也给新人指指路。 我先把代码放出来备忘下 #region CreateSiteMapXml/// <summary>/// /// </summary>/// <returns></returns>[Route("/art/CreateSiteMapXml&qu…

Windows下安装启动nginx.exe报错

Windows下安装启动nginx.exe报错 前言&#xff1a; 问题1&#xff1a; 在安装使用nginx服务器时遇到最大的问题是windows下命令行输入start nginx后&#xff0c;或者双击nginx.exe&#xff0c;一闪而过&#xff0c;启动不了&#xff0c;怀疑是以下几个方面的问题&#xff0c;…

高效率工作之关于进入心流的方法

本文是向大家介绍高效率工作体验——心流&#xff01;心流也叫最优体验&#xff0c;是一位叫米哈里的心理学家在调查研究的基础上提出的概念。心流指的是一种将大脑注意力毫不费力地集中起来的状态&#xff0c;这种状态使人忘记时间的概念&#xff0c;忘掉自我&#xff0c;也忘…

商家必读!超店有数分享,tiktok达人营销变现如何更快一步?

近几年来&#xff0c;“粉丝经济”发展越来越迅猛&#xff0c;“网红带货”已经成为了一种营销的方式。这种方式让商家能基于达人的影响下迅速抢占自己的私域流量池。消费者会基于对达人的信任&#xff0c;购买达人推荐的产品。达人效应可以助力品牌走出营销困境。如果商家想要…

cmake 引入第三方库(头文件目录、库目录、库文件)

程序的编写需要用到头文件&#xff0c;程序的编译需要lib文件&#xff0c;程序的运行需要dll文件&#xff0c;因此cmake引入第三方库其实就是将include目录、lib目录、bin目录引入工程。 目录 1、find_package&#xff08;批量引入库文件和头文件&#xff09; 2、include_dir…

什么是禅道?禅道可以做什么?如何自动推送禅道消息?

什么是禅道&#xff1f;禅道是一款国产的开源项目管理软件。它的核心管理思想基于敏捷方法 scrum&#xff0c;内置了产品管理和项目管理&#xff0c;同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能&#xff0c;在一个软件中就可以将软件…

北京地铁口免费的大白鹅,你领了吗?

最近这几天&#xff0c;北京很多个地铁口周围涌现了许多只又白又肥的大鹅。 不过&#xff0c;此大鹅非铁锅之中的炖大鹅&#xff0c;乃是又呆又萌的大鹅玩偶。 而且&#xff0c;还是免费送的&#xff01; 所以每天晚上的下班时间&#xff0c;在一群掐着细长的大鹅脖子的地推人…

02:入门篇 - 漫谈 CTK

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 十万个为什么 五千个在哪里?七千个怎么办?十万个为什么?。。。生活中,有很多奥秘在等着我们去思考、揭示! 同样地,在使用 CTK 时,很多小伙伴一定也存在诸多疑问: 为什么 CTK Plugin Framework 要借…

计算机中有符号数的表示

文章目录二进制数制十进制二进制位模式基本数据类型无符号数的编码有符号数的编码原码&#xff08;Sign-Magnitude&#xff09;反码&#xff08;Ones Complement&#xff09;补码&#xff08;Twos Complement&#xff09;概念导读编码格式按权展开补码加法扩展一个数字的位表示…

基于STM32L431+Liteos的串口空闲中断加DMA循环接收

①MCU为STM32L431&#xff0c;使用串口2。 ②Liteos采用接管中断的方式。 STM32CubeMX配置生成串口代码&#xff1a; 串口DMA接收和发送配置区别是接收采用循环模式&#xff0c;发送为正常模式。 将生成的代码移植到liteos工程中&#xff0c;由于使用的接管中断的方式&#…

appium桌面版本以及一些自动化测试方方封装

标签&#xff08;空格分隔&#xff09;&#xff1a; appium_desktop 一 appium_desktop_v1.2.6 1.appium_desktop在github上最新下载地址&#xff1a;appium桌面版本地址 2.一路傻瓜式安装就好了&#xff1a; 3.然后点击搜索按钮&#xff08;右上角&#xff09; 三 inspector …

基于国产龙芯 CPU 的气井工业网关研究与设计(二)

3.1 系统硬件的总体设计 从硬件架构上&#xff0c;该 RTU 主要包括三大部分的设计&#xff1a; &#xff08;1&#xff09;外围电路设计&#xff1a;电源电路设计、RTC 电路设计和 EEPROM 电路设计。 &#xff08;2&#xff09;RTU 本体 I/O 端口设计&#xff1a;A/I 模拟量输入…

使用RedisDesktopManager无法连接Redis服务器问题

问题&#xff1a;解决办法问题1&#xff1a;redis的配置文件问题进入redis的目录 [rootredis ~]# cd /opt/apps/redis/ opt apps 是自己创建的文件夹&#xff08;用于安装redis&#xff09; 使用vim进入配置文件的修改 [rootredis redis]# vim redis.conf使用vim编辑器修改bi…