最近终于决定要在自己的demo中加入模型了!本次选择的是开源库Assimp,之前一直嫌麻烦没有去落实这件事,但实际上,assimp的配置意外的没有我想象中的那么麻烦。
参考这篇文章基本上可以配置成功https://learnopengl.com/Model-Loading/Assimp,它所提到的坑我都遇到了。
这里是Assimp下载地址:http://assimp.sourceforge.net/main_downloads.html。
下载源码后,需要使用cmake进行编译,在上方选择源码位置,和build工程的位置,如果没有什么特殊的配置需求的话,直接按顺序依次点1,2,3的按钮即可。Configure的时候,可能会遇到一个dx的error,按照引用文章的提示,直接在http://www.microsoft.com/en-us/download/details.aspx?id=6812下载相关组件,安装中还可能遇到s1023的错误代码,此处可在命令行输入(卸载vs的一些组件):
MsiExec.exe /passive /X{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5}
MsiExec.exe /passive /X{1D8E6291-B0D5-35EC-8441-6616F567A0F7}
这里特别需要注意的是,cmake的generator选择,需要和最终调用assimp的编译器匹配。比如,我的Qt版本为:5.11.2 MSVC2017 64bit。
也就是我使用了vs的msvc作为c++的编译器,并且是2017版本,64位的。那么相应的,如cmake界面右下角红色框所标出的,generator选择Visual Studio 15 2017 Win64。
编译完成之后,打开对应工程,切到release模式,然后点编译。之后,可以在code/Release得到我们所需的lib和dll(用不同的编译器得到的名称会不太一样):
之后,我们在Qt的pro文件处,新建include文件夹,把源码中的include内容复制过去;新建lib文件夹,把assimpxxx.lib放到该文件夹下。最后,把assimpxxx.dll放到生成的exe所在的文件夹下。(如果设定了shadow build和构建目录,那么就在这一目录下)。
最后,在.pro按如下写好lib和include的连接:
INCLUDEPATH += include
LIBS += -L$$PWD/lib/ \
-lassimp-vc140-mt
配置完成后,可以开始导入代码的编写。Assimp仅仅是实现了导入相关的功能,这些导入的数据实际上要如何使用,是需要额外实现的。
由于我的材质是动态加载和替换的,所以此处只导入了顶点、法线和纹理坐标:
(5.15更新,之前的代码只能加载一个mesh,改进了一下,不过每个mesh只支持一个贴图)
#ifndef GEOMETRYENGINE_H
#define GEOMETRYENGINE_H
#include
#include
#include
#include
#include
#include
#include
using namespace std;
struct VertexData
{
QVector3D position;
QVector3D tangent;
QVector3D normal;
QVector2D texcoord;
};
struct MeshBuffer
{
QOpenGLBuffer arrayBuf;
QOpenGLBuffer indexBuf;
int vertexNum;
int indiceNum;
MeshBuffer() : indexBuf(QOpenGLBuffer::IndexBuffer)
{
arrayBuf.create();
indexBuf.create();
}
~MeshBuffer()
{
arrayBuf.destroy();
indexBuf.destroy();
}
void Init(VertexData* vertex, int num)
{
vertexNum = num;
arrayBuf.bind();
arrayBuf.allocate(vertex, vertexNum * static_cast(sizeof(VertexData)));
}
void Init(GLushort* indice, int num)
{
indiceNum = num;
indexBuf.bind();
indexBuf.allocate(indice, indiceNum * static_cast(sizeof(GLushort)));
}
void bind()
{
arrayBuf.bind();
indexBuf.bind();
}
};
struct Mesh
{
string name;
MeshBuffer* buffer = nullptr;
QOpenGLTexture* albedo;
};
class Model
{
private:
vector vecMesh;
public:
void Push(Mesh* mesh)
{
vecMesh.emplace_back(mesh);
}
MeshBuffer* GetMeshBuffer(size_t idx) { return vecMesh[idx]->buffer;}
Mesh* GetMesh(size_t idx) { return vecMesh[idx];}
size_t Count() { return vecMesh.size();}
};
class GeometryEngine
{
public:
GeometryEngine();
virtual ~GeometryEngine();
bool loadObj(string path, Model*& pModel);
void drawObj(string path,QOpenGLShaderProgram* program,bool bTess = false);
void drawObj(MeshBuffer* meshBuffer, QOpenGLShaderProgram* program,bool bTess = false);
void CalTangent(VertexData& vertex0, VertexData& vertex1, VertexData& vertex2);
private:
void processNode(const string& path, aiNode *node, const aiScene *scene);
void processMesh(vector& vertices, vector& indices, QOpenGLTexture*& albedo, aiMesh *mesh, const aiScene *scen);
map mapModel;
};
#endif // GEOMETRYENGINE_H
#include "geometryengine.h"
#include "resourceinfo.h"
#include
#include
#include
GeometryEngine::GeometryEngine()
{
}
GeometryEngine::~GeometryEngine()
{
}
bool GeometryEngine::loadObj(string path, Model*& pModel)
{
if(mapModel.find(path) != mapModel.end())
{
return true;
}
Assimp::Importer import;
const aiScene *scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
{
qDebug() << "ERROR::ASSIMP::" << import.GetErrorString() ;
return false;
}
string directory = path.substr(0, path.find_last_of('/'));
processNode(path, scene->mRootNode, scene);
pModel = &mapModel[path];
return true;
}
void GeometryEngine::drawObj(MeshBuffer* meshBuffer, QOpenGLShaderProgram* program,bool bTess)
{
meshBuffer->bind();
auto gl = QOpenGLContext::currentContext()->extraFunctions();
int offset = 0;
int vertexLocation = program->attributeLocation("a_position");
program->enableAttributeArray(vertexLocation);
program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
offset += sizeof(QVector3D);
int tangentLocation = program->attributeLocation("a_tangent");
program->enableAttributeArray(tangentLocation);
program->setAttributeBuffer(tangentLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
offset += sizeof(QVector3D);
int normalLocation = program->attributeLocation("a_normal");
program->enableAttributeArray(normalLocation);
program->setAttributeBuffer(normalLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
offset += sizeof(QVector3D);
int texcoordLocation = program->attributeLocation("a_texcoord");
program->enableAttributeArray(texcoordLocation);
program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));
if(bTess)
{
gl->glPatchParameteri(GL_PATCH_VERTICES, 3);
gl->glDrawElements(GL_PATCHES, meshBuffer->indiceNum, GL_UNSIGNED_SHORT, nullptr);
}
else
{
gl->glDrawElements(GL_TRIANGLES, meshBuffer->indiceNum, GL_UNSIGNED_SHORT, nullptr);
}
}
void GeometryEngine::drawObj(string path,QOpenGLShaderProgram* program,bool bTess)
{
Model* pModel;
if(mapModel.find(path) == mapModel.end())
{
if(!loadObj(path, pModel))
{
return;
}
}
auto vecMesh = mapModel[path];
for(size_t i = 0;i < vecMesh.Count();i++)
{
auto meshBuffer = vecMesh.GetMeshBuffer(i);
drawObj(meshBuffer,program, bTess);
}
}
void GeometryEngine::processMesh(vector& vertices, vector& indices, QOpenGLTexture*& albedo, aiMesh *mesh, const aiScene *scene)
{
for(unsigned int i = 0; i < mesh->mNumVertices; i++)
{
VertexData vertex;
if(mesh->mVertices)
{
vertex.position = QVector3D(mesh->mVertices[i].x,mesh->mVertices[i].y,mesh->mVertices[i].z);
}
if(mesh->mTextureCoords[0])
{
vertex.texcoord = QVector2D(mesh->mTextureCoords[0][i].x,mesh->mTextureCoords[0][i].y);
}
if(mesh->mNormals)
{
vertex.normal = QVector3D(mesh->mNormals[i].x,mesh->mNormals[i].y,mesh->mNormals[i].z);
vertex.normal.normalized();
}
if(mesh->mTangents)
{
vertex.tangent = QVector3D(mesh->mTangents[i].x,mesh->mTangents[i].y,mesh->mTangents[i].z);
}
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(static_cast(face.mIndices[j]));
}
}
aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
for(unsigned int i = 0; i < material->GetTextureCount(aiTextureType_DIFFUSE); i++)
{
aiString str;
material->GetTexture(aiTextureType_DIFFUSE, i, &str);
albedo = CResourceInfo::Inst()->CreateTexture(str.C_Str());
}
}
void GeometryEngine::processNode(const string& path, aiNode *node, const aiScene *scene)
{
// process all the node's meshes (if any)
for(unsigned int i = 0; i < node->mNumMeshes; i++)
{
vector vertices;
vector indices;
QOpenGLTexture* albedo = nullptr;
aiMesh *aimesh = scene->mMeshes[node->mMeshes[i]];
processMesh(vertices, indices, albedo, aimesh, scene);
MeshBuffer* meshBuffer = new MeshBuffer();
for(size_t i = 0;i < indices.size() / 3; i++)
{
CalTangent(vertices[indices[3 * i]],vertices[indices[3 * i + 1]],vertices[indices[3 * i + 2]]);
}
meshBuffer->Init(vertices.data(),static_cast(vertices.size()));
meshBuffer->Init(indices.data(),static_cast(indices.size()));
Mesh* mesh = new Mesh();
mesh->name = node->mName.C_Str();
mesh->buffer = meshBuffer;
mesh->albedo = albedo;
mapModel[path].Push(mesh);
}
// then do the same for each of its children
for(unsigned int i = 0; i < node->mNumChildren; i++)
{
processNode(path, node->mChildren[i], scene);
}
}
void GeometryEngine::CalTangent(VertexData& vertex0, VertexData& vertex1, VertexData& vertex2)
{
float u0 = vertex0.texcoord.x();
float v0 = vertex0.texcoord.y();
float u1 = vertex1.texcoord.x();
float v1 = vertex1.texcoord.y();
float u2 = vertex2.texcoord.x();
float v2 = vertex2.texcoord.y();
float t1 = u1 - u0;
float b1 = v1 - v0;
float t2 = u2 - u0;
float b2 = v2 - v0;
QVector3D e0 = vertex1.position - vertex0.position;
QVector3D e1 = vertex2.position - vertex0.position;
float k = t1 * b2 - b1 * t2;
QVector3D tangent;
tangent = k * QVector3D(b2 * e0.x() - b1 * e1.x(),b2 * e0.y() - b1 * e1.y(),b2 * e0.z() - b1 * e1.z());
QVector vertexArr = { &vertex0, &vertex1, &vertex2};
QVector adjoinPlane;
adjoinPlane.resize(vertexArr.size());
for(int i = 0;i < vertexArr.size();i++)
{
adjoinPlane[i]++;
float ratio = 1.0f / adjoinPlane[i];
vertexArr[i]->tangent = vertexArr[i]->tangent * (1 - ratio) + tangent * ratio;
vertexArr[i]->tangent.normalize();
}
}
接下来,我们可以试着导入一个简单的模型。
在建模软件maya中,拖出一个简单的甜甜圈,并勾选使用三角面显示:
给这个甜甜圈绑定一个uv,此处uv用了一个圆柱形投影,将其水平扫描设为360度,高度稍微调高一些。这里只是大致得到一个还算过得去的uv纹理。
选中当前对象,然后在文件菜单选择导出当前选择,然后根据自己的需求导出:
然后就可以把这个"甜甜圈”导入自己的项目中了:
试着加一下材质,除了有一块因为瞎搞的uv导致不太对的地方,其它看起来都没什么问题了。