OpenGL入门:理清OpenGL程序逻辑

本文目标:

  1. 能够理解OpenGL的程序逻辑。
  2. 能够看懂大部分网上博客上的新手程序
  3. 能够认识更多的OpenGL函数
  4. 能够自己手写OpenGL。

1. 理清整个OpenGL代码的逻辑

1.1 一般来说,OpenGL的一个小程序往往由这几个部分组成:

a. 创建窗口(主函数)

在这里插入图片描述

b. 初始化函数

在这里插入图片描述

C. 显示绘制函数

在这里插入图片描述

D. 窗口重定型函数

在这里插入图片描述

1.2 几个函数分别有什么作用呢?
  • 很直观的理解:
    • 函数A:创建一个视界窗口,给我们画面一个载体。
    • 函数B:创建一个眼睛,并给眼睛各种属性。
    • 函数C:把物体绘制出来。
    • 函数D:当你改变窗口纵横比,物体因此也会发生变化,这个函数维护形状改变的正确性。

2. 开始写我们的OpenGL小程序

2.1 学习函数A:窗口创建函数

创建窗口步骤:
  1. glutInit(&argc,argv):初始化glut,这一步只要是写glut的程序,就必须要有,这用于对于glut的事件进行初始化
  2. (可选)void glutInitDisplayMode(unsigned int mode):设置显示方式,参数之后再解释
  3. (可选)void glutInitWindowPosition(int x, int y);:设置程序窗口在屏幕中的位置,默认x=100,y=100
  4. (可选)void glutInitWindowSize(int width, int height);设置程序窗口的大小
  5. int glutCreateWindow(char *name);创建窗口并添加标题
  6. void glutDisplayFunc(void (*func)(void));调用我们自己的绘图函数来绘制
  7. void glutMainLoop(void);启动消息循环后程序运行起来
完整代码:
#include<windows.h>
#include <GL/glut.h>

void display(void)
{
    //这是我们自己的绘图函数,我们在这里进行具体地绘制。
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);  //对GLUT进行初始化,必须一开始就初始化
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);  //设置显示方式,参数之后再解释
    glutInitWindowPosition(100, 100);  //设置程序窗口在屏幕中的位置
    glutInitWindowSize(400, 400);  //设置程序窗口的大小
    glutCreateWindow("First OpenGL Program");  //给程序窗口添加标题

    glutDisplayFunc(&display);  //*调用我们自己的绘图函数来绘制
    glutMainLoop();  //启动消息循环后程序运行起来

    return 0;
}

运行结果

在这里插入图片描述

  • 其实学的时候最好自己手打一遍,然后注释掉其中一行,看看会发生什么变化,这样对代码含义的理解非常快。例如:如果把glutDisplayFunc注释掉。
    在这里插入图片描述
  • 当没有绘图时,glut库不允许创建窗口。

2.2 函数C: 绘制函数

2.2.1 基础概念

  • OpenGL的基本图元(组成图的基本元件)有点(Point)、线段(Line)、多边形(Ploygon)、三角形(Triangle)、四边形(Quadrangle)。它们有这些组成关系:
    • 无数个“点”的线性排列组成----“线段”
    • 多个“线段”可以围成一个**“多边形”**
    • 多个“多边形”在三维空间中可以围成**“多面体”**

由此,可以看出,点是最基本的图元,其他的图元都是由顶点组合构成的,因此我们从点开始介绍。

2.2.2 点

  • 在OpenGL中,“点”称为顶点(Vertex),通常用一个形如(x, y, z)的三维坐标值表示。
  • 有时候会采用齐次坐标表示,就是一个形如(x, y, z, w)的四元组,可以这样理解这个四元组,前面的(x,y,z)表示“点”所在的方向,而w表示在该方向上的距离比例,所以(x, y, z, w)表示的实际坐标值就是(x/w, y/w, z/w)坐标点。如果w为0,则表示方向为(x, y, z)的无穷远点;如果w=10,表示取(x,y,z)的十分之一作为坐标点(x/10,y/10,z/10)。
  • ※ 一般情况 下,OpenGL 中的点将被画成单个的像素。实际上点还可以更小,虽然它可能足够小,但并不会是无穷小,一个像素内可以描绘多个点,取决于对点大小的设定,默认一个点的大小为一个像素。
  • 当然我们也可以改变点的大小,函数原型为:
//size 必须大于 0.0f,默认值为 1.0f,单位为“像素”。
//对于具体的 OpenGL 实现,点的大小都有个限度的,如果设置的 size 超过最大值,则设置可能会有问题,一般情况是不在增大。
void glPointSize(GLfloat size);

实现用代码几个点

void display(void)
{
    glPointSize(5);//在绘制之前要设置要相关参数,这里设置点的大小为5像素
    glBegin(GL_POINTS);
    {
        glVertex2f(0.0f, 0.0f); //OpenGl内的点是齐次坐标的四元组,缺省的z坐标为0.0f,w为1.0f,所以该点为(1, 2, 0, 1)
        glVertex2f(0.0f, 0.5f); //绘制的第二个点
        glVertex2f(0.5f, 0.25f); //绘制的第三个点
    }
    glEnd();

    glFlush();//glFlush,保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)
}

在这里插入图片描述

  • 需要注意的是:坐标的值默认是1.0为最大,即屏幕边缘,所以如果大于1,则会超出屏幕,这时候除非改变观察点,否则是看不到的。(0,0)表示在屏幕的中间,也就是坐标系原点。
  • OpenGL中绘制几何图元,必须使用 glBegain() 和 glEnd() 这一对函数,glBegin() 和 glEnd() 需要成对使用,两者之间是绘图语句。
  • glBegin()的参数就是需要绘制的图元类型,它决定了在glBegin()和glEnd()之间绘图的点将如何组织成目标图元,如参数为“GL_POINTS”,表示每一个点都是独立的,互不相连的,因此也就绘制出了一个个“点”。

补充知识:OpenGl函数命名规律

以点系列函数名为例:glVertex*()系列

glVertex2d, glVertex2f, glVertex3f, glVertex3fv

数字表示参数的个数,2表示有两个参数,3表示三个,4表示四个。

  • 字母表示参数的类型:

    • s 短整型,表示16位整数(OpenGL中将这个类型定义为GLshort),
    • i 整型,表示32位整数(OpenGL中将这个类型定义为GLint和GLsizei)
    • f 浮点型,表示32位浮点数(OpenGL中将这个类型定义为GLfloat和GLclampf),
    • d 双精度浮点型,表示64位浮点数(OpenGL中将这个类型定义为GLdouble和GLclampd)。
    • v 数组型,表示传递的几个参数将使用指针的方式。
GLfloat VertexArr3[] = {1.0f, 3.0f, 0.0f};//这三个参数以数组的形式传递过去
glVertex3fv(VertexArr3);

2.2.3 线

  • 线段是由两个顶点连接起来形成的图元。线段间的连接方式有三种:
    • 独立线段:图元类型参数–GL_LINES
    • 线段间首尾相连但最终不闭合:折线,图元类型参数–GL_LINE_STRIP
    • 线段间首尾相连最终封口闭合:图形,图元类型参数–GL_LINE_LOOP

对应的图片:
在这里插入图片描述

void display(void)
{
    /*同样用5个点,画出不同类型的线段组合*/
    //独立线段,5个点能画出2条线段
    glBegin(GL_LINES);
    {
        glVertex2f(-0.8f, -0.5f); 
        glVertex2f(-0.5f, -0.5f);

        glVertex2f(-0.8f, 0.0f); 
        glVertex2f(-0.5f, 0.0f); 

        glVertex2f(-0.8f, 0.5f); //最后的这个点没有与之配对的点,无法连成线段,所以不会被画出来,在独立线段模式下被舍弃
    }
    glEnd();

    //连续不闭合折线,5个点能画出4条线段
    glBegin(GL_LINE_STRIP);
    {
        glVertex2f(-0.3f, -0.5f); //起始点

        //后面的每一个点都会与前一个相连生成一条线段
        glVertex2f(-0.0f, -0.5f);
        glVertex2f(-0.3f, 0.0f); 
        glVertex2f(-0.0f, 0.0f); 
        glVertex2f(-0.3f, 0.5f);
    }
    glEnd();

    //连续闭合折线,5个点能画出5条线段
    glBegin(GL_LINE_LOOP);
    {
        glVertex2f(0.2f, -0.5f); //起始点

        //后面的每一个点都会与前一个相连生成一条线段
        glVertex2f(0.5f, -0.5f);
        glVertex2f(0.2f, 0.0f); 
        glVertex2f(0.5f, 0.0f); 
        //最后一个点不仅与前一个点相连,还与起始点相连,形成闭合
        glVertex2f(0.2f, 0.5f);
    }
    glEnd();

    glFlush();//保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)
}

在这里插入图片描述
同样的,直线可以指定宽度:

void glLineWidth(GLfloat width); //width表示线宽,单位:像素
  • 特殊的是线除了直线,还有虚线。可以用虚线连接两个点。在绘制虚线之前必须先启动“虚线模式”:glEnable(GL_LINE_STIPPLE); 虚线有不同的类型,调节函数如下:
/*
        参数pattern是16位二进制数(0或1),如OxAAAA表示1010101010101010
        从低位开始,每一个二进制位代表一个像素, 1表示用当前颜色绘制一个像素,0表示当前不绘制,只移动一个像素位,中间留下一个像素的空白
        factor是用来调节二进制位0和1代表的像素个数,如果factor为2,则表示遇到二进制1的时候用当前颜色绘制两个像素,0移动两个像素不绘制
*/ 
        void glLineStipple(GLint factor,GLushort pattern);
void display(void)
{
    //开启虚线模式
    glEnable(GL_LINE_STIPPLE);
    //调节虚线类型参数
    glLineStipple(2,0xAAAA);
    //调节线宽
    glLineWidth(3);
    //绘制一条虚线
    glBegin(GL_LINES);
    {
        glVertex2f(0,0);
        glVertex2f(0.5,0.5);
    }
    glEnd();

    glFlush();//保证前面的OpenGL命令立即执行(而不是让它们在缓冲区中等待)
}

2.2.4 多边形

  • 类似于点组合线段的方式,OpenGL定义的多边形是由多个点连接成线段再围成封闭区域。
  • 多边形有两种:凸多边形(指多边形任意非相邻的两点的连线位于多边形的内部)和凹多边形,但OpenGL中规定的多边形必须是凸多边形。
  • 但有时需要绘制一些凹多边形,通常解决的办法是对它们进行分割,用多个三角形来组合替代。显然,绘制这些三角形时,有些边不应该进行绘制,否则,多边形内部就会出现多余的线框。OpenGL提供的解决办法是通过设置边标志命令glEdgeFlag()来控制某些边产生绘制,而另外一些边不产生绘制,这里只需知道有这个工能,具体细节待遇到在研究,接下来看重点。
2.2.4.1连接方式
  • 按点的定义顺序依次连接:图元类型参数–GL_POLYGON
  • 从第1个点开始,每三个点一组画一个三角形,三角形之间是独立的:图元类型参数–GL_TRIANGLES
  • 从第三个点开始,每点与前面的两个点组合画一个三角形,即线性连续三角形串:图元类型参数–GL_TRIANGLE_STRIP
  • 从第三个点开始,每点与前一个点和第一个点组合画一个三角形,即扇形连续三角形:图元类型参数–GL_TRIANGLE_FAN
图例:

在这里插入图片描述在这里插入图片描述

2.2.4.2 多边形的正反两面

多边形为什么会有正反面这一说法呢?举个简单的例子。
虽然多边形是二维的,但是我们知道三维物体可以理解为多边形围成的,也就是说在三维空间物体上有二维多边形的存在。
一张“正方形”纸片,如果极限的薄,我们可以认为是一个多边形(正方形),那么纸仍然会存在两面,纸的正反面。更具体一点,如果我们用同大小6张正方形的纸片围成一个正方体,那么正方体的任何一个面都是一个多边形,而且这个多边形是有正反面,朝外的你能看到的部分是正面(假设),那么朝内的看不到的那部分就是反面。同样的道理,OpenGL要区分多边形的正反面。那么到底怎么定义正面或者反面呢?
有一个函数来定义:

glFrontFace(GL_CCW);//设置点序列逆时针方向围成的多边形为正面
glFrontFace(GL_CW); //设置点序列顺时针方向围成的多边形为正面

设置这个的原因是,默认情况下,OpenGL绘制三维物体时两面都会绘制,而实际的时候很多面我们是看不到的,如物体朝内方向的多边形,还有一些物体间遮挡的情况导致有些多边形是不可见的,因此为了提高性能,我们需要剔除这些不必要的绘制。

glEnable(GL_CULL_FACE); //来启动剔除功能
glCullFace(); //参数可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,表示剔除多边形的哪种面,设定后该类型的多边形不绘制
2.2.4.2 绘制模式

常用的多边形绘制模式有:填充式(默认)、轮廓线式、顶点式和镂空图案填充式。
前三种都是使用glPolygonMode()函数来指定模式,最后一种比较特殊。

void glPolygonMode(GLenum face,GLenum mode);//该函数要求说明是对多边形哪一个面是定face设置模式
void myDisplay(void)
{
    //设置点的大小,以便容易观察
    glPointSize(5);

    //设置正面的绘制模式为:填充式,反面不设定则默认为填充式
    glPolygonMode(GL_FRONT,GL_FILL);
    glBegin(GL_POLYGON);
    {
        glVertex2f(0,0);
        glVertex2f(0,0.3);
        glVertex2f(-0.3,0.3);
        glVertex2f(-0.3,0);
    }
    glEnd();

    //设置正面的绘制模式为:轮廓线式
    glPolygonMode(GL_FRONT,GL_LINE);
    glBegin(GL_POLYGON);
    {
        glVertex2f(0.5,0);
        glVertex2f(0.5,0.3);
        glVertex2f(0.2,0.3);
        glVertex2f(0.2,0);
    }
    glEnd();

    //设置正面的绘制模式为:顶点式
    glPolygonMode(GL_FRONT,GL_POINT);
    glBegin(GL_POLYGON);
    {
        glVertex2f(0.9,0);
        glVertex2f(0.9,0.3);
        glVertex2f(0.6,0.3);
        glVertex2f(0.6,0);
    }
    glEnd();

    glFlush();
}

在这里插入图片描述
主要参考博客:https://www.cnblogs.com/gl5773477/p/4007350.html
参考资料:https://www.tuicool.com/articles/uiayYrI, https://jingyan.baidu.com/album/215817f7e3e4bd1eda1423e6.html?picindex=4

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值