在加载OBJ模型文件之前首先要对OBJ文件的内容有所了解,在3d模型网站https://free3d.com/3d-models/3d-printable-obj
随便找了一个模型
它的OBJ文件内容为:
# File exported by ZBrush version 4.4
# www.zbrush.com
#Vertex Count 20545
#UV Vertex Count 15953
#Face Count 20559
#Auto scale x=0.022208 y=0.022208 z=0.022208
#Auto offset x=-0.531189 y=-0.179415 z=0.083041
mtllib stickman2.mtl
usemtl defaultMat
v 14.23160078 -34.12469952 6.2026003
v 14.47159986 -34.17430379 6.32490063
v 14.4308002 -34.00520591 6.28000128
v 14.26249945 -33.91609966 6.17129972
...
vt 0.699 0.3513
vt 0.7029 0.352
vt 0.7295 0.3476
...
f 390/393 398/401 371/374 378/381
f 399/402 401/404 402/405 400/403
f 365/368 372/375 400/403 402/405
...
\n
注意:文件末尾会有一个\n字符。而3dsmax建模出的文件是\r\n..
其中最为关键的为开头为"v","vt","f"的几行数据,“v”代表了顶点数据,“vt”代表贴图坐标也及uv坐标数据,“f”代表了面数据,
其中f 1 2 3 4代表四边形顶点,f 1 2 3代表三角顶点,f 1/1 2/2 3/3 代表顶点索引/纹理索引,f 1//1 2//2 3//3 代表顶点索引//法线索引,f 1/1/1 2/2/2 3/3/3 代表顶点索引/纹理索引/法线索引.索引及代表这是第几个元素,如f 390/393 398/401 371/374 378/381
表示这个面有四个顶点分别是第390个顶点/393个uv ,第398个顶点/401个uv...。
先从最简单的读取单纯的顶点数据开始:
本文在Qt下使用OpenGL,选择使用QOpenGLExtraFunctions类采用OpenGL原生API进行编写。
读取数据部分:
bool ObjLoader::Load(QString fileName, QVector<float> &vPoints)
{
if (fileName.mid(fileName.lastIndexOf('.')) != ".obj"&&fileName.mid(fileName.lastIndexOf('.')) != ".OBJ")
{
qDebug() << "file is not a obj file!";
return false;
}
QFile objfile(fileName);
if (!objfile.open(QIODevice::ReadOnly))
{
qDebug() << "open" << fileName << "failed";
return false;
}
else
{
qDebug() << "open" << fileName << "success!";
}
QVector<float> vertextPoints, texturePoints;
QVector<std::tuple< int, int>> facesIndexs;
while (!objfile.atEnd())
{
QByteArray lineData = objfile.readLine();
lineData = lineData.remove(lineData.count() - 2, 2);
qDebug() << lineData;
if (lineData == "")
continue;
QList<QByteArray> strValues = lineData.split(' ');
strValues.removeAll("");
QString dataType = strValues.takeFirst();
if (dataType == "v")
{
for(int i=0;i<strValues.count();i++)
{
if (strValues[i] != "")
vertextPoints.push_back( strValues[i].toFloat() );
}
}
else if (dataType == "f")
{
if (strValues.size() == 4)
{
strValues.push_back(strValues.at(0));
strValues.push_back(strValues.at(2));
}
std::transform(strValues.begin(), strValues.end(), std::back_inserter(facesIndexs), [](QByteArray &str) {
QList<QByteArray> intStr = str.split('/');
return std::make_tuple(intStr.first().toInt(), intStr.last().toInt());
});
}
}
if (vertextPoints.count() != 0)
{
qDebug() <<"vertpoints: "<< vertextPoints.count();
}
else
{
qDebug() << "none vert points";
return false;
}
if (facesIndexs.count() != 0)
{
qDebug() << "facepoints: "<<facesIndexs.count();
}
else
{
qDebug() << "none faces";
return false;
}
for (auto &verFaceInfo:facesIndexs)
{
int vIndex = std::get<0>(verFaceInfo);
int vPointSizes = vertextPoints.count() / 3;
//将顶点坐标放入
vPoints << vertextPoints.at(vIndex * 3 - 3);
vPoints << vertextPoints.at(vIndex * 3 - 2);
vPoints << vertextPoints.at(vIndex * 3 - 1);
}
vertextPoints.clear();
facesIndexs.clear();
objfile.close();
return true;
}
此代码改自https://blog.csdn.net/wanghualin033/article/details/84642286
整体思路便是 读取文件的每一行,删除掉最后的一个字符'\r\n' 并判断头个字符进行操作。先不管uv坐标.
有一个细节是此OBJ文件里的面有三个点成面的也有四个点成面的,我统一存储为三个点及把四个数据变为六个数据(顶点0 1 2 3 变为顶点 0 1 2 3 0 2)便于绘制.
将数据存储于容器中。在绘制函数中进行调用。
先初始化OpenGL绘制的环境:
void MyWidget::initializeGL()
{
showNormal();
setGeometry(0, 0, 800, 600);
_program.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex, "vsrc.vert");
_program.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment, "fsrc.frag");
if (_program.link())
{
qDebug() << "link success!";
}
else
{
qDebug() << "link failed";
}
_objLoader.Load("C:/Users/Administrator/Desktop/obj/43n0m0fl66o0-stickman/stickman.OBJ", _vertPoints);
qDebug() << _vertPoints.count();
f = QOpenGLContext::currentContext()->extraFunctions();
f->glGenVertexArrays(1, &VAO);
f->glGenBuffers(1, &VBO);
f->glBindBuffer(GL_ARRAY_BUFFER, VBO);
f->glBufferData(GL_ARRAY_BUFFER, _vertPoints.size()*sizeof(float), &_vertPoints[0], GL_STATIC_DRAW);
f->glBindVertexArray(VAO);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0);
f->glEnableVertexAttribArray(0);
f->glBindVertexArray(0);
vMatrix.lookAt(_cameraLocation, QVector3D(0.0, 0.0, 0.0), QVector3D(0.0, 1.0, 0));
}
此处又遇到了一个问题, f->glBufferData(GL_ARRAY_BUFFER, _vertPoints.size()*sizeof(float), &_vertPoints[0], GL_STATIC_DRAW);其中的第二个参数 sizeof(vector)是不能返回一个vector的总数据大小的,是一个相当小的定值,以至于在想怎么把数据传进去时出了许多问题:int* arr = new int[10];
sizeof(arr)返回的是arr[0]的大小而不是指针代表的数组的大小。
而int arr[10] 这种方式sizeof(arr)返回的就是数组的大小。
而第二种方式的数组的大小需要在编译时就确认也就是无法跟着读取数据大小的变化而改变,想了许久之后才发现,原来是sizeof(vector)这个地方出了问题,只传了一点数据进去,也就谈不上能绘制出来了。。改成_vertPoints.size()*sizeof(float)就解决了问题。
最后进行绘制:
void MyWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_program.bind();
f->glBindVertexArray(VAO);
_program.setUniformValue("uPMatrix", pMatrix);
_program.setUniformValue("uVMatrix", vMatrix);
_program.setUniformValue("uMMatrix", mMatrix);
glDrawArrays(GL_TRIANGLES, 0, _vertPoints.size()/3);第三个参数是要绘制的顶点数
f->glBindVertexArray(0);
update();
}
加以最简单的shader:
fragshader
#version 330
out vec4 fragColor;
void main(void)
{
fragColor = vec4(1.0f,1.0f,1.0f,1.0f);
}
vertexshader
#version 330
uniform mat4 uPMatrix,uVMatrix,uMMatrix;
layout (location = 0) in vec3 aPosition;
void main(void)
{
gl_Position = uPMatrix * uVMatrix * uMMatrix * vec4(aPosition,1);
}
出来吧:皮卡丘
感觉有点奇怪。
换种方式绘制试试:
glDrawArrays(GL_POINTS, 0, _vertPoints.size() );
没想到就是加载个模型也是如此困难重重。。之后加上光影和uv再看看效果吧。