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枚举量 最小值 映射后的最小值 最大值 映射后的最大值
GLbyte GL_BYTE -128 -1.0 127 1.0
GLshort GL_SHORT -32 768 -1.0 32 767 1.0
GLint GL_INT -2 147 483 648 -1.0 2 147 483 647 1.0
GLubyte GL_UNSIGNED_BYTE 0 0.0 255 1.0
GLushort GL_UNSIGNED_SHORT 0 65 535 1.0
GLuint GL_UNSIGNED_INT 0 0.0 4 294 967 295 1.0
GLfixed GL_FIXED -32 767 -1.0 32 767 1.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,再绘制一次。但是,由于线和填充多边形的光栅化过程并不是完全一致的,所以线和多边形边长的深度值通常也不是一样的,即使两个端点都是相同。这样的话,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值