4.OpenGL颜色、像素和片元(笔记)

基本颜色理论

OpenGL当中,通常会在RGB三个颜色分量之外,在增加第四个alpha分量,因此可以称为RGBA颜色空间。作为RGB的一种补充,OpenGL还支持sRGB颜色空间。

缓存及其用途

帧缓存(通常也就是屏幕)是由矩形的像素数组组成的,每个像素都可以在图形对应的点上显示一小块方形的颜色值。经过光栅化阶段,也就是执行片元着色器之后,得到的数据还不是真正的像素——只是候选的片元。每个片元都包含与像素位置对应的坐标数据,以及颜色和深度的存储值。

如图所示,OpenGL窗口左下角的像素通常也就是(0,0)位置的像素,这个像素对应该像素占据的1x1区域左下角窗口坐标。通常来说,像素(x,y)填充的区域是以x为左侧,x+1为右侧,y为底部,而y+1为顶部的一处矩形区域。

在这里插入图片描述

颜色缓存只是记录像素信息的多个缓存中的一个。实际上,一个像素可能会关联多个颜色缓存。一个显示系统的帧缓存(framebuffer)中包含了所有这些缓存类型,也可以在自己的应用程序中使用多个帧缓存。除了主颜色缓存之外,通常不需要直接观察其他缓存的内容;而是使用它们来执行一些特定的任务,比如隐藏表面的去除、模板操作、动态纹理生成,等等。

OpenGL系统中通常包含以下几种类型的缓存:

  • 一个或者多个可用的颜色缓存(color buffer)。
  • 深度缓存(depth buffer)
  • 模板缓存(stencil buffer)

所有这些缓存都是集成到帧缓存当中的,尽管我们可以自由决定需要用到哪些缓存。当启动应用程序之后,我们使用的是默认的帧缓存(default framebuffer),它是与应用程序窗口关联的帧缓存。默认帧缓存总是会包含一个颜色缓存。

  • 颜色缓存

颜色缓存是我们通常进行绘制的缓存对象。它包含RGB或者sRGB形式的颜色数据,也可能包含帧缓存中每个像素的alpha值。帧缓存中可能会含有多个颜色缓存。其中,默认帧缓存的"主"颜色缓存需要特别对待,因为它是与屏幕上窗口相关联的,所有绘制到其中的图像都会直接显示到屏幕上,而所有其他的颜色缓存都是与屏幕无关的。

颜色缓存中的像素,可能是采用每个像素存储单一颜色值的形式,也可能从逻辑上被划分为多个子像素,因此启用了一个名为**多重采样(multisampling)**的反走样技术形式。

在动画制作中广泛应用到了双重缓存技术。双重缓存的实现需要将主颜色缓存划分为两个部分:直接在窗口中显示的前置缓存(front buffer),以及用来渲染新图像的后备缓存(back buffer)。当我们执行交换缓存操作的时候(例如调用glfwSwapBuffers()函数),前置、后备缓存将进行交换。

只有默认帧缓存中的主颜色缓存可以使用双重缓冲的特性。

  • 深度缓存

深度缓存为每个像素保存了一个深度值,用来判断三维空间中物体的可见性。这里的深度是物体与观察者眼睛的距离,因此深度缓存值较大的像素会被深度缓存值较小的像素所覆盖。深度缓存的特性可以通过”深度测试“的方式来改变。深度缓存也叫做z缓存(z-buffer,这是因为我们通过x和y值来描述屏幕的水平和垂直方向的信息,所以这里使用z来描述垂直于屏幕的距离值)。

  • 模板缓存

模板缓存可以用来限制屏幕特定区域的绘制。可以把它想象成一个带有孔洞花纹的硬纸板,将它按在纸面上再使用喷绘进行喷绘,就可以得到非常精确的花纹图案了。

举例来说,模板缓存的一个经典用途就是模拟汽车的后视镜视角。首先将镜子本身的形状渲染到模板缓存中,然后绘制整个场景。此时模板缓存可以阻止所有在镜子形状之外的绘制操作。

缓存的清除

  • glClearBufferfv()

    void glClearBufferfv(GLenum buffer,GLint drawbuffer,const GLfloat *value);
    

    buffer参数设置为GL_COLOR,关联的颜色缓存将被清除;后续会同时绘制到多个颜色缓存的方法,现在只需设置drawbuffer为0即可。value是一个指向四个浮点数组成的数组的指针,它表示清除颜色缓存之后的初始颜色值,浮点数值依次表示红、绿、蓝和alpha。

    buffer参数设置为GL_DEPTH,清除深度缓存;drawbuffer必须设置为0(因为我们只有一个深度缓存),而value指向一个浮点数,即清除深度缓存之后的初始深度值。

  • 替代版本,清除模板缓存(整型数据),或同时清除深度和模板缓存

    void glClearBufferiv(GLenum buffer,GLint drawbuffer,const GLint* value);
    void glClearBufferuiv(GLenum buffer,GLint drawbuffer,const GLuint* value);
    void glClearBufferfi(GLenum buffer,GLint drawbuffer,GLfloat depth,GLint stencil);
    

    使用glClearBufferiv()或者glClearBufferuiv()来清除整型类型的缓存。glClearBufferiv()可以用来清除模板的缓存。value表示一个有符号或者无符号整数数组的指针,其中包含了缓存清除后的数值。

    glClearBuffer()可以同时清除深度和模板缓存。这里的buffer必须设置为GL_DEPTH_STENCIL,且drawbuffer必须是0。

缓存的掩码

在OpenGL向颜色、深度或者模板缓存写入数据之前,可以对数据执行一次掩码操作。

void glColorMask(GLboolean red,GLboolean green,GLboolean blue,GLboolean alpha);
void glColorMaski(GLuint buffer,GLboolean red,GLboolean green,GLboolean blue,GLboolean alpha);
void glDepthMask(GLboolean flag);
void glStencilMask(GLboolean mask);
void glStencilMaskSeparate(GLenum face,GLuint mask);

它们可设置用于控制写入不同缓存的掩码。

如果glDepthMask()的参数flag为GL_TRUE,那么深度缓存可以写入;否则,无法写入。glStencilMask()的mask参数用于与模板值进行按位“与”操作,如果对应位操作的结果为1,那么像素的模板值可以写入;如果为0则无法写入。

所有GLboolean掩码默认值均为GL_TRUE,而所有GLuint掩码的默认值都是1。

glStencilMaskSeparate()可以为多边形的正面和背面设置不同的模板掩码值。

如果需要渲染到多个颜色缓存,glColorMaski()可以对特定的缓存对象(通过buffer参数指定)设置颜色掩码。

glStencilMask()所设置的掩码用于控制写入的模板位平面。这个掩码与glStencilFunc()中的第三个掩码参数没有关系,后者用于指定模板函数所对应的为平面值。

颜色与OpenGL

颜色表达与OpenGL

OpenGL内部会使用浮点数来表示一个颜色分量,并且负责维护它的精度,直到数据保存到帧缓存中为止。除非另有设置,否则片元着色器的输入总是浮点数类型,为片元颜色设置的数值,并且这些值总是限制在[0.0,1.0]的范围内——称为归一化数值(normalized value)。

这样的颜色写入到帧缓存后,会被映射到帧缓存所支持的数据区间内。例如,如果帧缓存的每个红色、绿色和蓝色分量都有8位,那么最后颜色分量的区间范围为[0,255]。

用户应用程序提供给OpenGL的数据基本上都是C语言的数据类型(例如int或者float)。可以选择让OpenGL自动将非浮点数类型转换为归一化的浮点数。

将输入数据转换为归一化的浮点数值
OpenGL类型OpenGL枚举量最小值映射后的最小值最大值映射后的最大值
GLbyteGL_BYTE-128-1.01271.0
GLshortGL_SHORT-32 768-1.032 7671.0
GLintGL_INT-2 147 483 648-1.02 147 483 6471.0
GLubyteGL_UNSIGNED_BYTE00.02551.0
GLushortGL_UNSIGNED_SHORT065 5351.0
GLuintGL_UNSIGNED_INT00.04 294 967 2951.0
GLfixedGL_FIXED-32 767-1.032 7671.0

平滑数据插值

与其他顶点数据类似,颜色数据也保存到顶点缓存对象(VBO)当中。当数据从顶点着色器传递到片元着色器的时候,OpenGL会沿着被渲染的图元的每个面对数据进行平滑的插值。在片元着色器中使用这些数据来生成颜色时,可以在屏幕上得到平滑着色的效果。也就是Gouraud着色

片元的测试与操作

绘制几何体时,OpenGL会按照如下顺序处理管线:

  1. 执行当前绑定的顶点着色器
  2. 然后依次是细分和几何着色器(如果它们存在于当前流水线中)
  3. 然后将最终几何体装配为图元并送入光栅化阶段,这里计算窗口中哪些像素受到了几何体的影响
  4. 当OpenGL确定生成一个片元时,将执行片元着色器,然后经过几个处理阶段,判断片元是否可以作为像素绘制到帧缓存中,以及控制绘制的方式。

这些测试和操作大部分可以通过glEnable()glDisable()来分别启用和禁止。

测试和操作的发生顺序如下:

  1. 剪切测试(scissor test)
  2. 多重采样的片元操作
  3. 模板测试(stencil test)
  4. 深度测试(depth test)
  5. 融混(blending)
  6. 逻辑操作

剪切测试

片元可见性判断的第一个附加测试,叫做剪切测试(scissor test)。我们将程序窗口中的一个矩形区域称作一个剪切盒(scissor box),并且将所有的绘制操作都限制在这个区域内。如果片元位于这个矩形区域内,则通过剪切测试。

使用glEnable()设置参数为GL_SCISSOR_TEST开启测试。

void glScissor(GLint x,GLint y,GLsizei width,GLsizei height);

设置剪切矩形(或者剪切盒)的位置与大小。函数的参数定义了矩形的左下角(x,y)以及宽度(width)和高度(height)。所有位于矩形之内的像素都会通过剪切测试。默认条件下,剪切矩形与窗口的大小是相等的,并且剪切测试是关闭的。

若已开启测试,那么所有渲染(包括窗口的清除)都被限制在剪切盒区域内,这一点与视口的设置不同,后者不会限制屏幕的清除操作。

gllsEnabled()与参数GL_SCISSOR_TEST,用于判断是否开启剪切测试;glGetIntegerv()与参数GL_SCISSOR_BOX,获取剪切矩形的参数。

多重采样的片元操作

默认情况下,多重采样在计算片元的覆盖率时不会考虑alpha的影响。不过,如果使用glEnable()开启下面的某个特定模式,那么片元的alpha值将被纳入到计算过程中,这里假设多重采样本身已经开启,并且帧缓存已经关联一个多重采样的缓存数据。

可用的特定模式如下所示:

  • GL_SAMPLE_ALPHA_TO_COVERAGE使用片元的alpha值来计算最后的采样覆盖率,并且这一过程与具体的硬件实现无关。

  • GL_SAMPLE_ALPHA_TO_ONE将片元的alpha值设置为最大的alpha值,然后使用这个值来进行覆盖率的计算。如果GL_SAMPLE_ALPHA_TO_COVERAGE也被启动的话,那么系统将使用替代前的片元的alpha值,而不是替代值1.0。

  • GL_SAMPLE_COVERAGE将使用glSampleCoverage()中设置的数值,与覆盖率计算的结果进行合并(“与”操作)。此外,由此生成的样本掩码也可以通过glSampleCoverage()函数的invert参数来进行反转。

    void glSampleCoverage(GLfloat value,GLboolean invert);
    

    设置多重采样覆盖率的参数,以正确结算alpha值。如果开启GL_SAMPLE_COVERAGE或者GL_SAMPLE_ALPHA_TO_COVERAGE,那么value是一个临时的采样覆盖率。invert是一个布尔变量,用于设置这个临时覆盖率是否需要先进行位反转,然后再与片元覆盖率进行合并("与"操作)。

  • GL_SAMPLE_MASK设置了一个精确的位掩码来计算和表达覆盖率(而不是让OpenGL自己去生成这个掩码)。这个掩码与帧缓存的每个采样值都有一位(bit)进行对应,它将与片元的采样覆盖值再次进行"与"操作。采样掩码是通过glSampleMaski()函数来设置的。

    void glSampleMaski(GLuint index,GLbitfield mask);
    

    设置一个32位的采样掩码mask。如果当前帧缓存包含了多于32个采样数,那么采样掩码的长度可能是多个32位大小的WORD字段组成,其中第一个WORD表示前32位的数据,第二个WORD表示之后32位的数据,以此类推。掩码本身的索引位置通过index来设置,而新的掩码值通过mask来设置。当采样结果准备写入到帧缓存时,只有当前采样掩码值中对应位的数据才会被更新,而其他的数据将会被丢弃。

    采样掩码也可以在片元着色器中通过写入gl_SampleMask变量来设置,后者也是一个32位数据的数组。

模板测试

只有在建立窗口的过程中预先请求缓存的前提下,才能够使用模板测试(如果没有模板缓存,模板测试总是通过的)。

模板测试过程中会取像素在模板缓存中的值,然后与一个参考值进行比较。根据测试结果的不同,我们可以对模板缓存中的数据进行更改。

可以使用各种特定的比较函数、参考值,然后使用glStencilFunc()和glStencilOp()命令还完成修改的操作。

void glStencilFunc(GLenum func,GLint ref,GLuint mask);
void glStencilFuncSperate(GLenum face,GLenum func,GLint ref,GLuint mask);

设置比较函数func、参考值ref以及掩码mask以完成模板测试。参考值将与模板缓存中已有的值进行比较,但是在此之前需要与mask参数进行按位"与"操作,丢弃结果为0的位平面。比较函数可以是GL_NEVERGL_ALWAYSGL_LESSGL_LEQUALGL_EQUALGL_GEQUALGL_GREATER或者GL_NOTEQUAL中的一种。

举例来说,如果函数为GL_LESS,那么当ref比较模板缓存中的数值更小时,片元将通过测试。如果模板缓存包含了s个位平面,那么mask参数中较低的s个位数据将分别与模板缓存中的值,以及参考值进行"与"操作,然后再执行具体的比较。

经过掩码操作的数据均被解析成非负数据。模板测试的开启和禁止是通过glEnable()glDisable(),以及参数GL_STENCIL_TEST来完成的。默认情况下,func为GL_ALWAYS,ref为0,而mask的所有位均为1,并且模板测试默认为禁止。

glStencilFuncSeparate()允许我们为多边形的正面和背面单独设置模板函数参数。

void glStencilOp(GLenum fail,GLenum zfail,GLenum zpass);
void glStencilOpSparate(GLenum face,GLenum fail,GLenum zfail,GLenum zpass);

设置当片元通过或者没有通过模板测试的时候,要如何处理模板缓存中的数据。

三个函数参数fail、zfail和zpass都可以设置为GL_KEEPGL_ZEROGL_REPLACECL_INCRGL_INCR_WRAPGL_DECRGL_DECR_WRAP或者GL_INVERT的一个。

它们以此等价于保持当前值、替换为0值、替换为参考值、增加1(使用饱和运算)、增加1(不使用饱和运算)、减少1(使用饱和运算)、减少1(不使用饱和运算),以及按位反转。加1和减1函数的结果值总是落在0到最大无符号整数值(如果模板缓存有s位,那么就是2 S - 1)的区间内。

如果片元没有通过模板测试,将执行fail函数;如果通过了模板测试,但是没有通过深度测试,那么执行zfail函数;如果通过深度测试或者没有开启深度测试,则执行zpass函数。默认情况下,这三个模板操作的函数均设置为GL_KEEP。

glStencilOpSeparate()允许我们为多边形的正面和背面单独设置模板测试参数。

“使用饱和运算”是将模板值截断到区间的极值上,也就是说,如果0值再减1,饱和运算之后结果依然为0;“不使用饱和运算”是数值超出区间范围之后,将它重新变换到区间的另一端,将0值减1,在此情形下将会变成当前的最大无符号整数值(这个值可能非常大!)。

  • 模板查询

    通过查询函数glGetIntegerv()以及相应枚举值,可以获取所有6个与模板相关的参数数值。

    模板测试的查询函数
    查询参数意义
    GL_STENCIL_FUNC模板函数
    GL_STENCIL_REF模板参考值
    GL_STENCIL_VALUE_MASK模板掩码
    GL_STENCIL_FAIL模板测试失败的处理函数
    GL_STENCIL_PASS_DEPTH_FAIL模板测试通过但是深度测试失败的处理函数
    GL_STENCIL_PASS_DEPTH_PASS模板测试和深度测试均通过的处理函数

    可以使用glIsEnabled()函数传入GL_STENCIL_TEST来判断模板测试是否开启。

深度测试

深度缓存会记录场景中物体与视点在这个像素上的距离信息。深度测试用来比较已经存储的数据和新的片元数据,并据此决定结果的处理方法。输入的深度值通过测试,即可替换当前深度缓存中的已有值。

深度缓存主要的用途是隐藏表面的消除。使用glEnable()传入GL_DEPTH_TEST即可开启测试,并且每帧重绘场景时都需清除深度缓存数据。亦可设置不同的深度测试比较函数。

void glDepthFunc(GLenum func);

设置深度测试的比较函数。比较函数可以是GL_NEVERGL_ALWAYSGL_LESSGL_LEQUALGL_EQUALGL_GEQUALGL_GREATER或者GL_NOTEQUAL中的一种。

对于任何输入的片元,如果它的z值与深度缓冲中已有的值相比,符合函数定义的条件,则测试通过。

默认的比较函数为GL_LESS,即只有输入片元的z值比深度缓存中已有的值更小的时候,深度测试才会通过。而z值对应于物体到视点的距离,更小的值意味物体更靠近视点所在位置。

  • 多边形偏移

    如果需要将一个实体物体的边缘加亮,可以设置这个物体的多边形模式为GL_FILL,绘制一次,然后设置一个不同的颜色,并且设置多边形模式为GL_LINE,再绘制一次。但是,由于线和填充多边形的光栅化过程并不是完全一致的,所以线和多边形边长的深度值通常也不是一样的,即使两个端点都是相同。这样的话,加亮的线可能会隐没在多边形重叠的区域内,这一现象称作"斑驳"(stitching)。

    这种不期望的现象可以使用多边形偏移(polygon offset)的方式来消除,方法是添加一个恰当的偏移值,让发生重叠的z值强行分离。

    启用多边形偏移的方法有三种,分别对应于三种不同的多边形光栅化方式:GL_FILLGL_LINEGL_POINT。可以向glEnable()传入对应的参数来开启多边形偏移:GL_POLYGON_OFFSET_FILLGL_POLYGON_OFFSET_LINEGL_POLYGON_OFFSET_POINT

    void glPolygonOffset(GLfloat factor,GLfloat units);
    

    开启后,每个片元的深度值都会被修改,在执行深度测试之前添加一个计算偏移值。这个偏移值的计算过程为:
    o f f s e t = m ∗ f a c t o r + r ∗ u n i t s offset = m * factor + r * units offset=mfactor+runits
    其中m是多边形的最大深度斜率(在光栅化过程中计算得到),r是两个不同深度值之间的、可识别的最小差值,它是一个与平台实现相关的常量。参数都可为负数。

  • 深度斜率

在这里插入图片描述

深度斜率的意义是,当我们遍历多边形的时候,z值(深度)与x或者y坐标方向上的变化做除法。深度值被限制在[0,1]区间内,而x和y坐标值属于窗口坐标。计算公式如下:
m = ( ∂ z ∂ x ) 2 + ( ∂ z ∂ y ) 2 m = \sqrt{(\frac{\partial z}{\partial x})^2+(\frac{\partial z}{\partial y})^2} m=(xz)2+(yz)2
也可使用下面的近似公式:
m = m a x ( ∂ z ∂ x , ∂ z ∂ y ) m = max(\frac{\partial z}{\partial x},\frac{\partial z}{\partial y}) m=max(xz,yz)
与近裁切平面和远裁切平面相互平行的多边形,深度斜率的值为0。这些多边形可设置一个较小的常数偏移值,factor = 0.0和units = 1.0;

与裁切平面有较大夹角的多边形,深度斜率可能会非常大,因此需要一个很大的偏移值,可以给factor设置一个较小的非零值,如0.75或者1.0。

融混

如果输入的片元,通过了所有片元测试,那么它就可以与颜色缓冲中当前内容,通过某种方式合并。我们可以将帧缓存中已有颜色与输入的片元颜色进行混合——这个过程称作融混(blending)。大多数情况下,融混与片元的alpha值直接相关。

开启融混使用glEnable()GL_BLEND参数来开启。

  • 融混参数

    OpenGL的融混参数,分别叫源融混参数(source-blending factor),以及目标混合参数(destination-blending factor)。源混合参数对应于片元着色器输出颜色,目标混合参数对应于帧缓存中的颜色值。

    使用(Sr、Sg、Sb、Sa)来表示源混合参数,使用(Dr、Dg、Db、Da)来表示目标混合参数,使用(Rs、Gs、Bs、As)和(Rd、Gd、Bd、Ad)分别表示源(片元)和目标像素的颜色值,融混方程式:
    ( S r R s + D r R d , S g G s + D g G d , S b B s + D b B d , S a A s + D a A d ) (S_rR_s+D_rR_d,S_gG_s+D_gG_d,S_bB_s+D_bB_d,S_aA_s+D_aA_d) (SrRs+DrRd,SgGs+DgGd,SbBs+DbBd,SaAs+DaAd)
    默认融混操作时相加操作,后续将说明自定义融混操作的方法。

  • 控制融混的参数

    void glBlendFunc(GLenum srcfactor,GLenum destfactor);
    void glBlendFunc(GLuint buffer,GLenum srcfactor,GLenum destfactor);
    

    控制片元输出的颜色值(源)与存储在帧缓存中的值(目标)来进行混合。各参数可用值见下表。参数srcfactor设置源融混参数的计算方式,而destfactor设置目标融混参数的计算方式。

    glBlendFunc()设置所有可绘制缓存的融混参数,而glBlendFunci()只会设置缓存buffer的融混参数。

    对于无符合和有符号的归一化帧缓存格式,融混参数将被分别限制在[0,1]或者[-1,1]的区间内。如果帧缓存采用浮点数格式,那么参数是不存在上限和下限的。

    void glBlendFuncSpearate(GLenum srcRGB,GLenum destRGB,GLenum srcAlpha,GLenum destAlpha);
    void glBlendFuncSeparate(GLuint buffer,GLenum srcRGB,GLenum destRGB,GLenum srcAlpha,GLenum destAlpha);
    

    控制片元输出的颜色值(源)与存储在帧缓存中的值(目标)之间的混合方式。参数srcRGB设置颜色值的源混合参数,destRGB设置颜色值的目标融混参数;参数srcAlpha设置alpha值的源融混参数,destAlpha设置alpha值的目标融混参数。

    glBlendFuncSeparate()设置所有可绘制缓存的融混参数,glBlendFuncSeparatei()只会设置缓存buffer的融混参数。

    源和目标融混参数
    枚举常量RGB融混参数Alpha融混参数
    GL_ZERO(0,0,0)0
    GL_ONE(1,1,1)1
    GL_SRC_COLOR(Rs,Gs,Bs)As
    GL_ONE_MINUS_SRC_COLOR(1,1,1)-(Rs,Gs,Bs)1-As
    GL_DST_COLOR(Rd,Gd,Bd)Ad
    GL_ONE_MINUS_DST_COLOR(1,1,1)-(Rd,Gd,Bd)1-Ad
    GL_SRC_ALPHA(As,As,As)As
    GL_ONE_MINUS_SRC_ALPHA(1,1,1)-(As,As,As)1-As
    GL_DST_ALPHA(Ad,Ad,Ad)Ad
    GL_ONE_MINUS_DST_ALPHA(1,1,1)-(Ad,Ad,Ad)1-Ad
    GL_CONSTANT_COLOR(Rc,Gc,Bc)Ac
    GL_ONE_MINUS_CONSTANT_COLOR(1,1,1)-(Rc,Gc,Bc)1-Ac
    GL_CONSTANT_ALPHA(Ac,Ac,Ac)Ac
    GL_ONE_MINUS_CONSTANT_ALPHA(1,1,1)-(Ac,Ac,Ac)1-Ac
    GL_SRC_ALPHA_SATURATE(f,f,f),f = min(As,1-Ad)1
    GL_SRC1_COLOR(Rs1,Gs1,Bs1)As1
    GL_ONE_MINUS_SRC1_COLOR(1,1,1)-(Rs1,Gs1,Bs1)1-As1
    GL_SRC1_ALPHA(As1,As1,As1)As1
    GL_ONE_MINUS_SRC1_ALPHA(1,1,1)-(As1,As1,As1)1-As1
    void glBlendColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha);
    

    如果融混模式为GL_CONSTANT,那么设置一组红色、绿色、蓝色和alpha值作为常量颜色(Rc、Gc、Bc、Ac)。

  • 融混方程

    void glBlendEquation(GLenum mode);
    void glBlendEquation(GLuint buffer,GLenum mode);
    

    设置帧缓存和源数据颜色之间混合的方法。mode可用的数值包括GL_FUNC_ADD(默认值)、GL_FUNC_SUBTRACTGL_FUNC_REVERSE_SUBTRACTGL_MINGL_MAX

    glBlendEquation()为所有的缓存设置融混模式,而glBlendEquationi()可以为buffer参数指定的缓存设置模式,buffer指的是缓存的整数索引名。

    void glBlendEquationSeparate(GLenum modeRGB,GLenum modeAlpha);
    void glBlendEquationSeparatei(GLuint buffer,GLenum modeRGB,GLenum modeAlpha);
    

    设置帧缓存和源数据颜色之间混合的方法,并且允许RGB和alpha颜色分量使用不同的融混模式。

    下表中,Cs和Cd分别表示源和目标的颜色。其中S和D参数表示glBlendFunc()或者glBlendFuncSeparate()设置的源融混参数和目标融混参数。

    融混方程的数学操作符
    融混模式参数数学操作
    GL_FUNC_ADDCsS+CdD
    GL_FUC_SUBTRACTCsS-CdD
    GL_FUC_REVERSE_SUBTRACTCdD-CsS
    GL_MINmin(S,D)
    GL_MAXmax(S,D)

    融混方程找中的GL_MINGL_MAX有一个奇特的地方,就是它们不包含源和目标参数Srgba和Drgb,它们只对源和目标颜色值RGBAs和RGBAd起作用。

逻辑操作

片元的最后一个操作就是逻辑操作,包括或(OR)、异或(XOR)和反转(INVERT),它用于输入的片元数据(源)以及当前颜色缓存中的数据(目标)。

这类片元操作对于位块传输(blt-blt)类型的系统的是非常有用的,因为对它们来说,主要的图形操作就是窗口中的某一处矩形数据拷贝到另一处,或者从窗口拷贝到处理器内存,以及从内存拷贝到窗口。通常情况下,这一步拷贝操作不会将数据直接写入内存上,而是允许用户对输入的数据和已有数据之间做一次逻辑操作,然后用操作的结果替换当前已有的数据。

使用glEnable()和glDisable(),设置GL_COLOR_LOGIC_OP参数,可开启和禁用逻辑操作,否则它将保持默认的状态值,也就是GL_COPY。

void glLogic(GLenum opcode);

对于给定的输入片元(源)和当前颜色缓存的像素(目标),选择需要执行的逻辑操作。默认为GL_COPY。(注:s表示数据源,d表示目标)

16种不同的逻辑操作
参数操作
GL_CLEAR0
GL_COPYs
GL_NOOPd
GL_SETl
GL_COPY_INVERTED!s
GL_INVERT!d
GL_AND_REVERSEs∧!d
GL_OR_REVERSEs∨!d
GL_ANDs∧d
GL_ORs∨d
GL_NAND!(s∧d)
GL_NOR!(s∨d)
GL_XORs XOR d
GL_EQUIV!(s XOR d)
GL_AND_INVERTED!s∧d
GL_OR_INVERTED!s∨d

对于浮点型缓存,或者sRGB格式的缓存,逻辑操作将被自动忽略。

遮挡查询

可以使用遮挡查询(occlusion query)的方法判断一系列几何体的可见性,这一步是在逐片元的各种测试之后完成的。

这是一种合理的性能优化方法。它不需要渲染一个复杂物体的全部几何信息,而是先渲染它的包围体或者其他简化的表达形式,这样降低了渲染资源的消耗,然后再计算通过了所有测试的片元的数量。

如果OpenGL将这个简化几何体渲染之后,没有得到任何片元或者采样值,那么我们就知道这个复杂物体在这一帧是不可见的,那么对于这一帧来说,我们就可以忽略这个物体的渲染。

实现步骤:

  1. 每次遮挡查询都要创建一个查询对象,设置类型为GL_SAMPLES_PASSED、GL_ANY_SAMPLES_PASSED或者GL_ANY_SAMPLES_PASSED_CONSERVATIVE。

  2. 调用glBeginQuery(),开始进行遮挡查询。

  3. 渲染几何体,以完成遮挡测试。

  4. 调用glEndQuery(),完成本次遮挡查询。

  5. 获取本次通过深度测试的样本数量。

创建查询对象
void glCreateQueries(GLenum target,GLsizei n,GLuint* ids);

创建n个新的查询对象,它们使用时对应的目标由target决定。新的查询对象的名称被放置在ids对应的数组当中,ids中保存的名称不一定是连续的整数数列。

0是一个被保留的遮挡查询对象名称,glCreateQueries()永远不会返回0值作为有效的对象名。

GLboolean glIsQuery(GLuint id);

如果id是一个遮挡查询对象的名称,那么返回GL_TRUE。如果id为0或者是一个非零值,但是不是遮挡查询对象的名称,那么返回GL_FALSE。

遮挡查询测试的初始化

如果要指定遮挡查询过程中用到的几何体,只需要将它的渲染操作放置在glBeginQuery()glEndQuery()之间即可。

glBeginQuery(GL_SAMPLES_PASSED,Query);
glDrawArrays(GL_TRIANGLES,0,3);
glEndQuery(GL_SAMPLES_PASSED);

遮挡查询启用之后,所有的OpenGL操作都是可用的,除了glCreateQueries()glDeleteQueries()之外,它们会产生一个GL_INVALID_OPERATION

void glBeginQuery(GLenum target,GLuint id);

启动遮挡查询操作。target必须是GL_SAMPLES_PASSEDGL_ANY_SAMPLES_PASSED或者GL_ANY_SAMPLES_PASSED_CONSERVATIVE。id是一个无符号整型的标识符,用于标识本次遮挡查询操作。

void glEndQuery(GLenum target);

结束遮挡查询操作。target必须是GL_SAMPLES_PASSEDGL_ANY_SAMPLES_PASSED、或者GL_ANY_SAMPLES_PASSED_CONSERVATIVE

  • GL_SAMPLE_PASSED 会得到精确的片元数量,这些片元全部通过了逐片元的测试过程。使用这个查询类型可能会降低OpenGL的性能,因此只应在需要精确结果的场合下使用。
  • GL_ANY_SAMPLES_PASSED 也叫做是非遮挡查询(Boolean occlusion query),得到的是一个近似的计数结果。事实上,这个目标唯一能够确保的是:如果没有任何片元能够通过逐片元的测试,那么查询的结果一定是0。否则,结果就是某个非零值。在某些硬件实现中,这个非零值可能是一个相对精确的通过测试的片元数量值,但是你不能完全信任这个数据。
  • GL_ANY_SAMPLES_PASSED_CONSERVATIVE提供了一个比GL_ANY_SAMPLES_PASSED更为宽松的结果。对于这个查询类型来说,只有OpenGL绝对确信不会有任何片元通过测试,查询结果才为0。但是即使实际上并没有片元能通过测试,查询的返回值也有可能是一个非零值。这种测试类型的性能是最高的,但是得到的结果是最不准确的。

在实际当中,性能的差异有可能是很小的。不过不管怎么说,我们应该根据自己的需要选择合适的类型,如果并不需要足够精确的结果的话,GL_ANY_SAMPLES_PASSED_CONSERVATIVE确实是一个好的选择。

查询对象的机制并不仅用在遮挡查询当中。我们在计算顶点数、图元数,甚至时间的时候都会用到不同的查询类型。这些查询类型所涉及的方向并不相同,但是都会用到刚才介绍的glBeginQuery()和glEndQuery()函数(或者其他变体)。

判断遮挡查询的结果
void glGetQueryObjectiv(GLenum id,GLenum pname,GLint* params);
void glGetQueryObjectuiv(GLenum id,GLenum pname,GLint* params);

获取遮挡查询对象的状态信息。id是遮挡查询物体的名称。如果pname是GL_QUERY_RESULT,那么通过深度测试的片元或者样本(如果开启了多重采样)的数量将被写入params中,如果返回值为0,那么表示这个物体已经被完全遮挡了。

遮挡查询操作结束之后,可能会有一点延迟才能获取结果。如果pname为GL_QUERY_RESULT_AVAILABLE,并且查询id的结果已经可以读取,那么params中会写入GL_TRUE;否则会写入GL_FALSE

例程—获取遮挡查询的结果

count = 1000;	//使用计数器来避免陷入无限循环
do
{
    glGetQueryObjectiv(Query,GL_QUERY_RESYULT_AVAILABLE,&queryReady);
}while(!queryReady && count--);

if(queryReady)
{
    glGetQueryObjectiv(Query,GL_QUERY_RESULT,&samples);
    cerr << "Samples rendered:" << samples << endl;
}
else
{
	cerr << "Result not ready ... rendering anyway" << endl;
    samples = 1;	//确保进行一次渲染
}

if(samples > 0)
{
    glDrawArrays(GL_TRIANGLE_FAN,0,NumVertices);
}
清除遮挡查询对象
void glDeleteQueries(GLsizei n,const GLuint* ids);

删除n个遮挡查询对象,它们的名字为数组ids中的元素。

释放查询对象之后,就可以重新使用了(例如通过glCreateQueries()函数再次生成)。

多重采样

多重采样(multisampling)是一种对几何图元的边缘进行平滑的技术——通常也称作反走样(antialiasing)。

多重采样的工作方式对每个像素的几何图元进行多次采样。此时每个像素点不会只保存单个颜色值(以及深度和模板值,如果存在的话),而是记录多个样本值,它们类似于更小行的像素,可以用来存储每个样本位置的颜色、深度和模板值。当需要表示最终图像的内容时,这个像素的所有样本值会被解析为最终的像素的颜色。

  • 用户程序需要先请求一处多重采样的缓存(创建窗口时完成),通过glGetIntegerv()函数来查询GL_SAMPLE_BUFFERS,以判断是否请求成功(并不是所有设备都支持多重采样)。
  • 在渲染时启动多重采样,使用GL_MULTISAMPLE作为参数来调用glEnable()。多重采样渲染时间较长,因此不需所有渲染都使用。

对于每个像素中有多少个样本值会用于实现多重采样,可以通过glGetIntegerv()并设置参数为GL_SAMPLES来完成。然后可以通过glGetMultisamplefv()函数进行查找样本信息。

void glGetMultisamplefv(GLenum pname,GLuint index,GLfloat* val);

如果设置pname为GL_SAMPLE_POSITION,那么函数会返回第index个样本的位置信息,作为存储在val参数中的一对浮点数值。位置信息的区间为[0,1],即该样本相对像素左下角位置的偏移值。

如果index大于或者等于系统所支持的样本数(即通过glGetIntegerv()设置参数为GL_SAMPLES来获取的结果),那么将产生一个GL_INVALID_VALUE错误。

在片元着色器当中,可以通过读取gl_SamplePosition变量的内容来获取相同的信息。此外,也可以通过gl_SampleID变量来判断着色器当前正处理哪个样本值。

如果只启用多重采样,那么片元着色器的执行过程与平常无异,最终颜色值会自动分配给该像素的所有的样本。也就是说,所有的颜色值都是一样的,但是每个样本经过光栅化之后得到的深度值和模板值是不同的。然后,如果片元着色器使用gl_Sample*变量,或者使用sample关键字限定着色器的输入变量,那么片元着色器将会在同一像素上执行多次,每次输出不同的样本位置信息。

//多重采样形式的片元着色器
#version 430 core

sample in vec4 color;

out vec4 fColor;

void main()
{
    fColor = color;
}

上述代码简单地添加了sample关键字,这样每个采样着色器(sample shader,这个术语专用于描述逐样本执行的片元着色器)的实例结果都会因为样本的位置偏差而产生轻微的差异。

采样着色

如果不能使用sample关键字来修改片元着色器,可以通过glEnable()启用GL_SAMPLE_SHADING,强制OpenGL使用采样着色的方式。

通过函数设置最小采样着色的比率,可以控制样本的数量,即从片元着色器中获取独立样本插值数据的采样点数量。

void glMinSampleShading(GLfloat value);

设置每个像素中独立着色的样本值数量。value设置的是独立着色样本占总样本数的比率,因此需要限制在[0,1]区间内,其中1.0表示所有的样本都会使用唯一一组采样数据。

因为不同的OpenGL实现中,每个像素所包含的样本数量可能不同,使用比率的方式可以减少对多重采样配置参数的查询需求。

如果系统中设置每像素4个采样点的话,那么光栅化图元时每个像素上的工作都相当于原来的4倍;glMinSampleShading()可以控制每个像素中具体有多少样本会使用独立的着色结果(也就是有多少个样本位置会独立执行一次片元着色器),如果应用程序已经受限于片元着色的速度,那么降低最小采样着色的比例有助于提升应用程序的性能。

逐图元的反走样

有些OpenGL的图像中的线条存在锯齿问题,尤其是哪些几乎水平和几乎垂直的线。这是因为一条理想直线是通过像素网格上的一系列像素点去逼近的,由此产生的问题叫做走样(aliasing)。

在这里插入图片描述

当我们实现反走样的时候,OpenGL会根据屏幕上每个像素块所覆盖的范围计算每个片元的覆盖值。OpenGL会将片元alpha值与这个覆盖值相乘。然后我们就可以使用alpha值来实现片元与帧缓存中已有像素的融混操作了。

可以使用glHint()来进行进一步的控制反走样,在图像质量和速度上做出权衡,不过并不是所有的设备实现都会受到这个函数的影响。

void glHint(GLenum target,GLenum hint);

控制OpenGL的一些具体特性。target参数用来设置控制的特性类型。hint参数可以设置为GL_FASTEST,表示使用效率最高的方式,而GL_NICEST表示使用质量最高的方式,GL_DONT_CARE表示没有偏好。hint参数的解析方式是与平台相关的;有些OpenGL的实现可能会完全忽略它们的影响。

glHint()中可用的参数值
参数描述
参数描述
GL_LINE_SMOOTH_HINT线的反走样质量
GL_POLYGON_SMOOTH_HINT多边形的反走样质量
GL_TEXTURE_COMPRESSION_HINT纹理图像压缩的质量和性能
GL_FRAGMENT_SHADER_DERIVATIVE_HINT片元处理内置函数的导数精度、包括dFdx、dFdy以及fwidth

对于线段的反走样技术,它比多重采样的反走样技术更佳。那么对于线段和多边形反走样,可以通过GL_LINE_SMOOTH或者GL_POLYGON_SMOOTH作为参数传入,使用glEnable()开启反走样。

线段反走样

  • 开启融混。融混参数通常可以设置为GL_SRC_ALPHA(源)和GL_ONE_MINUS_SRC_ALPHA(目标)。或者也可以使用GL_ONE作为目标参数,这样线段相交的地方会显得更亮。
  • 使用反走样绘制点和线段绘制点和线段。
//线段反走样的初始化过程
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glHint(GL_LINE_SMOOTH_HINT,GL_DONT_CARE);

多边形的反走样

为了对多边形进行反走样,需要使用alpha值来表达多边形边的覆盖值。

  • 通过glEnable()设置参数GL_POLYGON_SMOOTH来开启多边形的反走样功能。这样多边形边上的像素会根据覆盖率来设置alpha值。

  • 如果需要,亦可设置GL_POLYGON_SMOOTH_HINT的质量值

  • 混溶方式,可以设置参数为GL_SRC_ALPHA_SATURATE(源)和GL_ONE(目标)。通过这个特定的融混函数,得到的最终颜色将是目标颜色和成比例的源颜色的总和;比例系数是以下两者中的较小值:输入源的alpha值、1减去目标alpha值。

    这也就是说,对于一个alpha值很大的像素,连续输入像素对于最终颜色的影响很小,因为1减去目标alpha几乎为0。通过这种方式,多边形的像素就可以与之后绘制的其他多边形的颜色很好地融合在一起。

  • 最后,还需对场景中的所有多边形进行排序,保证按照从前往后的顺序排序后再做渲染。

像素数据的读取和拷贝

渲染工作结束之后,可以获取渲染后的图像以作他用。

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

从可读的帧缓存中读取像素数据,读取的矩形区域范围的左下角定义为窗口坐标的(x,y),尺寸为width和height,然后将数据数组保存到pixels中。format用于指定要读取的像素数据元素的类型(颜色、深度或者模板值),而type用于指定每个元素的数据类型。

glReadPixels()的错误地使用也会产生一些OpenGL的错误信息。如果format设置为GL_DEPTH但是不存在深度缓存,或者format为GL_STENCIL但是不存在模板缓存,或者format为GL_DEPTH_STENCIL,但是帧缓存当中没有同时关联深度和模板缓存,那么都会产生GL_INVALID_OPERATION错误。

如果format为GL_DEPTH_STENCIL,但是type不是GL_UNSIGNED_INT_24_8,也不是GL_FLOAT_32_UNSIGNED_INT_24_8_REV,则会产生GL_INVALID_ENUM错误。

glReadPixels()的数据格式
枚举量像素格式
GL_RED或者GL_RED_INTEGER单一的红色颜色分量
GL_GREEN或者GL_GREEN_INTEGER单一的绿色颜色分量
GL_BLUE或者GL_BLUE_INTEGER单一的蓝色颜色分量
GL_ALPHA或者GL_ALPHA_INTEGER单一的alpha分量
GL_RG或者GL_RG_INTEGER依次为红色分量、绿色分量
GL_RGB或者GL_RGB_INTEGER依次为红色分量、绿色分量、蓝色分量
GL_RGBA或者GL_RGBA_INTEGER依次为红色分量、绿色分量、蓝色分量、alpha分量
GL_BGR或者GL_BGR_INTEGER依次为蓝色分量、绿色分量、红色分量
GL_BGRA或者GL_BGRA_INTEGER依次为蓝色分量、绿色分量、红色分量、alpha分量
GL_STENCIL_INDEX单一的模板索引值
GL_DEPTH_COMPONENT单一的深度分量
GL_DEPTH_STENCIL深度和模板值的合并结果
glReadPixels()的数据类型
枚举量数据类型是否压缩格式
GL_UNSIGNED_BYTEGLubyte
GL_BYTEGLbyte
GL_UNSIGNED_SHORTGLushort
GL_SHORTGLshort
GL_UNSIGNED_INTGLuint
GL_INTGLint
GL_HALF_FLOATGLhalf
GL_FLOATGLfloat
GL_UNSIGNED_BYTE_3_3_2GLubyte
GL_UNSIGNED_BYTE_2_3_3_REVGLubyte
GL_UNSIGNED_SHORT_5_6_5GLushort
GL_UNSIGNED_SHORT_5_6_5_REVGLushort
GL_UNSIGNED_SHORT_4_4_4_4GLushort
GL_UNSIGNED_SHORT_4_4_4_4_REVGLushort
GL_UNSIGNED_SHORT_5_5_5_1GLushort
GL_UNSIGNED_SHORT_1_5_5_5_REVGLushort
GL_UNSIGNED_INT_8_8_8_8GLuint
GL_UNSIGNED_INT_8_8_8_8_REVGLuint
GL_UNSIGNED_INT_10_10_10_2GLuint
GL_UNSIGNED_INT_2_10_10_10_REVGLuint
GL_UNSIGNED_INT_24_8GLuint
GL_UNSIGNED_INT_10F_11F_11F_REVGLuint
GL_UNSIGNED_INT_5_9_9_9_REVGLuint
GL_FLOAT_32_UNSIGNED_INT_24_8_REVGLfloat

我们可能还需要设置从哪个缓存读取像素数据。例如,对于双重缓冲的窗口来说,可以从前缓存或者后缓存中读取像素。此时需要使用glReadBuffer()来设置要读取像素的缓存名称。

返回值的截断处理

OpenGL中各种不同类型的缓存——尤其是那些浮点数类型的缓存——都可保存范围在[0,1](OpenGL归一化之后的颜色值)之外的数据。当使用glReadPixels()读回数据时,可以自行设置这些数值是否需要使用glClampColor()截断到归一化的范围内,或者保留原始的全数据范围形式。

void glClampColor(GLenum target,CLenum clamp);

控制浮点数和定点数据存的颜色值截断方式,如果target设置为GL_CLAMP_READ_COLOR则开启截断。如果clamp设置为GL_TRUE,那么从缓存读回的颜色值将被截断到[0,1]范围;反之如果clamp为GL_FALSE,那么不会进行截断。

如果用户程序使用的是定点数与浮点数混合的缓存类型,那么设置clamp为GL_FIXED_ONLY的时候,只截断定点数数据;而浮点数数据仍然保留原始形式返回。

拷贝像素矩形

如果在一块缓存的不同区域之间进行拷贝,或者在不同的帧缓存之间进行拷贝,则可以使用glBlitFramebuffer()函数,它在拷贝过程中使用像素滤波的方式。

void glBlitNamedFramebuffer(GLuint readFramebuffer,GLuint drawFramebuffer,GLint srcX0,GLint srcY0,GLint srcX1,GLint srcY1,GLint dstX0,GLint dstY0,GLint dstX1,GLint dstY1,GLbitfield mask,GLenum filter);

将矩形的像素数据从可读帧缓存的一处区域拷贝到可绘制缓存的另一处区域中,在这一过程中,可能会存在自动缩放、反转、转换和滤波的操作。

srcX0、srcY0、srcX1和srcY1表示像素数据源的区域范围,目标区域的矩形范围通过dstX0、dstY0、dstX1和dstY1来设置。buffers中可以通过按位"或"的方式设置GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BIT以及GL_STENCIL_BUFFER_BIT,以设置拷贝所在的缓存类型。

最后如果两处矩形区域的大小不同的话,那么filter用设置插值的方法,可用的值包括GL_NEARESTGL_LINEAR;如果区域的大小是一致的,那么不会产生滤波的操作。

如果当前存在多个颜色绘制缓存,那么每个缓存都会进行一次源数据的拷贝。

如果srcX1 < srcX0或者dstX1 < dst X0,那么图像将在水平方向进行反转。类似地,如果srcY1 < src Y0或者dstY1 < dstY0,那么图像将在竖直方向进行反转。但是如果源和目标的尺寸值在相同方向均设置为负数,那么不会产生任何的翻转操作。

如果源数据缓存和目标数据缓存的格式不同,那么多数情况下像素数据的转换操作会自动进行。但是如果可读颜色缓存是浮点数类型,而任何一个写入的颜色缓存不是(反之亦然),或者可读颜色缓存是有符号(无符号)的整数类型,但是某个写入的缓存不是的话,那么都会产生GL_INVALID_OPERATION的错误,并且不会拷贝任何像素数据。

多重采样的缓存对于像素数据的拷贝也有一定影响。如果源缓存是多重采样的,而目标缓存不是的话,那么所有的样本值都会被归总到单一的像素值并存入目标缓存。反之,如果目标缓存是多重采样的,但是源缓存不是的话,那么源数据将被多次拷贝,以匹配所有的样本值。最后,如果两个缓存都是多重采样的形式,并且采样数是相同的,那么所有样本不需要修改就可以直接拷贝。但是,如果两个缓存的采样数不同,那么无法拷贝像素,并且会产生一个GL_INVALID_OPERATION错误。

如果buffers中含有多余的类型值,或者filter不是GL_LINEAR或者GL_NEAREST,那么将会产生GL_INVALID_VALUE错误。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值