01-OpenGL基础渲染

《OpenGL文章汇总》
上一篇绘制了一个三角形《00-OpenGL绘制三角形》,本文接着探讨基础渲染

理论较多,先看个图片,主要搞清楚下面的图像如何绘制出来


0.gif

OpenGL所创建的物体和场景是由更小、更简单的形状组成的,并且是按照各种各种各样独一无二的方式进行排列和组合的,组成3D物体的建筑块,称为图元。在OpenGL中,所有的图元都是一维、二维或三维的物体,从简单的点和线到复杂的多边形都是如此。

1.基础图形管线

一个单独的点就是一个图元,它只需要一个顶点。三角形则是另外一个例子,它是由三个顶点组成的图元。基础渲染管线接受三个顶点并将它们转换成一个三角形。它还可能应用颜色、一个或多个纹理并且移动它们的位置。


image

I.客户机-服务器

管线分为两部分,上半部分是客户机端,下半部分是服务器端。
客户机端是存储在CPU存储器中的,并且在应用程序中执行,或者在主系统内存的驱动程序中执行。驱动程序将渲染命令与数据组合起来,并发送到服务器执行。

客户机不断的将数据块和命令块组合在一起并送入缓冲区,然后这些缓冲区会发送到服务器执行。服务器将执行这些缓冲区的内容,与此同时客户端又做好了发送下一个用于渲染的数据或信息的准备。

II.着色器

着色器必须从源代码中编译和链接到一起(这一点和C、C++程序非常类似)才能使用。最终准备就绪的着色器程序随后在第一阶段构成顶点着色器,在第二阶段构成片段着色器。几何着色器可以(选择性地)安排在两者之间,就像用来将数据来回传递的所有类型的反馈机制一样。

顶点着色器处理从客户机输入的数据,应用变换,或者进行其他类型的数学运算来计算光照效果、位移、颜色值等等。为了渲染一个公有3个顶点的三角形,顶点着色器将执行3次,也就是为每个顶点执行一次。在目前的硬件上有多个执行单元同时运行,这就意味着所有这三个顶点都可以同时进行处理。今天的图形处理器属于大规模并行计算机。
3个顶点都做好了光栅化的准备。Primitive Assembly图元组合框图意在说明3个顶点已经组合在一起,而三角形已经逐个片段地进行了光栅化。每个片段都通过执行片段着色器而进行了填充,片段着色器会输出我们将在屏幕上看到的最终颜色值。
有2中向OpenGL着色器床底渲染数据方法供程序员选择,即属性、uniform值和纹理。

A.属性

属性就是一个对每个顶点都要作改变的数据元素。属性值可以是浮点数、整数或布尔数据。属性总是以四维向量的形式进行内部存储的,即使我们不会使用到所有4个分量。
属性会从本地客户机内存中赋值存储在图形硬件中的一个缓冲区上。属性只供顶点着色器使用,对于片段着色器来说没什么意义。对于每个顶点都有一个实际存储值。

B.Uniform值

属性是一个对每个顶点都要做改变的数据元素。顶点位置本身就是一个属性。属性是一种对于整个批次的属性都取统一值的单个值。它是不变的。通常设置完Uniform变量就紧接着发出渲染一个图元批次的命令。Uniform变量实际上可以无次数限制地使用。Uniform变量一个最常见的应用是在顶点渲染中设置变换矩阵。
Uniform值在本质上像属性一样,可以是浮点值、整数或布尔值,但和属性不同的是,顶点着色器和片段着色器中都可以有Uniform变量。

C.纹理

从顶点着色器和片段着色器中都可以对纹理值进行采样和筛选。片段着色器对一个纹理进行采样,并在一个三角形的表面上应用图形数据。

D.输出

输出数据是作为一个阶段着色器的输出定义的,而在后续阶段的着色器则是作为输入(In)定义的。输出类型的数据可以简单地从一个阶段传递到下一个阶段,也可以以不同的方式插入。客户端的代码接触不到这些内部变量,但是它们在顶点着色器和片段着色器中(还包括可选的几何着色器)都进行了声明。顶点着色器为输出变量分配了一个值,这个值是常量,也可以在图元被光栅化时插入到顶点之间。片段着色器对应的同名输入值接受这个常量或插入值。

2.创建坐标系

I.正投影

所有正投影空间范围内的所有东西都会被显示在屏幕上,而不存在照相机或视点坐标系的概念。通过调用GLFrustum方法来完成

GLFrustum::SetOrthographic(GLfloat xMin,GLfloat xMax,GLfloat yMin,GLfloat yMax,GLfloat zMin,GLfloat zMax);

II.透视投影

透视投影会进行透视除法对距离观察者很远的对象进行缩短和收缩。平截头体是一个金字塔形被截短之后的形状,它的观察方向是从金字塔的尖端到宽阔端,观察者的视点与金字塔的尖端拉开一定距离。
GLFrustum类通过调用SetPerspective方法为我们构建一个平截头体。

GLFrustum::SetPerspective(float fFov,float fAspect,float fNear,float fFar);

其中的参数分别在垂直方向上的视场角度,窗口的宽度和高度的纵横比,以及到近裁剪面和远裁剪面之间的距离。宽度除以高度就能得到窗口或视口的纵横比。

3.使用存储着色器

GLShaderManager在使用前必须进行初始化
shaderManger.InitializeStockShaders();

I.属性

OpenGL支持多达16种可以为每个顶点设置的不同类型参数。这些参数编号为从0到15,并且可以顶点着色器中的任何指定变量相关联。存储着色器为每个变量都使用一致的内部变量命名规则和相同的属性槽。

II.Uniform值

要对几何图形进行渲染,要为对象递交属性矩阵,但首先要绑定到我们想要使用的着色器程序上,并提供程序的Uniform值。GLShaderManager类可以为我们完成这项工作,UseStockShader函数会选择一个存储着色器并提供这个着色器的Uniform值,这些工作通过一次函数调用就能完成。
GLShaderManager::UseStockShader(GLenum shader,...);

A.单位(Identity)着色器

单位(Identity)着色器只是简单的使用默认笛卡尔坐标系(在所有坐标轴上的坐标范围都是-1.0~1.0)。所有片段都应用同一种颜色,几何图形为实心和未渲染的。这种着色器只使用一个属性GLT_ATTRIBUTE_VERTEX。vColor参数包含了要求的颜色。
GLShaderManger::UseStockShader(GLenum shader,...);

B.平面着色器

平面(Flat)着色器将统一着色器进行了扩展,允许为几何图形变换指定一个4x4变换矩阵。左乘模型视图矩阵和投影矩阵,经常被称作"模型视图投影矩阵"。这种着色器只使用一个属性GLT_ATTRIBUTE_VERTEX。

GLShaderManager::UseStockShader(GLT_SHADER_FLAT,GLfloat mvp[16],GLfloat vColor[4]);

C.上色(Shaded)着色器

着色器唯一的Uniform值就是在几何图形中应用的变换矩阵。GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_COLOR在这种着色器中都会使用。颜色值将被平滑的插入顶点之间(成为平滑着色)。

GLShaderManager::UseStockShader(GLT_SHADER_SHADED,GLfloat mvp[16]);

D.默认光源着色器

着色器创造出一种错觉,类似于由位于观察者位置的单漫射光所产生的效果。着色器使对象产生阴影和关照的效果。需要模型视图矩阵、投影矩阵和作为基本色的颜色值等Uniform值。所需的属性有GLT_ATTRIBUTE_VERTEX何GLT_ATTRIBUTE_NORMAL。光照着色器都需要正规矩阵(normal matrix)作为Uniform值。着色器从模型视图矩阵中推导出了正规矩阵。

E.点光源着色器

点光源着色器和默认光源着色器很相似,但是光源位置可能是特定的。这种着色器接受4个Uniform值,即模型视图矩阵、投影矩阵、视点坐标系中的光源位置和对象的节本漫反射颜色。

GLShaderManager::UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,GLfloat mvMatrix[16],GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vColor[4]);

F.纹理替换矩阵

着色器通过给定的模型视图投影矩阵,使用绑定到nTextureUnit指定的纹理单元的纹理对几何图形进行变换。片段颜色是直接从纹理样本中直接获取的。所需的属性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_NORMAL。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_REPLACE,GLfloat mvpMatrix[16],GLint nTextureUnit);

G.纹理调整着色器

着色器将一个基本色乘以一个取自纹理单元nTextureUnit的纹理。所需的属性有GLT_ATTRIBUTE_VERTEX和GLT_ATTRIBUTE_TEXTURE0。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_MODULATE,GLfloat mvpMatrix[16],GLfloat vColor,Glint nTextureUnit);

H.纹理光源着色器

将一个纹理通过漫反射照明计算进行调整(相乘),光线在视觉空间中的位置是给定的。着色器接受5个Uniform值,即模型视图矩阵、投影矩阵、视觉空间中的光源位置、几何图形的基本色和将要使用的纹理单元。
所需的属性有GLT_ATTRIBUTE_VERTEX、GLT_ATTRIBUTE_NORMAL和GLT_ATTRIBUTE_TEXTURE0。
GLShaderManager::UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,GLfloat mvMatrix,GLfloat pMatrix[16],GLfloat vLightPos[3],GLfloat vBaseColor[4],GLint nTextureUnit);

4.将点连接起来

确定视景体中的位置坐标,将这些点、线和三角形从创建的3D空间投影到计算机屏幕上的2D图形则是着色器程序和光栅化硬件要完成的工作。

I.点和线

从7个由定义的几何图元来开始绘制实心几何图形。
这些图元将在一个包含给定图元的所有顶点和相关属性的单个批次中进行渲染。实质上,在一个给定的批次中的所有顶点都会用于组成这些图元中的一个。
GL_POINTS:每个顶点在屏幕上都是一个单独的点
GL_LINES:每一对顶点定义了一个线段
GL_LINE_STRIP:一个从第一个顶点依次经过每个后续顶点而绘制的线条
GL_LINE_LOOP:和GL_LINE_STRIP相同,但最后一个顶点和第一个顶点也连接了起来
GL_TRIANGLES:每三个顶点定义了一个新的三角形
GL_TRIANGLE_STRIP:共用一个条带(strip)上的顶点的一组三角形
GL_TRIANGLE_FAN:以一个圆点为中心呈扇形排列,共用相邻顶点的一组三角形


0.gif

绘制的代码放最后

A.点

点是最简单的图元,每个特定的顶点在屏幕上都仅仅是一个单独的点。默认情况下,点的大小为一个像素。可以通过调用glPointSize(GLfloat size)改变默认点的大小。
void glPointSize(GLfloat size);
glPointSize函数接受一个参数,这个参数指定绘制点的近似尺寸(用像素表示)。

GLfloat sizes[2];//存储支持的点大小范围
GLfloat step;//存储支持的点大小增量
//获取支持的点大小范围和步长(增量)
glGetFloatv(GL_POINT_SIZE_RANGE,sizes);
glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step);

大小值数组由两个元素组成,其中包含了glPointSize的最小和最大可用值。变量step保存了相邻点大小值之间所允许的最小步长。OpenGL规范只需要支持一个点大小,即1.0,指定一个允许范围之外的点大小并不会被认为是一个错误。根据哪个值离指定值最近来使用所允许的最大值或最小值代替。
点大小和其他几何图形不同,它并不会受到透视除法的影响。
当它们离视点更远时,它们看上去并不会变得更小;如果他们离视点更近,他们看上去也不会变得更大一些。点总是正方形的像素,即便使用glPointSize增加点的大小,情况也不会发生变化。我们只能得到更大的正方形!为了获得圆点,我们必须在抗锯齿模式下绘制点。
还可以通过使用程序点大小模式来设置点大小。
glEnable(GL_PROGRAM_POINT_SIZE);
允许我们通过编程在顶点着色器或几何着色器中设置点大小
gl_PointSize = 5.0;

image

B.线

比点更进一步的是独立的线段。一条线段是在两个顶点之间绘制的,所以一批线段应该包括偶数个顶点,每个顶点都是线段的端点。
默认情况下,线段的宽度为一个像素。改变线段宽度的唯一方式是使用函数glLineWidth。
void glLineWidth(GLfloat width);

image

C.线带

线带连续地从一个顶点到下一个顶点绘制线段,以形成一个真正连接点的线条。为了用独立的线段围绕佛罗里达州的轮廓形成一个连续的线条,每个连接顶点都会被选定两次。其中一次是作为一条线段的终点,另一次是作为下一条线段的起点。


image

D.线环

线环是线带的一种简单扩展,在线带的基础上额外增加了一条连接着一批次中最后一个点和第一个点的线段。


image

II.绘制3D三角形

一个多边形就是一个封闭的图形,它可能用颜色或纹理数据进行填充,也可能不进行填充,在OpenGL中,它是所有实体对象构建的基础。

III.单独的三角形

最简单的实体多边形就是三角形,它只有3个边。光栅化硬件最欢迎三角形,而现在三角形是OpenGL中支持的唯一一种多边形了。每三个顶点定义一个新的三角形。

A.环绕

顺序与方向结合来指定顶点的方式成为环绕。OpenGL认为具有逆时针方向环绕的多边形是正面的。
glFrontFace(GL_CW);
GL_CW参数告诉OpenGL顺时针环绕的多边形将被认为是正面的。为了把多边形的正面重新恢复为逆时针环绕,可以在这个函数中使用GL_CCW参数。

B.三角形带

用前3个顶点指定第1个三角形之后,对于接下来的每个三角形,只需要再指定1个顶点。需要绘制大量的三角形时,采用这种方法可以节省大量的程序代码和数据存储空间。第二个优点是提高运算性能和节省带宽。更少的顶点意味着数据从内存传输到图形卡的速度更快,并且顶点着色器需要进行处理的次数也更少。


图片.png

C.三角形扇

使用GL_TRIANGLE_FAN创建一组围绕一个中心点的相连三角形。
原点稍微升高一点,以使它有一点深度。


1

IV.一个简单批次容器

GLTools库中包含一个简单的容器类,叫做GBatch。首先对批次进行初始化,告诉这个类它代表哪种图元,其中包括的顶点数,以及(可选)一组或两组纹理坐标。
至少要复制一个由3分量(x,y,z)顶点组成的数组。还可以选择复制表面法线、颜色和纹理坐标。
完成上述工作之后,可以调用End来表明已经完成了数据复制工作,并且将设置内部标记,以通知这个类包含哪些属性。一旦调用end函数就不能添加新的属性了。
在RenderScene函数中,选择了适当的存储着色器并调用Draw函数

    //Blue background
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    
    shaderManager.InitializeStockShaders();
    //Load up a triangle
    
    GLfloat vVerts[] = {
      -0.5f,0.0f,0.0f,
       0.5f,0.0f,0.0f,
       0.0f,0.5f,0.0f,
    };
    
    triangleBatch.Begin(GL_TRIANGLES, 3);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.End();
    GLfloat vRed[] = {1.0f,0.0f,0.0f,1.0f};
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
    triangleBatch.Draw();
    
    //Perform the buffer swap to display the back buffer
    glutSwapBuffers();

V.不希望出现的几何图形

默认情况下,我们所渲染的每个点、线或三角形都会在屏幕上进行光栅化,并且会按照在组合图元批次时指定的顺序进行排列,在某些特殊情况下会产生问题。
如果我们绘制一个有很多个三角形组成的实体对象,那么第一个绘制的三角形可能会被后面绘制的三角形覆盖。


2

对于这个问题,一个可能的解决办法是,对这些三角形进行排序,并且首先渲染哪些较远的三角形,再在它们上方渲染那些较近的三角形。这种方式成为油画法,在计算机图形处理中是非常低效的。主要原因有两个。其一是必须对任何发生集合图形重叠地方的每个像素进行两次写操作,而在存储其中进行写操作会使速度变慢。其二是对独立的三角形进行排序的开销会过高。

A.正面和背面剔除

对正面和背面三角形进行区分的原因之一就是为了进行剔除。在渲染的图元装配阶段就整体抛弃了一些三角形,并且没有执行任何不恰当的光栅化操作。
表面剔除按如下方式开启
glEnable(GL_CULL_FACE);
按照如下方式关闭
glDisable(GL_CULL_FACE)
剔除正面还是背面由另外一个函数glCullFace控制的。
void glCullFace(GLenum mode)
mode参数的可用值为GL_FRONT、GL_BACK或GL_FRONT_AND_BACK。要消除不透明物体的内部几何图形就需要两行代码。

glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
3

B.深度测试

深度测试是另外一种高效消除隐藏表面的技术。在绘制一个像素时,将一个值(成为z值)分配给它,这个值表示它到观察者的距离。当另外一个像素需要在屏幕上的同样位置进行绘制时,新像素的z值将与已经存储的像素的z值进行比较。如果新像素的z值比较大,那么它距离观察者就比较近,这样就在原来的像素上面,所以原来的像素就会被新的像素覆盖。如果新像素的z值更低,那么它就必须位于原来像素的后面,不能遮住原来的像素。在内部,这个任务是通过深度缓冲区实现的,它存储了屏幕上每个像素的深度值。在内部,这个任务是通过深度缓冲区实现的,它存储了屏幕上每个像素的深度值。
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
启用深度测试
glEnable(GL_DEPTH_TEDT);
未使用深度测试前,会有问题,一部分与离我们更近却恰好先进行绘制的另一部分重叠。

5

image

如果没有深度缓冲区,那么启用深度测试的命令将被忽略。
4.gif

首先绘制那些离观察者较近的对象,然后再绘制那些较远的对象。深度测试将消除那些应该被已存在像素覆盖的像素。

C.多边形模式

多边形(含三角形)不一定是实心的。在默认情况下,多边形是作为实心图形绘制的,但我们可以通过将多边形指定为显示轮廓或只有点(只显示顶点)来改变这种行为。函数glPolygonMode允许将多边形渲染成实体、轮廓或只有点。
void glPolygonMode(GLenum face,GLenum mode);
和表面剔除一样,face参数的可用值为GL_FRONT、GL_BACK或GL_FRONT_AND_BACK。而mode参数的可用值为GL_FILL(默认值)、GL_LINE或GL_POINT。
通过调用glPolygonMode将多边形正面和背面设为线框模式,就能实现这种线框渲染。
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);

6

VI.多边形偏移

前面深度测试是基于z值的不同而达到绘图效果的不同,特殊情况若想在一架打飞机上绘制一架小飞机,则z值完全相等,若我们想要绘制实心几何图形但又要突出它的边时。
为了看到三角形的边,使用glPolygonMode来绘制条带,会生成粗黑线边缘。

图片.png

可以使用glPolygonOffset函数使我们可以调节片段的深度值,这样就能使深度值产生偏移而并不实际改变3D空间中的物理位置。
void glPolygonOffset(GLfloat factor,GLfloat units);
应用到片段上的总偏移可以通过下面的方程式表示。
Depth Offset = (DZ x factor) + (r x units);
其中DZ是深度值(z值)相对于多边形屏幕区域的变化量,而r则是使深度缓冲区值产生变化的最小值。并没有一个硬性规定能够找到一个万无一失的值,具体应用的时候需要试验一下。

VII.裁剪

另外一种提高渲染性能的方法是只刷新屏幕上发生变化的部分。将OpenGL渲染限制在窗口中一个较小的矩形区域中。OpenGL允许我们再将要进行渲染的窗口中指定一个裁剪框。在默认情况下,裁剪框与窗口同样大小,并且不会进行裁剪测试。
void glScissor(GLint x,GLint y,GLsizei width,GLsizei height);

image

5.混合

OpenGL渲染时会把颜色值放在颜色缓冲区中。每个片段的深度值也是放在深度缓冲区中的。当深度测试被关闭(禁用)时,新的颜色值简单地覆盖颜色缓冲区中已经存在的其他值。当深度测试被打开(启用)时,新的颜色片段只有当它们比原来的值更接近邻近的裁剪平面时才会替换原来的颜色片段。如果打开了OpenGL的混合功能,那么下层的颜色值就不会被清除。
glEnable(GL_BLEND);

I.组合颜色

当混合功能被启用时,原颜色和目标颜色的组合方式是由混合方程式控制的。在默认情况下,混合方程式如下:
Cf = (Cs * S) + (Cd * D);
Cf是最终计算产生的颜色,Cs是源颜色,Cd则是目标颜色,S和D分别是源和目标混合因子。混合因子由如下函数进行设置。
glBlendFunc(GLenum S,GLenum D);

7

II.改变混合方程式

有五种混合方程式可供选择
void glBlendEquation(GLenum mode);
GL_FUNC_ADD:Cf = (Cs * S) + (Cd * D)
GL_FUNC_SUBTRACT: Cf = (Cs * S) - (Cd * D)
GL_FUNC_REVERSE_SUBTRACT:Cf = (Cd * D) - (Cs * S)
GL_MIN:Cf = min(Cs,Cd)
GL_MAX:Cf = max(Cs,Cd)
除了glBlendFunc之外,还可以利用下面的函数更加灵活的进行选择。
void glBlendFuncSeparate(GLenum srcRGB,GLenum dstRGB,GLenum srcAlpha,GLenum dstAlpha);

其中glBlendFunc函数指定了源和目标RGBA值的混合函数,而glBlendFuncSeparate函数则允许为RGB和alpha成分单独指定混合函数。
void glBlendColor(GLclampf red,GLclamp green,Glclampf blue,GLclampf alpha);

III.抗锯齿

OpenGL混合功能的另一个用途是抗锯齿。
为了消除图元之间的锯齿状边缘,OpenGL使用混合功能来混合片段的颜色,也就是把像素的目标颜色与周围像素的颜色进行混合。从本质上来说,在任何图元的边缘上,像素颜色会稍微延伸到相邻的像素。
开启抗锯齿功能非常简单。必须启用混合功能。
还需确保把混合方程式设置为GL_ADD,在启用混合功能并选择正确的混合函数以及混合方程式之后,可以选择调用glEnable函数对点、直接和(或)多边形(任何实心图元)进行抗锯齿处理。

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POINT_SMOOTH);//Smooth out points
glEnable(GL_LINE_SMOOTH);//Smooth out lines
glEnable(GL_POLYGON_SMOOTH);//Smooth out polygon edges

在抗锯齿和正常渲染模式间切换


8
//对菜单选择做出反应,正确地重置标志
// Reset flags as appropriate in response to menu selections
void ProcessMenu(int value)
    {
    switch(value)
        {
        case 1:
            // Turn on antialiasing, and give hint to do the best
            // job possible.
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
            glEnable(GL_POINT_SMOOTH);
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
            glEnable(GL_LINE_SMOOTH);
            glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
            glEnable(GL_POLYGON_SMOOTH);
            glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
            break;

        case 2:
            // Turn off blending and all smoothing
            glDisable(GL_BLEND);
            glDisable(GL_LINE_SMOOTH);
            glDisable(GL_POINT_SMOOTH);
            break;

        default:
            break;
        }
        
    // Trigger a redraw
    glutPostRedisplay();
    }

IV.多重采样

抗锯齿处理的最大优点之一就是能够使多边形的边缘更为平滑,使渲染效果显得更为自然和逼真。点和直线的平滑处理是得到广泛支持的,但遗憾的是多边形的平滑处理并没有在所有的平台上都得到实现。
OpenGL新增了一个特性,成为多重采样,可以用来解决多边形的平滑处理。
多重采样会在已经包含了颜色、深度和模板值的帧缓冲区就会添加一个额外的缓冲区。所有的图元在每个像素上都进行了多次采样,其结果就存储在这个缓冲区中。每次当这个像素进行更新时,这些采样值进行解析,以产生一个单独的值。
GLUT提供了一个位段(GLUT_MULTISAMPLE),允许请求这种帧缓冲区。为了请求一个多重采样、完全颜色、带深度的双缓冲帧缓冲区,可以调用:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH |GLUT_MULTISAMPLE);
可以使用glEnable/glDisable组合(使用GL_MULTISAMPLE标记)打开或关闭多重采样:

glEnable(GL_MULTISAMPLE);或glDisable(GL_MULTISAMPLE);

使用多重采样时,就不能同时使用点和直线的平滑处理。在一种特定的OpenGL实现中,点和直线如果采用平滑处理可能会比使用多重采样效果更好。当绘制点和直线时,可以关闭多重采样,在绘制其他实心几何图形时再打开多重采样。

glDisable(GL_MULTISAMPLE);
glEnable(GL_POINT_SMOOTH);
//Draw some smooth points
glDisable(GL_POINT_SMOOTH);
glEnable(GL_MULTISAMPLE);

当然,如果没有多重采样缓冲区,OpenGL就当做GL_MULTISAMPLE是被禁用的。
多种采样缓冲区在默认情况下使用片段的RGB值,并不包括颜色的alpha成分。可以通过调用glEnable来修改这个行为。

GL_SAMPLE_ALPHA_TO_COVERAGE ------使用alpha值
GL_SAMPLE_ALPHA_TO_ON -----将alpha值设为1并使用它
GL_SAMPLE_COVERAGE -----使用glSampleCoverage所设置的值
当使用GL_SAMPLE_COVERAGE时,glSampleConverage函数允许使用一个特定的值,它是与片段覆盖值进行按位与操作的结果。
void glsampleCoverage(GLclampf value,GLboolean invert);
这种对多重采样操作的优化并不是严格由OpenGL规范所规定的,其确切的结果可能因不同的OpenGL实现而异。

// Called to draw scene
void RenderScene(void)
    {
        // Clear blue window
        glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Now set scissor to smaller red sub region
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        glScissor(100, 100, 600, 400);
        glEnable(GL_SCISSOR_TEST);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Finally, an even smaller green rectangle
        glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
        glScissor(200, 200, 400, 200);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Turn scissor back off for next render
        glDisable(GL_SCISSOR_TEST);

    glutSwapBuffers();
    }

七种图元代码

GLShaderManager     shaderManager;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLFrame             cameraFrame;
GLFrame             objectFrame;
GLFrustum           viewFrustum;

GLBatch             pointBatch;
GLBatch             lineBatch;
GLBatch             lineStripBatch;
GLBatch             lineLoopBatch;
GLBatch             triangleBatch;
GLBatch             triangleStripBatch;
GLBatch             triangleFanBatch;

GLGeometryTransform transformPipeline;
M3DMatrix44f        shadowMatrix;


GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f };
GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };


// Keep track of effects step
int nStep = 0;


///////////////////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering context. 
// This is the first opportunity to do any OpenGL related tasks.
void SetupRC()
    {
    // Black background
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f );

    shaderManager.InitializeStockShaders();

    glEnable(GL_DEPTH_TEST);

    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

    cameraFrame.MoveForward(-15.0f);
    
    //////////////////////////////////////////////////////////////////////
    // Some points, more or less in the shape of Florida
    GLfloat vCoast[24][3] = {{2.80, 1.20, 0.0 }, {2.0,  1.20, 0.0 },
                            {2.0,  1.08, 0.0 },  {2.0,  1.08, 0.0 },
                            {0.0,  0.80, 0.0 },  {-.32, 0.40, 0.0 },
                            {-.48, 0.2, 0.0 },   {-.40, 0.0, 0.0 },
                            {-.60, -.40, 0.0 },  {-.80, -.80, 0.0 },
                            {-.80, -1.4, 0.0 },  {-.40, -1.60, 0.0 },
                            {0.0, -1.20, 0.0 },  { .2, -.80, 0.0 },
                            {.48, -.40, 0.0 },   {.52, -.20, 0.0 },
                            {.48,  .20, 0.0 },   {.80,  .40, 0.0 },
                            {1.20, .80, 0.0 },   {1.60, .60, 0.0 },
                            {2.0, .60, 0.0 },    {2.2, .80, 0.0 },
                            {2.40, 1.0, 0.0 },   {2.80, 1.0, 0.0 }};
    
    // Load point batch
    pointBatch.Begin(GL_POINTS, 24);
    pointBatch.CopyVertexData3f(vCoast);
    pointBatch.End();
    
    // Load as a bunch of line segments
    lineBatch.Begin(GL_LINES, 24);
    lineBatch.CopyVertexData3f(vCoast);
    lineBatch.End();
    
    // Load as a single line segment
    lineStripBatch.Begin(GL_LINE_STRIP, 24);
    lineStripBatch.CopyVertexData3f(vCoast);
    lineStripBatch.End();
    
    // Single line, connect first and last points
    lineLoopBatch.Begin(GL_LINE_LOOP, 24);
    lineLoopBatch.CopyVertexData3f(vCoast);
    lineLoopBatch.End();
    
    // For Triangles, we'll make a Pyramid
    GLfloat vPyramid[12][3] = { -2.0f, 0.0f, -2.0f, 
                                2.0f, 0.0f, -2.0f, 
                                0.0f, 4.0f, 0.0f,
                                
                                2.0f, 0.0f, -2.0f,
                                2.0f, 0.0f, 2.0f,
                                0.0f, 4.0f, 0.0f,
                                
                                2.0f, 0.0f, 2.0f,
                                -2.0f, 0.0f, 2.0f,
                                0.0f, 4.0f, 0.0f,
                                
                                -2.0f, 0.0f, 2.0f,
                                -2.0f, 0.0f, -2.0f,
                                 0.0f, 4.0f, 0.0f};
    
    triangleBatch.Begin(GL_TRIANGLES, 12);
    triangleBatch.CopyVertexData3f(vPyramid);
    triangleBatch.End();
    

    // For a Triangle fan, just a 6 sided hex. Raise the center up a bit
    GLfloat vPoints[100][3];    // Scratch array, more than we need
    int nVerts = 0;
    GLfloat r = 3.0f;
    vPoints[nVerts][0] = 0.0f;
    vPoints[nVerts][1] = 0.0f;
    vPoints[nVerts][2] = 0.0f;

    for(GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) {
        nVerts++;
        vPoints[nVerts][0] = float(cos(angle)) * r;
        vPoints[nVerts][1] = float(sin(angle)) * r;
        vPoints[nVerts][2] = -0.5f;
        }

    // Close the fan
    nVerts++;
    vPoints[nVerts][0] = r;
    vPoints[nVerts][1] = 0;
    vPoints[nVerts][2] = 0.0f;
        
    // Load it up
    triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
    triangleFanBatch.CopyVertexData3f(vPoints);
    triangleFanBatch.End();     
        
    // For triangle strips, a little ring or cylinder segment
    int iCounter = 0;
    GLfloat radius = 3.0f;
    for(GLfloat angle = 0.0f; angle <= (2.0f*M3D_PI); angle += 0.3f)
        {
        GLfloat x = radius * sin(angle);
        GLfloat y = radius * cos(angle);
            
        // Specify the point and move the Z value up a little   
        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = -0.5;
        iCounter++;

        vPoints[iCounter][0] = x;
        vPoints[iCounter][1] = y;
        vPoints[iCounter][2] = 0.5;
        iCounter++;            
        }

    // Close up the loop
    vPoints[iCounter][0] = vPoints[0][0];
    vPoints[iCounter][1] = vPoints[0][1];
    vPoints[iCounter][2] = -0.5;
    iCounter++;
    
    vPoints[iCounter][0] = vPoints[1][0];
    vPoints[iCounter][1] = vPoints[1][1];
    vPoints[iCounter][2] = 0.5;
    iCounter++;            
        
    // Load the triangle strip
    triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter);
    triangleStripBatch.CopyVertexData3f(vPoints);
    triangleStripBatch.End();    
    }


/////////////////////////////////////////////////////////////////////////
void DrawWireFramedBatch(GLBatch* pBatch)
    {
    // Draw the batch solid green
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
    pBatch->Draw();
    
    // Draw black outline
    glPolygonOffset(-1.0f, -1.0f);      // Shift depth values
    glEnable(GL_POLYGON_OFFSET_LINE);

    // Draw lines antialiased
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    // Draw black wireframe version of geometry
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glLineWidth(2.5f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();
    
    // Put everything back the way we found it
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
    }


///////////////////////////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
    {    
    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    modelViewMatrix.PushMatrix();
        M3DMatrix44f mCamera;
        cameraFrame.GetCameraMatrix(mCamera);
        modelViewMatrix.MultMatrix(mCamera);

        M3DMatrix44f mObjectFrame;
        objectFrame.GetMatrix(mObjectFrame);
        modelViewMatrix.MultMatrix(mObjectFrame);

        shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

        switch(nStep) {
            case 0:
                glPointSize(4.0f);
                pointBatch.Draw();
                glPointSize(1.0f);
                break;
            case 1:
                glLineWidth(2.0f);
                lineBatch.Draw();
                glLineWidth(1.0f);
                break;
            case 2:
                glLineWidth(2.0f);
                lineStripBatch.Draw();
                glLineWidth(1.0f);
                break;
            case 3:
                glLineWidth(2.0f);
                lineLoopBatch.Draw();
                glLineWidth(1.0f);
                break;
            case 4:
                DrawWireFramedBatch(&triangleBatch);
                break;
            case 5:
                DrawWireFramedBatch(&triangleStripBatch);
                break;
            case 6:
                DrawWireFramedBatch(&triangleFanBatch);
                break;
            }
        
    modelViewMatrix.PopMatrix();

    // Flush drawing commands
    glutSwapBuffers();
    }


// Respond to arrow keys by moving the camera frame of reference
void SpecialKeys(int key, int x, int y)
    {
    if(key == GLUT_KEY_UP)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    
    glutPostRedisplay();
    }
    
///////////////////////////////////////////////////////////////////////////////
// A normal ASCII key has been pressed.
// In this case, advance the scene when the space bar is pressed
void KeyPressFunc(unsigned char key, int x, int y)
    {
    if(key == 32)
        {
        nStep++;

        if(nStep > 6)
            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;
        case 4:
            glutSetWindowTitle("GL_TRIANGLES");
            break;
        case 5:
            glutSetWindowTitle("GL_TRIANGLE_STRIP");
            break;
        case 6:
            glutSetWindowTitle("GL_TRIANGLE_FAN");
            break;
        }
                
    glutPostRedisplay();
    }

///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.
void ChangeSize(int w, int h)
    {
    glViewport(0, 0, w, h);
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    modelViewMatrix.LoadIdentity();
    }

///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
    {
    gltSetWorkingDirectory(argv[0]);
    
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("GL_POINTS");
    glutReshapeFunc(ChangeSize);
    glutKeyboardFunc(KeyPressFunc);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
        
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
        }
    

    SetupRC();

    glutMainLoop();
    return 0;
    }

你可能感兴趣的:(01-OpenGL基础渲染)