美文网首页OpenGL初探
第七节—绘制简单的点和线(投影)

第七节—绘制简单的点和线(投影)

作者: L_Ares | 来源:发表于2020-05-09 13:46 被阅读0次

    本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。

    全部文章的代码都是在md里面手写的,没有任何的复制粘贴,所以如果出现小的错误,请大家见谅,如果可以帮忙指出的话万分感谢。

    本节主要利用上两节学习到的基本集合图元和固定管线进行绘制。绘制一些投影类型的点和线。主要是熟悉整个绘制的流程。废话不多说,下面直接上代码,代码分为三个大块

    1. 引用类

    2. 全局变量

    3. 函数

    4.main函数

    分别来讲清楚这些引用类是干什么的,全局变量都是干什么的,函数是干什么的,main函数里面需要做什么。

    一、引用类

    
    //GLTools的头文件里面包含大多数的GLTool类中类似C语言的独立函数
    #include "GLTools.h"
    
    //矩阵工具类
    //用于加载单元矩阵/矩阵/矩阵相乘/压栈/出栈/旋转/缩放/平移
    #include "GLMatrixStack.h"
    
    //矩阵工具类
    //表示位置。通过它可以设置vOrigin、vForward、vUp
    #include "GLFrame.h"
    
    //矩阵工具类
    //用来快速设置正投影矩阵/透视投影矩阵。里面有功能完成3D到2D的映射
    #include "GLFrustum.h"
    
    //三角形批次类
    //利用它传递顶点/颜色/光照/纹理等数据到存储着色器中
    #include "GLBatch.h"
    
    //变换管道类
    //用来快速在程序中传递模型视图矩阵/投影矩阵
    #include "GLGeometryTransform.h"
    
    //数学库
    #include <math.h>
    
    /**创建版本兼容
    根据宏判断系统
    MacOS下使用<glut/glut.h>
    Windows和Linux下使用<GL/glut.h>也就是FreeGlut的静态版本库
    需要添加一个宏定义FREEGLUT_STATIC*/
    #ifdef __APPLE__
    #include <glut/glut.h>
    #else
    #define FREEGLUT_STATIC
    #include <GL/glut.h>
    #endif
    
    

    二、全局变量

    
    //着色器管理器
    GLShaderManager shaderManager;
    
    //模型视图矩阵堆栈
    GLMatrixStack modelViewMatrix;
    
    //投影矩阵堆栈
    GLMatrixStack projectionMatrix;
    
    //观察者视图坐标
    GLFrame cameraFrame;
    
    //图形环绕坐标
    GLFrame objectFrame;
    
    //图元绘制时的投影方式
    GLFrustum viewFrustum;
    
    //图形做变换用的管道
    GLGeometryTransform transformPipeline;
    
    //容器。最好使用几个图元绘制方式就设定几个容器。
    GLBatch pointBatch;
    
    //颜色RGBA
    GLfloat vGreen[] = {0.f,1.f,0.f,1.f};
    GLfloat vBlack[] = {0.f,0.f,0.f,1.f};
    
    //点击空格的次数
    int nStep = 0;
    
    

    三、函数

    函数这里我会按照执行的逻辑顺序来写,所以从上到下就是程序内部的执行顺序。

    1. setUpRC

    设置渲染的环境和数据,在这里我们将颜色和顶点数据等环境参数设置好。

    
    void setUpRC()
    {
    //第一步先设置清屏颜色。随意设置自己要用的
    glClearColor(0.7f,0.7f,0.7f,1.f);
    
    //初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    //立体图形,开启深度测试
    glEnable(GL_DEPTH_TEST);
    
    //设置变换管道堆栈信息
    transformPipeline.SetMatrixStacks(modelViewMatrix,projectionMatrix);
    
    //设置观察者矩阵的位置靠近屏幕是负数,远离屏幕是正
    cameraFrame.MoveForward(-15.f);
    
    //设置顶点信息
    GLfloat vCoast[9] = {
         3.f,3.f,0.f,
         0.f,3.f,0.f,
         3.f,0.f,0.f
    };
    
    //设置顶点的绘制方式
    //参数1:选择图元
    //参数2:顶点数量
    pointBatch.Begin(GL_POINTS,3);
    
    //拷贝顶点数据
    pointBatch.CopyVertexData3f(vCoast);
    
    //结束容器设置
    pointBatch.End();
    }
    

    2.changeSize

    重塑函数。它需要在main函数里面注册。注册后,在窗口第一次创建或者窗口发生了变化的时候,都会自动调用。

    void changeSize(int w,int h)
    {
    
    //设置视口的大小。xy都是0是因为一般都默认用窗口左下角为视口原点
    glViewport(0.f,0.f,w,h);
    
    /**在这里设置投影方式和参数。之所以选择这里是因为这里最容易获取宽高。
    因为投影矩阵需要设置宽高比
    参数解析:
    (1).float fFov:指定观察者垂直视角。就像是眼睛可视范围的上下限的夹角。
    也就是可视空间顶面与底面的夹角。这个数值必须大于0
    (2).float fAspect:纵横比 宽(w)/高(h)
    (3).float fNear:视点(观察点)到近剪裁面的距离
    (4).float fFar:视点(观察点)到远剪裁面的距离
    注意:这里的fFar必须大于fNear*/
    viewFrustum.SetPerspective(36.f,float(w)/float(h),1.f,500.f);
    
    //设置投影矩阵
    //GetMatrix的方法都可以获取栈顶值,上面设置过投影信息了,
    //OpenGL会自己计算投影矩阵,直接从栈顶拿来就行,然后加载到我们的投影矩阵堆栈里面。
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //设置模型视图矩阵堆栈
    //模型视图矩阵堆栈直接加载一个单元矩阵,方便做变换相乘,我理解为给了他
    //初始化。
    modelViewMatrix.LoadIdentity();
    
    }
    
    

    3.RenderScene

    渲染函数。需要在main函数里面注册。在屏幕发生变化或者开发者手动触发的时候会调用,完成从数据到图形显示。

    void RenderScene() {
    
    //渲染的第一步一定是清空缓冲区,避免之前的渲染对新的渲染造成影响
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //对于多种图形的绘制,多种图元设置,需要进行压栈,这样在绘制完成一个之
    //后可以复制数据然后出栈。避免对后面的绘制造成影响。
    modelViewMatrix.PushMatrix();
    
    //开始做变换。定义一个观察者矩阵。
    M3DMatrix44f mCamera;
    
    //获取观察者坐标栈顶的矩阵值,并赋值给mCamera
    cameraFrame.GetCameraMatrix(mCamera);
    
    //观察者矩阵和模型视图矩阵相乘,结果放在mv栈顶
    modelViewMatrix.MultMatrix(mCamera);
    
    //定义一个图形的视图矩阵
    M3DMatrix44f mObjectFrame;
    
    //获取图形环绕坐标栈顶的矩阵值,并且赋值给mObjectFrame
    objectFrame.GetMatrix(mObjectFrame);
    
    //图形的视图矩阵再和模型视图矩阵相乘,结果放到mv栈顶
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //到上面,本节需要的视图变换就都做完了。开始使用着色器。
    //因为我们只用变换和颜色,所以选择了平面着色器就可以。
    //直接利用上面定义的变换管道获取mvp矩阵
    shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.
    GetModelViewProjectionMatrix(),vBlack);
    
    //开始绘制。因为要绘制多个图形,所以根据定义的nStep来分别绘制
    switch (nStep) {
         case 0:
         //设置点的大小
         glPointSize(4.f);
         //利用对应的容器类绘制
         pointBatch.Draw();
         //绘制完成后记得把点的大小修改回去,因为OpenGL是个状态机
         glPointSize(1.f);
         break;
    }
    
    //绘制完成一个图形,就要把刚才Push进来的矩阵都Pop出去,避免造成下一次绘制的混乱
    modelViewMatrix.PopMatrix();
    
    //交换缓冲区
    glutSwapBuffers();
    
    }
    
    

    下面的4和5就不说顺序了,不是重点的逻辑顺序。

    4.SpecialKeys

    特殊键位函数。需再main函数里面注册。上下左右让图形发生环绕。

    在这里发生环绕有两种思路,一个是让图形的每个顶点都发生仿射变化,完成图形的环绕,另外一个就是让世界坐标系都变化,发生视觉上的相对变化,当图形的顶点很多的时候,明显第一种方法是很难的,所以我们选择第二种。

    RotateWorld参数解析:

    参数1:发生旋转的弧度
    参数2,3,4:分别代表x,y,z轴,可以赋值0和1,表示围绕哪个轴发生环绕
    围绕哪个环绕,哪个就是1,不围绕的就是0

    
    void SpecialKeys(int key , int x , int y)
    {
    
         switch (key) {
    
            case GLUT_KEY_UP:
                   objectFrame.RotateWorld(m3dDegToRad(-5.f),1.f,0.f,0.f);
                   break;        
            case GLUT_KEY_DOWN:
                   objectFrame.RotateWorld(m3dDegToRad(5.f),1.f,0.f,0.f);
                   break;
            case GLUT_KEY_LEFT:
                   objectFrame.RotateWorld(m3dDegToRad(-5.f),0.f,1.f,0.f);
                   break;
            case GLUT_KEY_RIGHT:
                   objectFrame.RotateWorld(m3dDegToRad(5.f),0.f,1.f,0.f);
                   break;
         }
    
        //手动发送重新渲染绘制的信息
        glutPostRedisplay();
    
    }
    
    

    5.KeyPressFunc

    按键函数。需在main函数里面注册。点击空格,切换不同的视图。这里利用的是ASCII码值来做判断,所以F1,F2这种特殊的键就不行了,abc这种带ACSII码的可以。

    
    void KeyPressFunc(unsigned char key , int x , int y)
    {
         //这里我们使用“空格”来切换视图,空格的ASCII是32
         if(key == 32)
        {
            nStep++; 
            //只画4个图形,所以到第4个的时候再点“空格”跳回到第一个
            if(nStep > 3)
           {
               nStep = 0;
           }
        }
    
        switch (nStep) {
           case 0:
                  glutSetWindowTitle("GL_POINTS");
                  break;
           case 1:
                  glutSetWindowTitle("GL_LINES");
                  break;
           case 2:
                  glutSetWindowTitle("GL_LINE_STRIP");
                  break;
           case 3:
                  glutSetWindowTitle("GL_LINE_LOOP");
                  break;
        }
    
        //手动发送重新渲染的通知
        glutPostRedisplay();
    
    }
    
    

    四、main函数

    
    int main(int argc , char *argv[])
    {
        //设置工作目录到工程的可执行程序的文件夹下(/Resouce)
        //GLUT的优先设定已经设定过了,这里手动是为了保险
        gltSetWorkingDirectory(argv[0]);
    
        //初始化GLUT
        glutInit(&argc,argv);
    
        //初始化显示模式
        glutInitDisplayMode(GLUT_DOUBLE|GLUT_DEPTH|GLUT_RGBA|
                            GLUT_STENCIL);
    
        //初始化窗口大小
        glutInitWindowSize(800,600);
    
        //创建窗口并命名
        glutCreateWindow("GL_POINTS");
    
        //注册重塑函数
        glutReshapeFunc(changeSize);
    
        //注册渲染函数
        glutDisplayFunc(RenderScene);
    
        //注册普通按键函数
        glutKeyboardFunc(KeyPressFunc);
    
        //注册特殊键位函数
        glutSpecialFunc(SpecialKeys); 
       
        //初始化一个Glew库,确保OpenGL的API对程序都可以使用
        GLenum status = glewInit();
    
        //在做任何的渲染之前,都必须要保证驱动程序的初始化过程中不能有错误
        if (GLEW_OK != status) {
    
            printf("glew Error : %s \n",glewGetErrorString(status));
    
            return 1;
    
        }
    
        //设置渲染环境
        setUpRC();
    
        //建立一个类似RunLoop的循环
        glutMainLoop();
    
        return 0;    
    
    }
    
    

    结束上述的绘制以后,结果应该是如下图GL_POINTS显示:

    GL_POINTS

    这个就是绘制三个点,可以进行上下左右的翻转。

    下面写一下每两个点连成一条线(LINES),但是不足两个点则不连接。
    每两个点连成一条线,不闭口(STRIP)。
    每两个点连成一条线,形成线环(LOOP)。

    五、线

    线和点的不同点主要在于setUpRC里面要用对应的GLBatch来设置图元类型。在RenderScene中的switch中,case1,2,3的时候要使用对应的GLBatch来进行Draw()。

    GLBatch容器最好画几种图元类型就设置几个,这样不会造成互相影响。

    下面只写不同的地方,也就是上面说的那两个地方。直接将对应的代码替换掉就好。

    //全局变量处添加
    GLBatch lineBatch;
    GLBatch lineStripBatch;
    GLBatch lineLoopBatch;
    
    //setUpRC中添加
    lineBatch.Begin(GL_LINES,3);
    lineBatch.CopyVertexData3f(vCoast);
    lineBatch.End();
    
    lineStripBatch.Begin(GL_LINE_STRIP,3);
    lineStripBatch.CopyVertexData3f(vCoast);
    lineStripBatch.End();
    
    lineLoopBatch.Begin(GL_LINE_LOOP,3);
    lineLoopBatch.CopyVertexData3f(vCoast);
    lineLoopBatch.End();
    
    //在RenderScene的switch的case中添加1,2,3
    case 1:
          //设置线段的宽
          glLineWidth(2.f);
          lineBatch.Draw();
          glLineWidth(1.f);
          break;
    case 2:
          glLineWidth(2.f);
          lineStripBatch.Draw();
          glLineWidth(1.f);
          break;
    case 3:
          glLineWidth(2.f);
          lineLoopBatch.Draw();
          glLineWidth(1.f);
          break;
    
    

    结果是如下图三个:

    GL_LINES:

    GL_LINES

    GL_LINE_STRIP:

    GL_LINE_STRIP

    GL_LINE_LOOP:

    GL_LINE_LOOP

    还可以画三角形带,三角形扇,无底金字塔,这些代码将在下一节的知识学习完成之后再写出。

    相关文章

      网友评论

        本文标题:第七节—绘制简单的点和线(投影)

        本文链接:https://www.haomeiwen.com/subject/tcxqnhtx.html