OpenGL初探

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

2020-05-09  本文已影响0人  L_Ares

本文为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

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

上一篇下一篇

猜你喜欢

热点阅读