Vries的原教程里,对于模型载入,使用的是一种非常流行的模型加载库Assimp,可以方便的加载obj,fbx,3ds等常见的模型格式文件,在visual studio2015里,我照原教程进行了Assimp的配置,程序成功运行。在Qt中,把Assimp当作外库进行导入,试了很多种方法也不可以,万般无奈之下,我自写了一个基于Qt平台的简易模型导入程序,仅针对简易obj模型进行解析导入。
https://learnopengl-cn.github.io/03%20Model%20Loading/01%20Assimp/关于Assimp库的内容与常见模型只是请看Vries的原版教程)
Qt开发平台:5.8.0
编译器:Desktop Qt 5.8.0 MSVC2015_64bit
本程序源代码
百度网盘链接:https://pan.baidu.com/s/1w60NPe69ySSqzkQ6o0BeVA 密码:dmud
csdn下载连接: https://download.csdn.net/download/z136411501/10611540
1.4 比武台 1.5 坦克 1.6 基地
这里借用一下另外一个学习可编程管线OpenGL的网站里,对obj模型的解释,网站原链接如下:
http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-7-model-loading/
相对于原链接所介绍的简易OBJ模型,我增添了一些内容,主要是材质文件mtllib的使用,
在原作者的示例obj文件中,增添材质属性的使用,大概是这个模样:
# Blender3D v249 OBJ File: untitled.blend
# www.blender3d.org
mtllib cube.mtl
#
# object Arch41_039
#
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.748573 0.750412
vt 0.749279 0.501284
vt 0.999110 0.501077
vt 0.999455 0.750380
vt 0.250471 0.500702
vt 0.249682 0.749677
vt 0.001085 0.750380
vt 0.001517 0.499994
vt 0.499422 0.500239
vt 0.500149 0.750166
vt 0.748355 0.998230
vt 0.500193 0.998728
vt 0.498993 0.250415
vt 0.748953 0.250920
vn 0.000000 0.000000 -1.000000
vn -1.000000 -0.000000 -0.000000
vn -0.000000 -0.000000 1.000000
vn -0.000001 0.000000 1.000000
vn 1.000000 -0.000000 0.000000
vn 1.000000 0.000000 0.000001
vn 0.000000 1.000000 -0.000000
vn -0.000000 -1.000000 0.000000
usemtl Material_ray
s off
f 5/1/1 1/2/1 4/3/1
f 5/1/1 4/3/1 8/4/1
f 3/5/2 7/6/2 8/7/2
f 3/5/2 8/7/2 4/8/2
f 2/9/3 6/10/3 3/5/3
f 6/10/4 7/6/4 3/5/4
f 1/2/5 5/1/5 2/9/5
f 5/1/6 6/10/6 2/9/6
f 5/1/7 8/11/7 6/10/7
f 8/11/7 7/12/7 6/10/7
f 1/2/8 2/9/8 3/13/8
f 1/2/8 3/13/8 4/14/8
v vt vn都很好理解。f比较麻烦。例如f 8/11/7 7/12/7 6/10/7:
我们称这些数字为索引。若几个顶点共用同一个坐标,索引就显得很方便,文件中只需保存一个”v”,可以多次引用,节省了存储空间。
以下是原教程一个简易的obj载入函数,仅一个函数即可完成模型载入的关键步骤,不过读取的模型不包含材质文件,只有最基础的顶点位置参数,且读取文件时使用的是最基础的stdio.h标准输入输出流。
百度网盘链接:https://pan.baidu.com/s/15aQ3QbHtzzkt4zQXE6oF3g 密码:22el
这是一个简单的mtl材质文件:
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# 创建的文件:01.03.2017 19:24:15
newmtl 01___Default
Ns 58.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.1167 0.1167 0.1167
Ke 0.0000 0.0000 0.0000
map_Ka white.jpg
map_Kd white.jpg
newmtl 17___Default
Ns 1.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka Arch41_039_bark.jpg
map_Kd Arch41_039_bark.jpg
相对于教程(八)的简单框架,我进行了进一步的精简
这里仅解释model.h与model.cpp这个类,其余.h与.cpp文件,之前教程有过解析。
model.h
#ifndef MODEL_H
#define MODEL_H
#include
#include
#include
#include
#include
#include
#include
#include
class Object;
class Material;
class Model
{
public:
Model();
bool init(const QString& path); //模型文件的初始化设置,将.obj文件的路径传入参数列表
void draw(GLboolean isOpenLighting = GL_FALSE);//绘制模型,参数列表为是否打开Phong式光照计算
private:
bool loadOBJ(const QString& path);//整个程序的最关键函数!!! 参数为obj模型所在的路径
void bindBufferData();
QOpenGLFunctions_3_3_Core *core;
QVector
我借鉴了Assimp文件库的模型读取思想,将obj模型含有一个或多个object对象,一个object对象含有多个顶点,法向量,纹理坐标与材质信息,一个材质含有Phong式光照模型中的环境光,漫反射光,镜面光反射系数,镜面反射指数,环境纹理贴图与漫反射纹理贴图路径。
解释一下关键函数loadOBJ(const QString &path),流程就是使用QFile打开.obj模型文件所在的路径,while循环,一步一步扫描整个文件,遇到关键字,如object,usemtl,v,或者vn,等分开进行处理,处理完后,绑定数据运行即可。
bool Model::loadOBJ(const QString &path){
QFile file(path);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug()<<"OBJLOADER ERROR::FILE CAN NOT OPEN!";
file.close();
return false;
}
QTextStream in(&file);
QString line;//文件流
QVector positionIndices, uvIndices, normalIndices;
QVector temp_positions;
QVector temp_uvs;
QVector temp_normals;
QString temp_matName;//材质的名称
while(!in.atEnd()){
line = in.readLine();//读取一行,还有读取所有readAll();
QStringList list = line.split(" ", QString::SkipEmptyParts);
if(list.empty())
continue;
//qDebug() << list;
if(list[0] == "mtllib"){//处理材质文件,即处理图片纹理
/******* 1.1 处理材质文件路径 *********/
//":/models/res/models/huapen/penzi.obj"
QString mtl_path = path;
int tempIndex = path.lastIndexOf("/")+1;
mtl_path.remove(tempIndex, path.size()-tempIndex);//":/models/res/models/huapen/" 得到根目录路径,用来和材质文件名结合,生成正确路径
//mtl_path += list[1];//得到材料路径":/models/res/models/huapen/penzi.mtl"
// qDebug() << mtl_path;
/******* 1.2 读取材质文件,导入Material类中 *********/
QFile mtl_file(mtl_path+list[1]);//正确的材质文件路径
if(!mtl_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug()<<"OBJLOADER ERROR::MTL_FILE CAN NOT OPEN!";
mtl_file.close();
file.close();
return false;
}
QTextStream mtl_in(&mtl_file);
QString mtl_line;//读取材质文件流的一行
Material material;
QString matName;//材质的名称
while(!mtl_in.atEnd()){
mtl_line = mtl_in.readLine();//读取一行,还有读取所有readAll();
QStringList mtl_list = mtl_line.split(QRegExp("\\s+"), QString::SkipEmptyParts); //以“空格”与“\t”为识别符号,分开字符串
if(mtl_list.empty())
continue;
if(mtl_list[0] == "newmtl"){
matName = mtl_list[1];
map_materials[matName] = material;
}else if(mtl_list[0] == "Ns"){
double shininess = mtl_list[1].toDouble();
map_materials[matName].shininess = shininess;
}else if(mtl_list[0] == "Ka"){
double x = mtl_list[1].toDouble();
double y = mtl_list[2].toDouble();
double z = mtl_list[3].toDouble();
QVector3D Ka(x, y, z);
map_materials[matName].Ka = Ka;
}else if(mtl_list[0] == "Kd"){
double x = mtl_list[1].toDouble();
double y = mtl_list[2].toDouble();
double z = mtl_list[3].toDouble();
QVector3D Kd(x, y, z);
map_materials[matName].Kd = Kd;
}else if(mtl_list[0] == "Ks"){
double x = mtl_list[1].toDouble();
double y = mtl_list[2].toDouble();
double z = mtl_list[3].toDouble();
QVector3D Ks(x, y, z);
map_materials[matName].Ks = Ks;
}else if(mtl_list[0] == "map_Ka"){
ResourceManager::loadTexture(mtl_list[1], mtl_path+mtl_list[1]);
map_materials[matName].name_map_Ka = mtl_list[1];
}else if(mtl_list[0] == "map_Kd"){
ResourceManager::loadTexture(mtl_list[1], mtl_path+mtl_list[1]);
map_materials[matName].name_map_Kd = mtl_list[1];
}
}
/******* 1.2 读取材质文件,导入Material类中 *********/
}else if(list.size() > 1 && list[1] == "object"){//扫描寻找object
if(!objects.empty()){
for(int i=0; i < positionIndices.size(); i++ ){
//得到索引
int posIndex = positionIndices[i];
int uvIndex = uvIndices[i];
int norIndex = normalIndices[i];
//根据索引取值
QVector3D pos = temp_positions[posIndex-1];
objects.last().positions.push_back(pos);
QVector3D nor = temp_normals[norIndex-1];
objects.last().normals.push_back(nor);
if(uvIndex != 0){
QVector2D uv = temp_uvs[uvIndex-1];
objects.last().uvs.push_back(uv);
}
}
objects.last().matName = temp_matName;
positionIndices.clear();
uvIndices.clear();
normalIndices.clear();
}
Object object;
objects.push_back(object);//obj模型文件中的第一个object对象,因为一个obj模型可能还有多个object对象
}else if (list[0] == "v"){
double x = list[1].toDouble();
double y = list[2].toDouble();
double z = list[3].toDouble();
QVector3D pos;
pos.setX(x);
pos.setY(y);
pos.setZ(z);
temp_positions.push_back(pos);
}else if (list[0] == "vt"){
double x = list[1].toDouble();
double y = list[2].toDouble();
QVector2D uv;
uv.setX(x);
uv.setY(y);
temp_uvs.push_back(uv);
}else if (list[0] == "vn"){
double x = list[1].toDouble();
double y = list[2].toDouble();
double z = list[3].toDouble();
QVector3D nor;
nor.setX(x);
nor.setY(y);
nor.setZ(z);
temp_normals.push_back(nor);
}else if (list[0] == "usemtl"){
temp_matName = list[1];
//qDebug() << list[1];
}else if (list[0] == "f"){
if(list.size() > 4){
qDebug() << "OBJLOADER ERROR::THE LOADER ONLY SUPPORT THE TRIANGLES MESH!" << endl;
file.close();
return false;
}
for(int i = 1; i < 4; ++i){//读取处理 f字符后边的 三长串字符,如“f 2396/2442/2376 101/107/111 100/106/110”
QStringList slist = list[i].split("/");
int posIndex = slist[0].toInt();
int uvIndex = slist[1].toInt();
int norIndex = slist[2].toInt();
positionIndices.push_back(posIndex);
uvIndices.push_back(uvIndex);
normalIndices.push_back(norIndex);
//qDebug() <
因为这是一个简易的obj模型读取程序,一些细节处,我懒得处理,所以如果要载入一些新的obj模型,务必修改文件
的格式,使之与penzi.obj与penzi.mtl的格式对应。
比如,
另外,该Model类对象,不能拿出来直接单独使用,必须结合指定着色器“model”与ResourceManagersh类使用(资源文件中有,本来想把Model类写成一个独立的类来使用,想了半天,处理起来太麻烦了,没有好的思路,就算了)
看着该读取程序有很多限制,其实改起来也特别简单,学个思想就行。