项目中使用OpenGL对Mesh进行Debug,实时监测mesh的变化,好在debug的途中一步一步分析哪里改错了。记录一下目前学到的OpenGL知识,后续可以方便查阅。
sudo apt-get install build-essential
sudo apt-get install libgl1-mesa-dev
sudo apt-get install libglew-dev libsdl2-dev libsdl2-image-dev libglm-dev libfreetype6-dev
sudo apt-get install libglfw3-dev libglfw3
sudo apt-get install libglu1-mesa-dev
sudo apt-get install freeglut3-dev
find_package(OpenGL REQUIRED)
include_directories(${OPENGL_INCLUDE_DIR})
find_package(GLUT REQUIRED)
include_directories(${GLUT_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} ${OPENGL_LIBRARIES} ${GLUT_LIBRARY})
# QT modules
QT += opengl
LIBS += -lGL -lglut -lGLU
https://www.freesion.com/article/2688454116/
https://docs.microsoft.com/zh-cn/windows/win32/opengl/gluproject?redirectedfrom=MSDN
https://blog.csdn.net/hjq376247328/article/details/49387011
目前我使用时发现的OpenGL初始化窗口时传入的参数必须是main函数的传入参数,其余情况目前还没有碰到;例子中使用的VCG去做Mesh的加载,这里就不贴代码了,加载mesh的方法很多,只要能拿到点云,face数组等数据结构就可以用OpenGL显示;
渲染时,先写的在最上层渲染。
//从代码上讲,我们使用glVertex3f设定的坐标值实际上是局部坐标系下的值
/*
* 1.世界系
//这个阶段的变换,主要包括 glTranslae (平移)、
glScale (缩放)、
glRotate (旋转)。
这些变换针对向极坐标系原点操作,将相机原点移动到世界系下的不同位置,应该是对世界系中的模型进行操作的,而不是动相机
在经过这样一系列变换之后,
局部坐标系上的某个点P0(x0, y0, z0, 1),会被摆放到世界坐标系上的P1(x1, y1, z1, 1)点
*/
/*
*
2. 世界系->相机系(正交/摄影)
//gluLookAt(eye, center, up) 将摄像机摆放在了世界系下eye的位置,指定相机上方up,视线指向center。
*/
/*
*
3. 相机系->投影系(正交/摄影)
正交投影在OpenGL中使用 glOrtho(left, right, bottom, top, near, far); 来进行设置。
一般设定透视投影的方法有两种:
3.1、gluPerspective(theta, aspect, near, far)
3.2、glFrustum(left, right, bottom, top, near, far)
*/
/*
4.规范化
将原来的观察体,映射到规范化立方体的过程,就是规范化
相当于将模型投影之后得到的平面所放到单位立方体中
*/
/*
5.设置视口
glViewport(x,y, width, height),
调用这个方法之后,会将照片的左下角摆放在(x,y)点,并将其缩放,
使得原来单位大小的图片,放大到宽为 width,高为 height。
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include<algorithm>
#include <GL/glew.h>
#include <GL/freeglut.h>
class MyOpenGLShowMesh
{
public:
MyMesh* myMeshData;
vcg::Point3f modeAvePose; //use to translate
MyOpenGLShowMesh()
{
myMeshData = new MyMesh();
}
bool LoadMesh(string fileName);
bool InitGLWindow(int argc, char** argv);
bool InitGLMesh();
void UpdateMesh(string fileName);
void GLMainLoop();
void UpdateGLMesh();
};
//global params for camera and world pos
//定义,分配内存,以后DebugShowMesh每一个对象(实例)的创建都不再分配内存
float xRotate = 0.0f;
float yRotate= 0.0f;
float zRotate= 0.0f;
float scale = 1.0f;
bool showFace = true;
bool showWire = true;
bool showPoint = false;
float translateX = 0.0f;
float translateY = 0.0f;
float translateZ = 0.0f;
GLuint showFaceList, showWireList, showPointList;
GLuint showStrengthList;
//global
MyMesh mesh;
vcg::Point3f avePos(0,0,0);
//reshape window
void myReshape(GLint w, GLint h)
{
glViewport(0, 0, static_cast<GLsizei>(w), static_cast<GLsizei>(h));
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//正交投影
// if (w > h)
// glOrtho(-static_cast<GLdouble>(w) / h, static_cast<GLdouble>(w) / h, -1.0, 1.0, -1.0, 1.0);
// else
// glOrtho(-1.0, 1.0, -static_cast<GLdouble>(h) / w, static_cast<GLdouble>(h) / w, -1.0, 1.0);
//摄影投影 LookAt放在reshape中
gluPerspective(60.0f,GLfloat(w) / GLfloat(h),0.5,600);
gluLookAt(0.0,0.0, 100, 0.0,0.0,0.0, 0.0f,1.0f,0.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void myIdle()
{
xRotate += 0.5f;
yRotate += 1.0f;
zRotate += 1.5f;
if (xRotate >= 360.0f)
xRotate -= 360.0f;
if (yRotate >= 360.0f)
yRotate -= 360.0f;
if (zRotate >= 360.0f)
zRotate -= 360.0f;
glutPostRedisplay();
}
void myKeyboard(unsigned char key, int x, int y)
{
switch (key) {
case '1':
showFace = !showFace;
break;
case '2':
showWire = !showWire;
break;
case '3':
showPoint = !showPoint;
break;
case 'w':
translateY += 1.0;
break;
case 's':
translateY -= 1.0;
break;
case 'd':
translateX += 1.0;
break;
case 'a':
translateX -= 1.0;
break;
case 'q':
translateZ -= 1.0;
break;
case 'e':
translateZ += 1.0;
break;
default:
break;
}
//std::cout << key << std::endl;
glutPostRedisplay();
}
//reset model
//update?
void myDisplay()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glRotatef(xRotate, 1.0f, 0.0f, 0.0f);
glRotatef(yRotate, 0.0f, 1.0f, 0.0f);
glRotatef(zRotate, 0.0f, 0.0f, 1.0f);
glScalef(scale, scale, scale);
glTranslatef(translateX,translateY,translateZ);
//切换之后,从每个list中拿出渲染的东西显示
if (showFace)
glCallList(showFaceList);
if (showPoint)
glCallList(showPointList);
if (showWire)
glCallList(showWireList);
glCallList(showStrengthList);
glutSwapBuffers();
}
//key board movement
void mySpecial(int key, int x, int y) {
switch (key)
{
case GLUT_KEY_UP:
xRotate += 5.0f;
break;
case GLUT_KEY_DOWN:
xRotate -= 5.0f;
break;
case GLUT_KEY_LEFT:
yRotate += 5.0f;
break;
case GLUT_KEY_RIGHT:
yRotate -= 5.0f;
break;
case GLUT_KEY_PAGE_UP:
scale = scale + 0.01;
break;
case GLUT_KEY_PAGE_DOWN:
if(scale > 0.01)
scale = scale - 0.01;
break;
default:
break;
}
glutPostRedisplay();
}
bool MyOpenGLShowMesh::LoadMesh(string fileName)
{
string suffix = fileName.substr( fileName.find_last_of('.') + 1);
int loadMask = 0;
int err = 0;
if(suffix == "ply")
err = vcg::tri::io::ImporterPLY<MyMesh>::Open(mesh,fileName.c_str());
else if(suffix == "obj")
err = vcg::tri::io::ImporterOBJ<MyMesh>::Open(mesh,fileName.c_str(),loadMask);
else
{
std::cout << "this program not support mesh file " + suffix << std::endl;
err = 1;
}
myMeshData = &mesh;
return err == 0;
}
bool MyOpenGLShowMesh::InitGLWindow(int argc, char** argv)
{
xRotate = 0.0f;
yRotate = 0.0f;
zRotate = 0.0f;
scale = 1.0;
showFace = true;
showWire = true;
showPoint = false;
translateX = 0.0f;
translateY = 0.0f;
translateZ = 0.0f;
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowPosition(100, 100);
glutInitWindowSize(1024, 768);
glutCreateWindow("Mesh Viewer");
glutKeyboardFunc(&myKeyboard);
glutSpecialFunc(&mySpecial);
glutReshapeFunc(&myReshape);
glutDisplayFunc(&myDisplay);
//应该还需要设置一下透视投影的视锥
//-----
return true;
}
//---MyOpenGLShowMesh-------------
bool MyOpenGLShowMesh::InitGLMesh()
{
glClearColor(0.3, 0.3, 0.3, 0.0);
glClearDepth(1.0);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
// ------------------- Lighting
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
// ------------------- Display List
showFaceList = glGenLists(1);
showWireList = glGenLists(1);
showPointList = glGenLists(1);
UpdateGLMesh();
return true;
}
void MyOpenGLShowMesh::UpdateGLMesh()
{
avePos = vcg::Point3f(0.0f,0.0f,0.0f);
std::vector<MyMesh::VertexType>& vec_vertexes = mesh.vert;
uint totalCnt = 0;
vcg::Box3<float> bbox = mesh.bbox;
for(uint j=0;j<vec_vertexes.size();j++)
{
avePos = avePos * (float(totalCnt) / (totalCnt+1)) + vec_vertexes[j].P() / (totalCnt+1);
totalCnt++;
}
this->modeAvePose = avePos;
//translate mesh
for(uint j=0;j< vec_vertexes.size();j++)
{
vec_vertexes[j].P() -= avePos;
}
//DRAW
//draw line to showWireList
glNewList(showWireList, GL_COMPILE);
glDisable(GL_LIGHTING);
glLineWidth(2.0f);
glColor3f(0.1f, 0.1f, 0.1f);
glBegin(GL_LINES);
std::vector<MyMesh::FaceType>& vec_faces = mesh.face;
for (auto iter_face = vec_faces.begin();iter_face != vec_faces.end();iter_face++)
{
for (int i = 0;i<3;i++)
{
for (int j = i+1;j<3;j++)
{
glVertex3f(iter_face->P(i).X(),iter_face->P(i).Y(),iter_face->P(i).Z());
glVertex3f(iter_face->P(j).X(),iter_face->P(j).Y(),iter_face->P(j).Z());
}
}
}
glEnd();
glEnable(GL_LIGHTING);
glEndList();
// SHOW POINT
glNewList(showPointList, GL_COMPILE);
glDisable(GL_LIGHTING);
glPointSize(1.0f);
glColor3f(0.8f, 0.8f, 0.8f);
glBegin(GL_POINTS);
for (auto iter_vert = mesh.vert.begin(); iter_vert != mesh.vert.end();iter_vert++)
{
glVertex3f(iter_vert->P().X(),iter_vert->P().Y(),iter_vert->P().Z());
}
glEnd();
glEnable(GL_LIGHTING);
glEndList();
// SHOW FACE
glNewList(showFaceList, GL_COMPILE);
for (auto iter_face = mesh.face.begin();iter_face != mesh.face.end();iter_face++)
{
glBegin(GL_TRIANGLES);
for (int i = 0;i<3;i++)
{
//glNormal3f(iter_face->N().X(),iter_face->N().Y(),iter_face->N().Z());
glVertex3f(iter_face->P(i).X(),iter_face->P(i).Y(),iter_face->P(i).Z());
//glVertex3f(mesh.point(*fv_it)[0], mesh.point(*fv_it)[1], mesh.point(*fv_it)[2]);
}
glEnd();
}
glEndList();
}
void MyOpenGLShowMesh::UpdateMesh(string meshFile)
{
LoadMesh(meshFile);
InitGLMesh();
}
void MyOpenGLShowMesh::GLMainLoop()
{
glutMainLoop(); //loop window here
}
int main(int argc, char *argv[])
{
string meshFile = "";
MyOpenGLShowMesh showMesh ;
showMesh.InitGLWindow(argc,argv);
showMesh.UpdateMesh(meshFile);
showMesh.GLMainLoop();
}
上面是一个简单的静态Mesh加载,在加载后mesh的点,面等拓扑结构和位置不再变化;如果需要渲染变化的mesh的点或者动态渲染一些点,则不能用glGenLists, glNewList ,glEndList 等“渲染列表”这种操作,我的理解是这类操作会将渲染命令直接放到GPU中,不可修改了属于是。如果需要画动态的mesh或者点云,可以直接在display函数中(glutDisplayFunc设置的函数)使用一般的渲染方式,每帧渲染一次即可。 我在使用的时候会在cpp中设置几个全局变量存放变化的vertexesList,每一帧画变化的点即可。
我在使用时,主线程需要处理别的东西,不可能说MainLoop在那边循环,因此需要开新线程去独立渲染mesh,主线程的处理工作动态去修改显示的mesh;
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include<algorithm>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <thread>
#include <mutex>
//该类为了实验多线程中,在另一个线程修改mesh之后如何画图
//继承了之前的MyOpenGLShowMesh类
class ShowMeshForMultiThread:public MyOpenGLShowMesh
{
public:
ShowMeshForMultiThread(){}
void InitGLWindowForMultyThread(int argc, char** argv);
//修改mesh
void UpdateMeshForMultiThread(std::string meshFile);
};
//这些变量都在MyOpenGLShowMesh对应的cpp中定义
extern float xRotate;
extern float yRotate;
extern float zRotate;
extern float scale;
extern bool showFace;
extern bool showWire;
extern bool showPoint;
extern float translateX;
extern float translateY;
extern float translateZ;
extern GLuint showFaceList, showWireList, showPointList;
extern GLuint showStrengthList;
extern MyMesh mesh;
extern vcg::Point3f avePos;
//每帧调用 画画
void myDisplay_directShow()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glRotatef(xRotate, 1.0f, 0.0f, 0.0f);
glRotatef(yRotate, 0.0f, 1.0f, 0.0f);
glRotatef(zRotate, 0.0f, 0.0f, 1.0f);
glScalef(scale, scale, scale);
glTranslatef(translateX,translateY,translateZ);
/*
直接画在处理速度上不如每帧调用list来得快
但是当一个显示列表被编译后,它不能被改变
感觉现实列表是在mainloop之前就被编译好,然后后续就改不了了
*/
//先写的在最上层渲染
//比如先写渲染这个面片为颜色A,后写渲染这个面片为颜色B,则最后渲染出的面片为颜色A。
//DRAW
//draw line to showWireList
glDisable(GL_LIGHTING);
glLineWidth(2.0f);
glColor3f(0.1f, 0.1f, 0.1f);
glBegin(GL_LINES);
std::vector<MyMesh::FaceType>& vec_faces = mesh.face;
for (auto iter_face = vec_faces.begin();iter_face != vec_faces.end();iter_face++)
{
for (int i = 0;i<3;i++)
{
for (int j = i+1;j<3;j++)
{
glVertex3f(iter_face->P(i).X(),iter_face->P(i).Y(),iter_face->P(i).Z());
glVertex3f(iter_face->P(j).X(),iter_face->P(j).Y(),iter_face->P(j).Z());
}
}
}
glEnd();
glEnable(GL_LIGHTING);
// SHOW FACE
for (auto iter_face = mesh.face.begin();iter_face != mesh.face.end();iter_face++)
{
glBegin(GL_TRIANGLES);
for (int i = 0;i<3;i++)
{
//glNormal3f(iter_face->N().X(),iter_face->N().Y(),iter_face->N().Z());
glVertex3f(iter_face->P(i).X(),iter_face->P(i).Y(),iter_face->P(i).Z());
//glVertex3f(mesh.point(*fv_it)[0], mesh.point(*fv_it)[1], mesh.point(*fv_it)[2]);
}
glEnd();
}
glutSwapBuffers(); //将渲染好的交换出来,将没渲染的换进去
}
void ShowMeshForMultiThread::InitGLWindowForMultyThread(int argc, char** argv)
{
MyOpenGLShowMesh::InitGLWindow(argc, argv);
//全局的显示函数,覆盖掉 MyOpenGLShowMesh::InitGLWindow 的设置
glutDisplayFunc(&myDisplay_directShow);
glClearColor(0.3, 0.3, 0.3, 0.0);
glClearDepth(1.0);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
// ------------------- Lighting
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
}
//更新的mesh
void ShowMeshForMultiThread::UpdateMeshForMultiThread(std::string meshFile)
{
MyOpenGLShowMesh::LoadMesh(meshFile);
avePos = vcg::Point3f(0.0f,0.0f,0.0f);
std::vector<MyMesh::VertexType>& vec_vertexes = mesh.vert;
uint totalCnt = 0;
vcg::Box3<float> bbox = mesh.bbox;
for(uint j=0;j<vec_vertexes.size();j++)
{
avePos = avePos * (float(totalCnt) / (totalCnt+1)) + vec_vertexes[j].P() / (totalCnt+1);
totalCnt++;
}
this->modeAvePose = avePos;
//translate mesh
for(uint j=0;j< vec_vertexes.size();j++)
{
vec_vertexes[j].P() -= avePos;
}
}
void TestChangeMesh(ShowMeshForMultiThread& showMeshMT)
{
sleep(5); //sleep延时是为了测试动态修改mesh的效果
showMeshMT.UpdateMeshForMultiThread("another mesh 2");
std::cout << "finish load" << std::endl;
}
int main(int argc, char *argv[])
{
string meshFile = "mesh 1";
ShowMeshForMultiThread showMeshMT;
showMeshMT.InitGLWindowForMultyThread(argc,argv);
showMeshMT.UpdateMeshForMultiThread(meshFile);
thread t(TestChangeMesh,ref(showMeshMT));
t.detach();//不能join join会阻塞直到TestChangeMesh执行完
showMeshMT.GLMainLoop();
}
以上就是多线程的初步实现,比较简陋,毕竟是自己debug用,因此没有安全性检测,比如资源分配等问题没考虑,多线程的一些资源互斥等问题也没有考虑。
OpenGL使用时,需要经过初始化,绘制设置,绘制循环三个大步骤,可以自订一些渲染时用到的函数,可以动态绘制模型,可以多线程使用。多线程时,初始化传入的参数为主线程main函数的传入参数。