当前位置: 首页 > 工具软件 > Mesh Viewer > 使用案例 >

【OpenGL】OpenGL——初始化,mesh渲染,以及多线程调用相关。

高寒
2023-12-01

前言

项目中使用OpenGL对Mesh进行Debug,实时监测mesh的变化,好在debug的途中一步一步分析哪里改错了。记录一下目前学到的OpenGL知识,后续可以方便查阅。

Ubuntu 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

CMakeLists

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})

QMake

# QT modules
QT += opengl  

LIBS += -lGL -lglut -lGLU

单线程使用OpenGL

参考:

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,每一帧画变化的点即可。

开一个新线程显示Mesh

我在使用时,主线程需要处理别的东西,不可能说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函数的传入参数。

 类似资料: