绪论

图形和图像的区别?

图形由场景的几何模型和景物的物理模型共同组成,更强调场景的几何表示;而图像则是计算机内以位图形式存在的灰度信息。

计算机如何实现三维图形的显示?

  1. 通过透视在2D屏幕上产生3D效果
  2. 通过隐藏面消除、光照和阴影、纹理贴图等增强3D效果

OpenGL的三种常见定义及其含义?

  1. 程序员观点:认为OpenGL是开放的图形程序库(Open Graphics Library)、是图形应用程序的编程接口(API)和图形硬件的软件接口。开放是指OpenGL是跨平台、跨语言,可移植的。OpenGL的实现非常接近于底层硬件,使得OpenGL应用程序的运行效率很高。同时作为软件接口,OpenGL屏蔽了硬件和及其实现的复杂性,向程序员提供了一系列图形API,使得程序员可以编写出交互式计算机图形应用。
  2. 状态机观点:认为OpenGL是一个具有输入和输出的状态机(State Machine)。通过应用程序中指定输入对象的函数和修改OpenGL状态机中状态的函数,经过OpenGL这个“状态机”的处理后,输出为2D屏幕上的图像。
  3. 流水线观点:认为OpenGL是对图元和像素进行加工的流水线。这与硬件的实现比较接近。

OpenGL的版本和扩展?

常用的有OpenGL核心库(GL库)、OpenGL实用库(GLU库)及OpenGL实用工具包 (GLUT库)。

  1. 其中GL库是OpenGL的核心,它包含全部必须的OpenGL函数,可以实现图形绘制的所有功能,但不能实现窗口创建和用户交互;
  2. GLU库完全用GL库编写,是对GL库中一些常用操作的简化与封装——程序员可以不需要GLU库,但使用GL库编写程序会更复杂;
  3. GLUT库独立于操作系统和窗口系统,可以实现窗口创建和用户交互,是对OpenGL的补充和进一步封装。

二维图形编程

基于GLUT的OpenGL应用程序的基本结构?

主函数框架:

  1. 初始化GLUT
  2. 配置和创建窗口
  3. 注册窗口和用户输入事件处理函数
  4. 初始化OpenGL状态机
  5. 进入事件处理循环

主函数外基于事件驱动进行编程。

图元的概念和分类?

图元是OpenGL中的基本绘制实体,复杂的对象就是由很多这些基本的图元组成。图元包括几何图元和非几何图元

  1. 几何图元:点、直线段、多边形。
  2. 非几何图元:位图(Bit map)、像素图(Pixel map)。

10种几何图元类型及其装配方式?

image-20220524100237352

  1. 点:GL_POINTS
  2. 线:GL_LINESGL_LINE_STRIPGL_LINE_LOOP
  3. 面:GL_POLYGONGL_TRIANGLESGL_TRIANGLE_STRIPGL_TRIANGLE_FANGL_QUADSGL_QUAD_STRIP

各几何图元的属性?

  1. 点:颜色:glColor3f()、尺寸:glPointSize()
  2. 线:颜色、线宽:glLineWidth()、点划模式:glLineStipple()
  3. 面:颜色、绘制模式、拣选特性、点划模式(了解)。

几何图元的着色模式有两种:

  1. 平滑着色 GL_SMOOTH:使用颜色插值,颜色逐渐过渡,使得表面看起来更平滑。
  2. 平面着色 GL_FLAT:所有顶点都使用相同的颜色值,图形看起来棱角分明。

交互与动画

GLUT回调函数有哪些?

  1. 显示回调函数:void glutDisplayFunc(void (*)())
  2. 窗口大小回调函数:void glutReshapeFunc(void (*)(int width, int height))
  3. 空闲回调函数:void glutIdelFunc(void (*)())
  4. 键盘回调函数:void glutKeyboardFunc(void (*)(unsigned char key, int x, int y))
  5. 特殊键回调函数:void glutSpecialFunc(void (*)(int key, int x, int y))
  6. 鼠标按键回调函数:void glutMouseFunc(void (*)(int button, int state, int x, int y))
  7. 鼠标移动回调函数:void glutMotionFunc(void (*)(int x, int y))

双缓存机制?

缓存的作用是存放OpenGL渲染流水线的绘制输出,然后为显示器提供图像输入

单缓存 GLUT_SINGLE只适用于静态场景的生成和显示,而动画则可能产生闪烁,因为这个缓存既是输入又是输出,可能会有二者更新不同步的情况。——glFlush()

双缓存 GLUT_DOUBLE使用了两个缓存来分别完成输入输出的职责,后台缓存绘制完后,和前台缓存进行交换——glutSwapBuffers()

选择模式和渲染模式的区别?

OpenGL的绘制模式包括渲染模式 GL_RENDER选择模式 GL_SELECT两种。

image-20220524104854893

  1. 渲染模式是可见的绘制模式,而选择模式是不可见的绘制模式。
  2. OpenGL流水线一次只能处理一种绘制模式,两种模式相互独立。
  3. 从选择模式转换到渲染模式时,命中记录才会真正写入到选择缓存中。

几何变换与三维编程

OpenGL生成三维图像的基础?合成摄像机模型的主要功能?

OpenGL对真实摄像机成像的过程进行了数学和物理建模,即合成摄像机模型

合成摄像机模型的两个主要功能为:

  1. 视图变换:确定虚拟摄像机的位置和朝向
  2. 投影变换:虚拟摄像机的投影取景

OpenGL变换与坐标系的关系?

image-20220524105826995

  1. 模型变换:确定几何对象在世界坐标系中的位置和朝向

    OpenGL函数:glTranslateglRotateglScale

  2. 视图变换:确定几何对象在视点坐标系中的位置和朝向

    OpenGL函数:gluLookAt(注意这是GLU库的函数!)

  3. 投影变换:确定哪些几何对象在视野之内以及它们的形状和大小

    OpenGL函数:gluOrtho2DglOrthoglFrustumgluPerspective

两种建模方案?

模型变换的主要用途就是为几何对象建模。建模的两种方案为实例化建模层次化建模

  1. 实例化建模:由基本的对象通过三种独立模型变换来建立不同的几何场景。
  2. 层次化建模:几何对象具有彼此相互连接的层次关系,如人体关节。

实例化建模是最典型、最常用的建模方式,需要重点掌握!

实例化建模进行几何变换的模板为:

1
2
3
4
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//几何变换
//绘制几何图形

使用几何变换时需要注意变换的次序,程序最下面的变换在数学上会最先应用:

image-20220524111705213

上面的第一个程序就是先旋转再平移,对应的变换为2;第二个程序先平移再旋转,对应的变换为1。

如何正确理解模型变换和视图变换之间的联系?

模型变换:确定几何对象在世界坐标系中的位置和朝向,而视图变换:确定几何对象在视点坐标系中的位置和朝向。二者共用一个模型视图矩阵,是因为二者的改变是相对的——模型变换移动的是物体,而视图变换移动的是观察者,两个变换的角度不同。

正交投影和透视投影的区别?

投影取景都是通过设置视域体来实现的。两种投影方式的不同在于视域体的不同

  1. 正交投影的视域体是长方体,用于模拟长焦镜头。优点是可以保持距离和形状,缺点是没有透视效果,缺乏立体感。
  2. 透视投影的视域体是平截头体,用于模拟广角等透视镜头。优点是具有立体感,缺点是物体可能发生形变(透视缩短)。

隐藏面消除的两种途径?

  1. 多边形拣选:剔除不可见的面。

    使用方法:

    1
    2
    glEnable(GL_CULL_FACE);//启用多边形拣选
    glCullFace(GL_BACK);//剔除背面
  2. 深度缓存算法:额外增加深度缓存来保存图元的深度信息,绘制时比较图元的深度来决定其是否绘制。

    使用方法:

    1
    2
    3
    glutInitDisplayFunc(GLUT_DEPTH);//申请深度缓存
    glEnable(GL_DEPTH_TEST);//开启深度测试
    glClear(GL_DEPTH_BUFFER);//清空当前深度缓存

GLU和GLUT提供的高级几何对象?

  1. GLU提供的高级几何对象有球体(Sphere)、圆台(Cylinder)、圆盘(Disk)。

    使用模板为:

    1
    2
    3
    GLUquadricObj* obj=gluNewQuadric();//创建一个GLU对象
    gluDisk(obj,0,0.01,100,100);//给该对象绑定一个实体
    gluDeleteQuadric(obj);//删除该GLU对象
  2. GLUT提供的常用高级几何对象有球体(Sphere)、圆锥(Cone)、茶壶(Teapot)等。

    它的使用比较简单,直接调用函数 glut[Wire/Solid][Sphere/Cone/Teapot]即可。

光照与材质

现实世界光照明系统的建模?

真实世界物体的颜色及明暗效果由光照与物体材质间的交互作用产生。物体表面反射的光形成了我们看到的颜色。光的来源由两种:①光源的直接照射 ②物体周围的环境光照。

什么是Phong光照模型?

Phong模型是计算机图形学十分有用的一种近似光照模型。该模型认为一个点的明暗程度由4种类型的贡献决定:漫反射(diffuse)、镜面反射(specular)、环境反射(ambient)和材质的自发光(emissive)。

需要关注的向量:l(光的入射)、n(表面法向量)、r(镜面反射光的方向)、v(视点的方向)。

image-20220524131851375

  1. 漫反射:反射光均匀地散射到各个方向,这时需要关注的参数有:

    ① 漫反射光的颜色

    ② 表面材质的漫反射率

    ③ l与n的夹角,用于衡量漫反射光的强度。

  2. 镜面反射:反射光全部集中在全反射方向r附近,需要关注的参数有:

    ① 入射光的颜色

    ② 表面材质的镜面反射率

    ③ r与v的夹角(这时是就需要考虑视点了)

    ④ 高光指数:高光指数越大,表面材质越光滑,高光越集中

  3. 环境反射:所有点的各个方向都获得相同强度的光照,它模拟的是经过很多表面多次反射后的存在于整个场景中的光照。需要关注的参数有:

    ① 环境反射光的颜色

    ② 表面材质的环境反射率

  4. 发射光:表面材质的发射光只用于自身渲染,而不将其作为光源。

OpenGL中的光源如何使用?

OpenGL的每个光源都有独立的漫射光、镜面光、环境光等属性,这些属性都需要我们自己去设置(或取默认值)。这是为了通过以人为控制的方式更好地逼近真实值。

光源的使用:

  1. 指出光照是否有效:glEnable(GL_LIGHTING);glDisable(GL_LIGHTING);。如果光照无效,则不进行光照的各种计算,这时使用glColor设定的颜色。
  2. 启用光源:启用光源0:glEnable(GL_LIGHT0);,关闭光源0:glDisable(GL_LIGHT0)。OpenGL提供了 GL_LIGHT0~GL_LIGHT78种默认光源,我们可以直接使用这些光源的默认值,也可以自己修改光源的参数。

OpenGL中光源的特性有哪些?怎么设置?

光源有如下三种:上面两种为位置型光源,下面的为方向型光源。位置型光源又可分为点光源和聚光灯。这三种光源都可以通过设置光源的参数来实现。

image-20220524141055262

将光源light的属性pname设置为params的函数为:

1
void glLightfv(GLenum light, GLenum pname, const GLfloat *params);

用于一个光源有多个属性,所以这个函数往往需要多次调用,来设置光源的不同属性。

光源常用的属性如下:

光源特性 缺省值 意义
GL_AMBIENT (0,0,0,1) RGBA模式下光源的环境光
GL_DIFFUSE 光源0:(1,1,1,1),其他:(0,0,0,1) RGBA模式下光源的漫反射光
GL_SPECULAR 光源0:(1,1,1,1),其他:(0,0,0,1) RGBA模式下的镜面光
GL_POSITION (0,0,1,0) 默认在摄像机的后方
GL_SPOT_DIRECTION (0,0,-1) 聚光矢方向向量
GL_SPOT_EXPONENT 0 聚光指数
GL_SPOT_CUTOFF 180 截止角,默认为点光源

前三个参数分别设置光源的三种光的颜色;第四个参数设置光源的位置;最后三个参数设置聚光灯的属性,分别为聚光灯的方向,光线衰减的速度和聚光灯张开角度(的一半)。

什么是材质?材质颜色和光线颜色有什么不同?

材质是指物体表面的反射性质。与光相对应,材质也要分漫反射、镜面反射和环境反射,以及自发光。

将材质的某一面face的属性pname设置为params的函数为:

1
void glMaterialfv(GLenum face, GLenum pname, const GLfloat *params)

这个函数往往也要多次调用。

材质常用的属性如下:

源特性 缺省值 意义
GL_AMBIENT (0.2,0.2,0.2,1) 材质环境光颜色
GL_DIFFUSE (0.8,0.8,0.8,1) 材质漫反射光颜色
GL_AMBIENT_AND_DIFFUSE 材质环境光和漫反射光颜色
GL_SPECULAR (0,0,0,0) 材质镜面反射光颜色
GL_SHINESS 0 辉度系数
GL_EMISSION (0,0,0,1) 材质自发光颜色

怎么向OpenGL场景中添加光照?(注光照≠光源)

向场景中添加光照涉及光源和材质等要素,步骤如下:

  1. 为场景的顶点提供法向量,以确定物体的各个顶点法向相对于光源的方位。
  2. 启用光照计算,然后创建一个或多个光源,并设置其参数。✅
  3. 创建光照模型,来定义全局环境光的等级和观察点的有效位置等。
  4. 为场景中的物体定义材质属性。✅

还有两步之前没有讲:

  1. 为顶点指定法向量使用函数 glNormal3f,该法向量将一直生效直到下一次使用 glNormal3f
  2. 创建光照模型使用函数 glLightModel,它的属性如下:

    image-20220524174820029

平面渲染模式和平滑渲染模式

两种模式在函数 glShadeModel里面设置。

  1. 平滑渲染模式 GL_SMOOTH:使用颜色插值,颜色逐渐过渡,表面看起来更平滑。
  2. 平面渲染模式 GL_FLAT:所有顶点都使用相同的颜色值,图形看起来棱角分明。

如何利用混合实现半透明表面的特殊渲染效果?

使用alpha分量混合时要分清混合的顺序:哪种颜色是原颜色,哪种颜色是目标颜色。

image-20220524175406789

最终颜色为$(R_S\times S_r+R_d\times D_r,G_S\times S_g+G_d\times D_g,B_S\times S_b+B_d\times D_g)$

关键是得到混合因子和alpha分量的关系。

使用混合分为两步:

  1. 开启混合模式:glEnable(GL_BLEND);
  2. 设置由alpha分量获取源混合因子和目标混合因子的模式:

    1
    2
    //分别设置源混合因子和目标混合因子的获取方式
    void glBlendFunc(GLenum sfactor, GLenum dfactor)

    需要掌握的获取混合因子的方式有:

参数 计算得到的混合因子
GL_ZERO (0,0,0,0)
GL_ONE (1,1,1,1)
GL_SRC_ALPHA (a,a,a,a)
GL_ONE_MINUS_SRC_ALPHA (1-a,1-a,1-a,1-a)

例子和使用方法如下:

image-20220524180720114

离散图元

像素及像素流水线的概念?

帧缓存是一个缓存的集合,它可以看成由多个位面组成。位于帧缓存中每个像素就由对应的一组位表示,它通常是整数字节。

image-20220524181229786

像素流水线是与几何流水线独立并行的另一条流水线。它的输入对象是非几何图元:位图或者像素图,二者都是由矩形的像素数组格式来组织。

位图的概念及主要操作?

位图是只由单个位(0和1)构成的矩形数组。如果是0则为黑色,是1则为 glColor设置的颜色。

位图的操作如下:

  1. 位图的显示

    1
    void glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap)

    width和height分别指位图的宽和高(以位为单位),xorig和yorig指位图原点的相对于当前光栅位置的偏移量,xmove和ymove指绘制完后当前光栅位置的增量;位图的信息储存在bitmap数组中,该数组需要我们自己定义或引入。

  2. 位图的定位:使用函数 glRasterPos2i(x,y)指定当前光栅位置。
  3. 颜色的选择:值为1的位的颜色就是当前光栅颜色。当前光栅颜色就是在 glRasterPos<mark>之前</mark>的第一个 glColor设置的颜色。

像素图的概念和主要操作?

像素图即一般意义上的图像,其中的每个像素包含多位的信息,如RGB等分量。

像素图的主要操作:

  1. 读取将像素从帧缓存读取到内存中。

    1
    void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) 

    作用是从帧缓存中位置(x,y)开始将一个w*h的像素矩阵读取到数组pixels中,pixels就是内存中的一个位置。需要注意,format指RGB或RGBA,type指GL_BYTE等。

  2. 写入:将像素从内存写入到帧缓存中。

    1
    void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)

    作用是将pixels中的数据写到帧缓存中,该操作恰好与 glReadPixels相反。

  3. 拷贝:将像素从帧缓存的一个位置拷贝到另一个位置。

    1
    void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)

    作用是将帧缓存(x,y)处的像素数据拷贝到当前光栅位置。

  4. 缩放:一般情况下,帧缓存中一个像素对应屏幕上的一个像素。有时则需要将像素缩小或放大。

    1
    void glPixelZoom(GLfloat xfactor, GLfloat yfactor)

    作用是设置x和y方向上的伸缩因子。若xfactor=2,那么帧缓存中一个像素在x方向上对应两个像素。

纹理映射

纹理的概念和纹理映射的基本原理?

纹理(Texture)是表示物体表面细节的一幅二维图形,是应用到几何图元上的图像数据。纹理也称为纹理贴图。使用纹理映射能够将几何图元和非几何图元联系起来,充分发挥几何流水线和像素流水线各自的优势。

我们经常使用的纹理是二维纹理,它在纹理坐标系sot(单位化的坐标系)中描述。图像由离散的像素组成,但纹理空间是连续的。

image-20220524201904556

纹理映射就是将纹理空间中的纹理单元(texel)映射为屏幕空间中像素的过程。

image-20220524202246268

纹理映射过程中中两个重要的映射为:

  1. 物体空间——>纹理空间:$(s,t)=F(x,y,z)$
  2. 屏幕空间——>物体空间

纹理映射的基本步骤?

  1. 启用二维纹理:glEnable(GL_TEXTURE_2D);
  2. 定义纹理:创建纹理并将图像数据从处理器内存写到纹理内存中。

    1
    void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)

    target指纹理类型,一般使用的是二维纹理,设为 GL_TEXTURE_2D

    level指多级渐进纹理的层次,0代表最高分辨率。用于模拟场景模糊的情景。

    internalFormat指纹理单元的内部格式,常常取为RGB。

    widthheightborder指纹理的尺寸和边界的宽度。border一般取0。

    format指像素数组的像素格式,这和图像有关,不能乱取。

    type指像素数组中的数据类型。

    pixels指像素数组。

  3. 指定纹理参数:glTexParameterf()
  4. 为顶点赋予纹理坐标:glTexCoord2f(s,t)下面的顶点映射到纹理空间中的坐标(s,t)。直到下次调用 glTexCoord2f()前,当前纹理坐标保持不变。

纹理映射与绘制像素的区别与联系。

  1. 相同点:像素流水线开始,图像数据都是从处理器内存被送进像素流水线。
  2. 不同点:像素流水线末端,纹理映射将图像数据写入纹理内存(在显卡中,一般有许多MB)中,而绘制像素是则将图像数据写入帧缓存中。

纹理映射的参数、参数的意义?

设置纹理参数的函数为:

1
void glTexParameterf(GLenum target, GLenum pname, GLfloat param)

作用是将target类型纹理的pname参数的值设置为param。

纹理参数主要分为两类:覆盖模式滤波模式

覆盖模式的参数:

参数
GL_TEXTURE_WRAP_S GL_CLAMP/GL_REPEAT
GL_TEXTURE_WRAP_T GL_CLAMP/GL_REPEAT
  1. GL_REPEAT即重复模式。当纹理坐标超过(1,1)时,就会取小数部分作为新的纹理坐标。最后表现出来就是纹理在这个方向上不断重复。
  2. GL_CLAMP即钳位模式。若纹理坐标超过1,则强制取1;纹理坐标小于0,则强制取0。

image-20220524210417066

上面程序纹理坐标的取值超过1了,所以需要对超出的部分进行处理。

image-20220524210541268

不同的覆盖模式纹理参数最后纹理映射后的结果不同,上面三种情况要好好理解。

滤波模式:纹理映射后,屏幕上的单个像素可以对应一个纹理单元的一小部分(放大),也可能对应多个纹理单元(缩小)。这种纹理单元和像素不是一一对应的情况就需要滤波

image-20220524213347836

滤波模式的参数:

参数
GL_TEXTURE_MAG_FILTER GL_NEAREST/GL_LINER
GL_TEXTURE_MIN_FILTER GL_NEAREST/GL_LINER

GL_TEXTURE_MAG_FILTER为纹理单元放大,GL_TEXTURE_MIN_FILTER为纹理单元缩小。

过滤方法分为两类:

  1. 点采样 GL_NEAREST:选择与纹理坐标最近的纹理单元,计算量小,执行快。
  2. 线性采样 GL_LINER:选择纹理坐标2x2的纹理单元,计算平均值。映射到几何体表面的的纹理更平滑。

纹理环境的使用?

设置纹理环境的函数:

1
void glTexEnvf(GLenum target, GLenum pname, GLfloat param)

其中target设置为 GL_TEXTURE_ENV,pname设置为 GL_TEXTURE_ENV_MODE,参数值及其意义为:

意义
GL_REPLACR 用纹理颜色替代片元颜色
GL_MODULATE 使用纹理颜色调节片元颜色(适宜光照)
GL_BLENDER 将纹理颜色与片源颜色混合

image-20220524213300830

纹理对象的使用步骤?

使用纹理对象是进行纹理映射的另一种方法。使用纹理对象能避免从处理器内存到纹理内存的重复加载。使用步骤为:

  1. 生成纹理标识符:glGenTexture(GLsizei n, GLuint *name),作用是在数组name中生成n个未使用的纹理标识符。
  2. 创建纹理对象,将纹理数据绑定纹理对象:glBindTexture(GLenum target, GLuint texture)。target一般取 GL_TEXTURE_2D,texture取自name数组。

可编程管线

可编程管线的基本概念?

上面学习都是固定管线的知识,而现在实际使用的是可编程管线

  1. 固定管线(Fixed-Function Pipeline):通常是指在较旧的GPU上实现的渲染流水线,通过OpenGL等图形接口函数,开发者对渲染流水线进行配置,控制权十分有限。
  2. 可编程管线(Programmable Pipeline):随着人们对画面品质和GPU硬件能力的提升,在原有固定管线流程中插入了Vertex ShaderFragment(Pixel) Shader等可编程的阶段,让开发者对管线拥有更大控制权。

顶点着色器和片段着色器的基本概念?

OpenGL的流水线如图所示:

image-20220525090435499

  1. 几何处理器接收从应用程序传来的顶点,对其进行模型视图变换、投影变换、光照计算等,输出装配为几何图元的顶点流
  2. 光栅化器接收从几何处理器传来的图元,将其转化为二维图像。这时二维图像上的每个点都包含了颜色、深度、纹理等各种信息,但是没有储存在帧缓存中(和像素的区别)。光栅化器输出片元
  3. 片段处理器接收一个片段流,对其进行一系列测试后转化为像素储存到帧缓存

在显卡中几何处理器和片段处理器都可以看成专用计算机,主要分别计算顶点和片段的明暗度。我们可以将这两个处理器分别称为顶点着色器片段着色器,控制这两个着色器工作的语言称为着色器语言

早期着色器语言只有汇编语言,后来出现了类C和C++的着色器高级语言,如Cg,GLSL

Shader加入到应用程序的基本流程?

  1. 创建着色器:glCreateShader()
  2. 读取着色器:glShaderSource()
  3. 编译着色器:glCompileShader()
  4. 创建程序对象:glCreateProgram()
  5. 链接程序内容:glAttachShader()&glLinkProgram()
  6. 返回程序对象

着色器程序由字符串表示,可以在这个程序内编写,也可以从外部导入。一个着色器程序示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GLuint createShaderProgram()  {
const char *vshaderSource =
"#version 430 \n"
"void main(void) \n"
"{ gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }";
const char *fshaderSource =
"#version 430 \n"
"out vec4 color; \n"
"void main(void) \n"
"{ color = vec4(0.0, 0.0, 1.0, 1.0); }";
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vShader, 1, &vshaderSource, NULL);
glShaderSource(fShader, 1, &fshaderSource, NULL);
glCompileShader(vShader);
glCompileShader(fShader);
GLuint vfProgram = glCreateProgram();
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
return vfProgram;
}

顶点属性、统一变量的概念和用法?

从C++应用程序向着色器发送数据有两种方式:

  1. 通过一个缓冲区(VBO,顶点缓冲对象)发送到顶点属性(Vertex Attribute)。
  2. 直接赋值到统一变量(Uniform Variable)。

使用顶点属性的步骤:

  1. 创建缓冲区(VBO):glGenBuffers()
  2. 将顶点赋值到缓冲区中:glBufferData()
  3. 启用包含这些顶点的缓冲区:glBindBuffer()
  4. 将缓冲区与一个顶点属性关联起来:glVertexAttribPointer()
  5. 启用顶点属性:glEnableVertexAttribArray()
  6. 使用 glDrawArray()绘制对象
1
2
3
4
5
6
7
8
9
GLuint vbo[2];//声明两个VBOs
glGenBuffers(2,vbo);//创建两个VBOs
//用数据填充一个VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vPositions), vPositions, GL_STATIC_DRAW);//vPosition是一个包含顶点的浮点数组
//将一个VBO与一个顶点属性关联起来
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);//使第0个缓冲区“激活”
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glEnableVertexAttribArray(0);//启用第0个顶点属性

使用统一变量的步骤:

  1. 在着色器中声明一个统一变量:使用 uniform限定符
  2. 获得对统一变量的引用:glGetUniformLocation
  3. 向统一变量发送所需的数据:glUniformMatrix4fv和统一变量的类型有关
1
2
3
uniform mat4 mv_matrix;//声明一个统一变量(在着色器中)
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");//获得对统一变量的引用
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));//从C++向一个统一变量发送数据