《OpenGL编程指南》 笔记四 颜色、像素和帧缓存(一)

第四章 颜色、像素和帧缓存

4.1 基本颜色理论

  • RGB颜色空间–三原色构成显示设备的整个色域
  • 颜色缓存的一个通常格式是:每个红色、绿色和蓝色分量都占用8位;因此我们得到了一个像素深度为24位的颜色缓存,它可以显示总共2的24次方种独立的颜色

4.2 缓存及其用途

  • 每个片元都包含与像素对置对应的坐标数据,以及颜色和深度的存储值

  • 通常来说,像素(x,y)填充的区域是以x为左侧,x+1为右侧,y为底部,y+1为顶部的一处矩形区域

  • 颜色缓存只是记录像素信息的多个缓存中的一个

  • 硬件系统的帧缓存包含所有的缓存内容,因此我们也可以在应用程序中使用多个帧缓存

  • OpenGL系统中通常包含三种缓存类型:

    1. 一个或多个可用的颜色缓存(color buffer)
    2. 深度缓存(depth buffer)
    3. 模板缓存(stencil buffer)
  • 所有类型的缓存都被集成到帧缓存当中,尽管我们可以*决定需要用到哪些缓存,但是当启动应用程序后,我们使用的是默认的帧缓存,它是与应用程序所关联的帧缓存

  • 默认帧缓存总是会包含一个双重缓冲机制(double-buffered)的颜色缓存

颜色缓存

  • 颜色缓存是我们通常进行绘制的缓存对象。
  • 它包含RGB或者sRGB形式的颜色数据,也可能包含帧缓存中每个像素的alpha值
  • 帧缓存中可能会包含多个颜色缓存。其中,默认帧缓存中的“主”颜色缓存要特别对待,因为它是与屏幕上的窗口相关联的,所以绘制到其中的图像都会直接显示到屏幕上,而所有其他的颜色缓存都是与屏幕无关的
  • 颜色缓存中的像素,可能是采用每个像素存储单一颜色值的形式,也可能从逻辑上被划分为多个子像素,因此启用了一种名为多重采样(multisampling)的反走样技术形式
  • 双重缓冲的实现需要将主颜色缓存划分为两个部分:直接在窗口中显示的前置缓存(front buffer),以及用来渲染新图像的后备缓存(back buffer)
  • 只有默认帧缓存中的主颜色缓存可以使用双重缓冲的特性

深度缓存

  • 深度缓存为每个像素保存一个深度值,它可以用来判断三维空间中物体的可见性
  • 这个深度是物体与观察者眼睛的距离

模板缓存

  • 模板缓存可以用来限制屏幕特定区域的绘制

4.2.1 缓存的清除

  • 通常每帧都至少需要一次清除缓存的操作

  • 设置不同缓存的清除值:

    1. void glClearColor(GLclampf red, GLclampf green, GLclamp blue, GLclampf alpha)
    2. void glClearDepth(GLclampd depth)
    3. void glClearDepthf(GLclampf depth)
    4. void glClearStencil(GLint s)
    5. GLclampf和GLclampd类型(即截断后的GLfloat和GLdouble)需要被截断到0.0~1.0区间内
  • 设置清除值之后,就可以使用glClear()来执行缓存清除操作了

  • void glClear(GLbitfield mask) mask的取值

    1. GL_COLOR_BUFFER_BIT
    2. GL_DEPTH_BUFFER_BIT
    3. GL_STENCIL_BUFFER_BIT
  • 如果启用了像素的所有权测试(ownership test)、剪切测试(scissor test)或者抖动(dithering)特性,那么他们会对清除操作产生影响;同理还有glColorMask()所设置的掩码操作;但是,深度测试和模板测试不会影响到glClear()的结果

4.2.2 缓存的掩码

  • 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则无法写入
  • glStencilMaskSeparate()可以为多边形的正面和背面设置不同的掩码值
  • 如果需要渲染到多个颜色缓存,glColorMask()可以对特定的缓存对象设置颜色掩码

4.3 颜色与OpenGL

  • 片元着色器负责设置每个片元的颜色值,而有多种方式完成这一操作
    1. 片元着色器可以不借助任何外部数据,直接就生成片元的颜色值
    2. 每个输入的顶点都会提供一个附加的颜色数据,这个颜色数据可以其他着色阶段中修改再传入片元着色器,并且用它来判断颜色值
    3. 颜色的补充(并不是具体的颜色值)–可以在片元着色器中通过计算来生成颜色值
    4. 外部数据,也可以在片元着色器中引用,用于查找颜色值

4.3.1 颜色的表达与OpenGL

  • 除非特殊设置,否则片元着色器的输入总是浮点数类型,为片元颜色设置的数值也是如此,并且这些值总是要限制在[0.0, 1.0]的范围内,这个值也称为归一化数值。归一化的颜色数值被写入到帧缓存后,会被映射到帧缓存所支持的数据区间内
  • 用户应用程序提供给OpenGL的数据基本上都是int或float类型;我们可以选择让OpenGL自动将非浮点数类型转化为归一化的浮点数;即通过glVertexAttribPointer()或者glVertexAttribN*()系列函数,此实OpenGL把输入的数据类型转化为对应的归一化数值范围
  • 数据的数据类型对应归一化时的枚举
OpenGL类型 OpenGL枚举
GLbyte GL_BYTE
GLshort GL_SHORT
GLint GL_INT
GLubyte GL_UNSIGNED_BYTE
GLushort GL_UNSIGNED_SHORT
GLuint GL_UNSIGNED_INT
GLfixed GL_FIXED

4.3.2 顶点颜色

  • 我们可以将多种数据与顶点相关联,到目前为止,关联的数据有顶点颜色,顶点位置;这些顶点数据必须保存在顶点缓存对象中(vbo)

4.3.3 光栅化

  • OpenGL中,顶点着色阶段以及片元着色阶段之间的过程称作光栅化。它的主要职责是判断屏幕空间的哪几个部分被几何体所覆盖。如果知道这些区域,再与输入的顶点数据相结合,那么光栅化阶段就可以对片元着色器中的每个变量数值进行线性插值计算,然后将结果输入到片元着色器当中
  • 光栅化相当于一个片元生命的开始,而片元着色器的计算过程本质上就相当于计算这个片元的最终颜色

4.4 多重采样

  • 多重采样(multisampling)是一种对几何图元的边缘进行平滑处理的技术。通常也叫做反走样(antialiasing)

  • 多重采样的工作方式:对每个像素的几何图元进行多次采样,记录多个采样值(包括颜色值,深度值,模板值);当我们需要表示最终图像的内容的时候,这个像素的所有样本值会被解析为最终像素的颜色

  • 使用多重采样的步骤:

    1. 用户程序请求一处多重采样的缓存(这一步在创建窗口时完成)
    2. 通过glGetIntegerv()函数在查询GL_SAMPLE_BUFFERS,判断是否清除成功
    3. 在渲染时启用多重采样glEnable(GL_MULTISAMPLE)
  • 每个像素中有多少个样本值用于多重采样glGetIntegerv(GL_SAMPLES)

  • glGetMultisamplefv(GLenum pname, GLfloat* val) 如果设置pname为GL_SAMPLE_POSITION,那么该函数返回第index个样本的位置信息,保存在val中

  • 在片元着色器中,可以通过gl_SamplePosition变量的内容来获取上一个函数相同的信息;也可以通过gl_SampleID来判断片元着色器当前正在处理哪个样本值

  • 经过多重采样后,所有的颜色值虽然保持不变,但每个样本经过光栅化后得到的深度值和模板值是不同的;这时候,如果片元着色器当中使用了上边提高的gl_Sample*变量或者sample关键字限定着色器的输入变量,那么片元着色器将会在同一像素上执行多次,每次都会输出不同的样本位置信息

  • 采样着色器,专用于描述逐样本执行的片元着色器

  • 片元着色器使用sample关键字限定输入变量,这样每个采样着色器的实例结果都会因为样本的位置偏差而产生轻微的差异;使用这一特性,对纹理贴图的采样也会得到更好的结果

4.4.1 采样着色

  • 如果不能使用sample关键字,那么可以通过glEnable(GL_SAMPLE_SHADING)强制OpenGL使用采样着色的方式
  • glMinSampleShading(GLfloat value) 设置最小采样着色比率

4.5 片元的测试与操作

  • 当我们在屏幕上绘制几何体的时候,OpenGL会按照下面的顺序来处理管线:

    1. 执行当前绑定的顶点着色器;如果有绑定,然后依次是细分和几何着色器
    2. 将最终几何体装配为图元并送入光栅化阶段,这里将计算出窗口中哪些像素受到了几何体的影响
    3. 当OpenGL确定当前需要生成一个独立片元时,它将执行片元着色器的内容
    4. 再经过几个处理阶段,判断片元是否可以做为像素绘制到帧缓存当中,以及控制绘制的方式
  • 片元进入到帧缓存之前需要经过的测试和操作,这些测试和操作的顺序如下:

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

4.5.1 剪切测试

  • void glScissor(GLint x, GLint y, GLsizei width, GLsizei height) 设置剪切盒的位置及大小
  • glEnable(GL_SCISSOR_TEST) 开启剪切测试
  • 默认条件下,剪切盒与窗口大小相等,并且是关闭状态
  • 如果开启剪切测试,那么所有的渲染,包括窗口

4.5.2 多重采样的片元操作

  • 默认条件下,多重采样在计算片元的覆盖率时不会考虑alpha的影响

  • 如果开启下列特定的模式,片元的alpha值将被纳入到计算过程中

    1. GL_SAMPLE_ALPHA_TO_COVERAGE 使用片元的alpha值来计算最后的采样覆盖率,并且这一过程与具体的硬件实现无关
    2. GL_SAMPLE_ALPHA_TO_ONE 将片元的alpha值设置为最大的alpha值,然后使用这个值来进行覆盖率的计算
    3. GL_SAMPLE_COVERAGE 将使用glSampleCoverage()中设置的数值,与覆盖率计算的结果进行合并;
      void glSampleCoverage(GLfloat value, GLboolean invert) 设置多重采样覆盖率的参数,以正确计算alpha值;如果开启GL_SAMPLE_COVERAGE或者GL_SAMPLE_ALPHA_TO_COVERAGE,那么value是一个临时的采样覆盖值;invert用于设置这个临时覆盖值是否需要先进行位反转,然后再与片元覆盖率进行“与”操作
    4. GL_SAMPLE_MASK 设置了一个精确的位掩码来计算和表达覆盖率;这个掩码将与片元的采样覆盖值再次进行“与”操作;采样掩码是通过glSampleMask()函数来设置的
      void glSampleMaski(GLuint index, GLbitfield mask) 设置一个32位的采样掩码mask;掩码本身的索引位置通过index来设置,新的掩码值通过mask来设置;当采样结果准备写入到准缓存时,只有当前采样掩码值中对应位的数据才会被更新,而其他的数据将会被丢弃
  • 采样掩码也可以在片元着色器中通过写入gl_SampleMask变量来设置

4.5.3 模板测试

  • 只有在建立窗口的过程中预先请求模板缓存的前提下,才能使用模板测试

  • 模板测试的过程:取像素在模板缓存中的值,然后与一个参考值进行比较

  • void glStencilFunc(GLenum func, GLint ref, GLuint mask)
    void glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask)
    设置比较函数func,参考值ref以及掩码mask已完成模板测试;参考值ref先与mask参数进行按位“与”操作,丢弃结果为0的位平面,然后与模板缓存中已有的值进行比较;
    比较函数可以是:

    1. GL_NEVER
    2. GL_ALWAYS
    3. GL_LESS
    4. GL_LEQUAL
    5. GL_EQUAL
    6. GL_GEQUAL
    7. GL_GREATER
    8. GL_NOTEQUAL

    默认情况下,func为GL_ALWAYS,ref为0,mask的所有位是1,并且模板测试为禁止
    glStencilFuncSeparate()允许我们为多边形的正面和背面单独设置模板函数参数

  • glStencilOp(GLenum fail, GLenum zfail, GLenum zpass)
    glStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass)
    设置当片元通过或者没有通过模板测试的时候,要如何处理模板缓存中的数据;三个参数都可以设置为下列中的一种:

    1. GL_KEEP 保存当前值
    2. GL_ZERO 替换为0
    3. GL_REPLACE 替换为参考值
    4. GL_INCR 增加1(使用饱和运算)
    5. GL_INCR_WRAP 增加1(不适用饱和运算)
    6. GL_DECR 减少1(使用饱和运算)
    7. GL_DECR_WRAP 减少1(不使用饱和运算)
    8. GL_INVERT 按位反转

    如果片元没有通过模板测试,将执行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 模板测试和深度测试均通过的处理函数

4.5.4 模板的例子

  • 在屏幕上通过掩码构成一个不规则形状的区域,然后阻止在其中进行任何绘制。实现步骤:

    1. 使用0值来填充模板掩码
    2. 在模板缓存中绘制所需的形状并设置值为1(虽然我们无法直接绘制几何体到模板缓存中,但是可以在绘制到颜色缓存的过程中选择一个合适的zpss参数值,例如GL_REPLACE,来完成这一操作)
  • 为了避免模板缓存的绘制影响到颜色缓存的内容,需要设置颜色掩码为0

  • 模板测试的其他用法:加盖、点画效果

4.5.5 深度测试

  • 每帧重绘场景的时候都要清除深度缓存数据

  • void glDepthFunc(GLenum func) 设置深度测试的比较函数,比较函数可以是下列中的一种:

    1. GL_NEVER
    2. GL_ALWAYS
    3. GL_LESS
    4. GL_LEQUAL
    5. GL_EQUAL
    6. GL_GEQUAL
    7. GL_GREATER
    8. GL_NOTEQUAL
  • 对于任何输入的片元,如果它的z值与深度缓存中已有的值相比,符合函数定义的条件,则测试通过;默认的比较函数是GL_LESS,即只有输入片元的z值比深度缓存中已有的值更小的时候,深度测试才会通过

多边形偏移

  • 启用多边形偏移的方法有三种,分别对应与三种不同的多边形光栅化方法:GL_FILL、GL_LINE、GL_POINT;我们可以调用glEnable()传入对应的参数来开启多边形偏移,即GL_POLYGON_OFFSET_FILL、GL_POLYGON_OFFSET_LINE、GL_POLYGON_OFFSET_POINT
  • glPolygonMode() 设置当前多边形光栅化方式
  • void glPolygonOffset(GLfloat factor, GLfloat units) 开启多边形偏移后,每个片元的深度值都会被修改,在执行深度测试之前添加一个计算偏移值

4.5.6 融合

  • 如果一个输入的片元通过了所有相关的片元测试,那么它就可以与颜色缓存中当前的内容通过某种方式进行合并了
  • glEnable(GL_BLEND)启用混合

4.5.7 混合参数

  • 源混合参数–片元着色器输出的颜色
  • 目标混合参数–帧缓存中已有的颜色值

4.5.8 控制混合参数

  • void glBlendFunc(GLenum srcfactor, GLenum destfactor) 控制所有可会知缓存的混合参数
    void glBlendFunci(GLuint buffer, GLenum srcfactor, GLenum destfactor) 只设置缓存buffer的混合参数
    对于归一化帧缓存格式,混合参数将被分别限制在[0,1]或者[-1,1]的区间内;如果帧缓存用浮点数格式,那么参数是不存在上限和下限的
  • void glBlendFuncSeparate(GLenum strRGB, GLenum destRGB, GLenum srcAlpha, GLenum destAlpha)
    void glBlendFuncSeparatei(GLuint buffer, GLenum srcRGB, GLenum destRGB, GLenum srcAlpha, GLenum destAlpha)
  • 源、目标混合参数表
    《OpenGL编程指南》 笔记四 颜色、像素和帧缓存(一)
  • 如果使用GL_CONSTANT这个混合枚举变量,那么必须使用glBlendColor()来设置对应的常量颜色值

4.5.9 混合方程

  • 标准的混合–帧缓存中的颜色值与输入的片元颜色叠加,产生新的帧缓存颜色

  • void glBlendEquation(GLenum mode)
    void glBlendEquationi(GLuint buffer, GLenum mode)
    设置帧缓存和源数据颜色之间混合的方法,mode可选的值有:

    1. GL_FUNC_ADD
    2. GL_FUNC_SUBTRACT
    3. GL_FUNC_REVERSE_SUBTRACT
    4. GL_MIN
    5. GL_MAX
  • void glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha)
    void glBlendEquationSeparatei(GLuint buffer, GLenum modeRGB, GLenum modeAlpha)
    设置帧缓存和源数据颜色之间的混合方法,并且允许RGB和alpha颜色分量使用不同的混合模式

4.5.10 抖动

  • 抖动操作与硬件相关;OpenGL能做的只是允许开启或者关闭这个特性
  • glEnable(GL_DITHER)

4.5.11 逻辑操作

  • 逻辑操作–片元的最后一个操作,它包括或(OR)、异或(XOR)和反转(INVERT)等,它作用于输入的片元数据(源数据)以及当前颜色缓存中的数据(目标数据)
  • glEnable(GL_COLOR_LOGIC_OP)开启逻辑操作
  • void glLogicOp(GLenum opcode) 对于给定的输入片元(源)和当前颜色缓存的像素(目标),选择需要执行的逻辑操作;可选的逻辑操作:
    《OpenGL编程指南》 笔记四 颜色、像素和帧缓存(一)
  • 对于付店行缓存或者sRGB格式的缓存来说,逻辑操作将被自动忽略

4.5.12 遮挡查询

  • 在几何信息送入渲染管线之前,使用遮挡查询判断几何体的可见性,以提高渲染性能

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

  • 实现遮挡查询的步骤:

    1. 为每个需要的遮挡查询对象生成一个查询ID,glGenQueries()
    2. 调用glBeginQuery(),开始进行遮挡查询
    3. 渲染几何体,以完成遮挡测试
    4. 调用glEndQuery(),完成本次遮挡查询
    5. 获取本次通过深度测试的样本数量
  • void glBeginQuery(GLenum target, GLuint id) target的取值必须是下列中的一个:

    1. GL_SAMPLES_PASSED
    2. GL_ANY_SAMPLES_PASSED
    3. GL_ANY_SAMPLES_PASSED_CONSERVATIVE
  • 结束遮挡查询函数中的target的取值必须是上边三个中,前两个中的一个

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

  • void glDeleteQueries(GLsizei n, const GLuint* ids) 删除n个遮挡查询对象

4.5.13 条件渲染

  • 条件渲染–为减少遮挡查询时的性能浪费
  • void glBeginConditionalRender(GLuint id, GLenum mode) 系统根据遮挡查询对象id的结果来决定是否自动抛弃他们;mode设置OpenGL中要如何使用遮挡查询的结果,它必须时以下枚举之一:
    1. GL_QUERY_WAIT
    2. GL_QUERY_NO_WAIT
    3. GL_QUERY_BY_REGION_WAIT
    4. GL_QUERY_BY_REGION_NO_WAIT
      《OpenGL编程指南》 笔记四 颜色、像素和帧缓存(一)
  • void glEndConditionalRender() 结束条件渲染