OpenGL(Open Graphics Library)是一种用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序接口。OpenGL的实现利用了图形加速硬件,这些实现一般由显示设备厂商提供。但真正使用时,一般采用基于gl的第三方库,用于在程序的运行期判断当前硬件是否支持相关的扩展,防止程序崩溃甚至造成硬件损坏。目前我了解到的第三方库就有glad、glew、glfw、freeglut等,下图反映了它们之间的关系。
此外在安装Qt后,Qt本身也会对gl库进行一定的封装,即qopengl.h和QOpenGLFunctions。Qt是目前做UI非常好的一款工具,再结合它给的例子,比如cube、hellogl2等,可以仿照着做出很多比较好的人机交互界面。
对于三维模型,常用的软件有solid work,AutoCAD等,可以采用这些软件画出后导出stl模型文件。 STL 文件有2 种类型:文本文件(ASCII格式)和二进制文件(BINARY)。其中文本格式的可以用notepad++打开,它里面包含多个三角形面片的定义组成,每个三角形的定义包括三角形各个定点的三维坐标及三角形面片的法矢量,三角形顶点的排列顺序遵循右手法则。因此对于用opengl渲染stl文件,我们首先得加载它,https://free3d.com也可下到很多模型文件。
使用Qt自带的库,窗口可继承public QOpenGLWidget, protected QOpenGLFunctions(提供了一套OpenGL ES2.0 API,免去开发人员手动解析这些函数符号)。不过关于OpenGL ES的写法,我还是有点迷糊,没有看到比较系统性的介绍,一般给的例子都是采用现成的模板,我这里采用的是Qt例子中的方法。关于stl文本文件的加载,网上就有太多方法。
bool QObjLoad::load(QString fileName, QVector& 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 vertextPoints, texturePoints, normalPoints;
QVector facesIndexs;
while (!objFile.atEnd())
{
QByteArray lineData = objFile.readLine();
QList strValues = lineData.trimmed().split(' ');
QString dataType = strValues.takeFirst();
if (dataType == "v")
{
std::transform(strValues.begin(), strValues.end(), std::back_inserter(vertextPoints), [](QByteArray& str) {
return str.toFloat();
});
}
else if (dataType == "vt")
{
std::transform(strValues.begin(), strValues.end(), std::back_inserter(texturePoints), [](QByteArray& str) {
return str.toFloat();
});
}
else if (dataType == "vn")
{
std::transform(strValues.begin(), strValues.end(), std::back_inserter(normalPoints), [](QByteArray& str) {
return str.toFloat();
});
}
else if (dataType == "f")
{
facesIndexs << strValues.at(0).toInt() << strValues.at(1).toInt() << strValues.at(2).toInt();
}
}
objFile.close();
for (auto& verFaceInfo : facesIndexs)
{
int vIndex = verFaceInfo - 1;
vPoints << vertextPoints.at(vIndex * 3);
vPoints << vertextPoints.at(vIndex * 3 + 1);
vPoints << vertextPoints.at(vIndex * 3 + 2);
}
vertextPoints.clear();
texturePoints.clear();
normalPoints.clear();
facesIndexs.clear();
return true;
}
这里主要是为了获取顶点坐标,接着就是在Qt的窗口上显示了,要重新实现三个函数void initializeGL() override、void paintGL() override和void resizeGL(int width, int height) override。
void GLWindow::initializeGL()
{
initializeOpenGLFunctions();
glClearColor(0, 0, 0, 0);
m_program = new QOpenGLShaderProgram;
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSourceCore);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSourceCore);
if (m_program->link())
{
qDebug() << "link success!";
}
else
{
qDebug() << "link failed";
}
m_program->bindAttributeLocation("vertex", 0);
m_program->bindAttributeLocation("normal", 1);
m_program->link();
m_program->bind();
m_projMatrixLoc = m_program->uniformLocation("projMatrix");
m_mvMatrixLoc = m_program->uniformLocation("mvMatrix");
m_normalMatrixLoc = m_program->uniformLocation("normalMatrix");
m_lightPosLoc = m_program->uniformLocation("lightPos");
// Setup our vertex buffer object.
m_Vbo.create();
m_Vbo.bind();
m_Vbo.allocate(m_vPoints.data(), m_vPoints.size() * sizeof(float));
// Store the vertex attribute bindings for the program.
setupVertexAttribs();
// Light position is fixed.
m_program->setUniformValue(m_lightPosLoc, QVector3D(10, 10, 10));
m_program->release();
}
void GLWindow::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
m_world.setToIdentity();
m_world.rotate(m_xRot / 16.0f, 1, 0, 0);
m_world.rotate(m_yRot / 16.0f, 0, 1, 0);
m_world.rotate(m_zRot / 16.0f, 0, 0, 1);
m_camera.setToIdentity();
m_camera.lookAt(m_camera_pos, QVector3D(0, 0, 0), QVector3D(1, 0, 0));
m_program->bind();
m_program->setUniformValue(m_projMatrixLoc, m_proj);
m_program->setUniformValue(m_mvMatrixLoc, m_camera * m_world);
QMatrix3x3 normalMatrix = m_world.normalMatrix();
m_program->setUniformValue(m_normalMatrixLoc, normalMatrix);
glDrawArrays(GL_TRIANGLES, 0, m_vPoints.size()/3);
m_program->release();
}
void GLWindow::resizeGL(int w, int h)
{
m_proj.setToIdentity();
m_proj.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
}
最后通过lookAt设置视角去显示。我把视角进行了可调,这样就能从不同视角来查看,效果如下。
freeglut是GLUT的开源替代库,里面很多函数,而且大多数人比较熟悉,所以用这个可能更受青睐。freeglut可以在http://freeglut.sourceforge.net下到,下载后通过cmake生成vs工程,然后就可以使用freeglut.lib和freeglut.dll库,同时需将GL目录下的头文件也拷到相应的目录下。
还是一样的,加载stl文件并显示。不过这种方法对于鼠标或者键盘的操作等的处理就非常麻烦。而且不知道为啥,我显示的颜色和角度有点奇怪。关于灯光位置,视角,渲染,各种矩阵变换,还是有很多有待弄清楚的。
typedef struct Vertex
{
//定义三维图形的
//用于face结构体中
float x, y, z;
} Vertex;
typedef struct Face
{
//多边形(三角形)面的结构体
Face(void) : vert_number(0), verts(0) {};
int vert_number; //记录顶点的个数
Vertex** verts; //这是一个面的所有 顶点数组(含有坐标)
float normal[3]; //记录点的法向量,分别是x,y,z三个方向
//注意点的法向量通过顶点的信息计算得到!
//对于obj模型如果我们已经得到了法线的信息
//那么就直接拿来用就好!
} Face;
typedef struct myMesh
{
//自定义mesh的结构体
myMesh(void) : vert_number(0), verts(0), face_number(0), faces(0) {};
//自定义构造器
int vert_number; //总的顶点个数
Vertex* verts; //定点数组
int face_number; //面的数目
Face* faces;
vectorpoint;
} myMesh;
void GLWin::drawObj()
{
if (m_mesh->face_number == 0)
return;
qDebug() << m_mesh->face_number;
float bbox[2][3] = { { 1.0E30F, 1.0E30F, 1.0E30F }, { -1.0E30F, -1.0E30F, -1.0E30F } };
for (int i = 0; i < m_mesh->vert_number; i++)
{
Vertex& vert = m_mesh->verts[i];
if (vert.x < bbox[0][0])
bbox[0][0] = vert.x;
else if (vert.x > bbox[1][0])
bbox[1][0] = vert.x;
if (vert.y < bbox[0][1])
bbox[0][1] = vert.y;
else if (vert.y > bbox[1][1])
bbox[1][1] = vert.y;
if (vert.z < bbox[0][2])
bbox[0][2] = vert.z;
else if (vert.z > bbox[1][2])
bbox[1][2] = vert.z;
}
// Setup initial viewing scale
float dx = bbox[1][0] - bbox[0][0];
float dy = bbox[1][1] - bbox[0][1];
float dz = bbox[1][2] - bbox[0][2];
scale = 2.0 / sqrt(dx * dx + dy * dy + dz * dz);
glPushMatrix();
glColor3f(0.5, 0.5, 0.5);
glScalef(scale, scale, scale);
glRotatef(rotation[0], 1.0, 0.0, 0.0);
glRotatef(rotation[1], 0.0, 1.0, 0.0);
glRotatef(rotation[2], 0.0, 0.0, 1.0);
for (int i = 0; i < m_mesh->face_number; i++)
{
Face& face = m_mesh->faces[i];
glBegin(GL_TRIANGLES);
glNormal3fv(face.normal);
for (int j = 0; j < face.vert_number; j++)
{
Vertex* vert = face.verts[j];
glVertex3f(vert->x, vert->y, vert->z);
}
glEnd();
}
glPopMatrix();
}
void GLWin::initializeGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.0, 0.0, 0.0, 0.0);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glEnable(GL_NORMALIZE);
glEnable(GL_DEPTH_TEST);
glutInitWindowSize(600, 600);
}
void GLWin::paintGL()
{
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
gluLookAt(1, 1, 1, 0, 0, 0, 0, 1, 0);
//draw axis
glPushMatrix();
glLineWidth(5);
glBegin(GL_LINES);
glColor3f(1, 0, 0);
glVertex3f(0.0f, 0, 0);
glVertex3f(10, 0, 0);
glEnd();
glBegin(GL_LINES);
glColor3f(0, 1, 0);
glVertex3f(0.0f, 0, 0);
glVertex3f(0, 10, 0);
glEnd();
glBegin(GL_LINES);
glColor3f(0, 0, 1);
glVertex3f(0, 0, 0);
glVertex3f(0, 0, 10);
glEnd();
glPopMatrix();
CLoadStlObj stlobj;
m_mesh = stlobj.ReaderOBj("head.obj");
drawObj();
// 设置光照信息
static GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
static GLfloat light0_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
设置满散射
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
glEnable(GL_LIGHT0);
static GLfloat light1_diffuse[] = { 0.5, 0.5, 0.5, 1.0 };
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glEnable(GL_LIGHT1);
glEnable(GL_LIGHTING);
}
void GLWin::resizeGL(int width, int height)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, (GLfloat)width / (GLfloat)height, 0.1, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
方法1的工程代码可以在https://github.com/WelinLee/QtOpenGLDemo中找到,enjoy!