OpenGL之深度测试和颜色混合

217 阅读9分钟

前言

关于深度测试能解决什么问题? 

  • 以甜甜圈为例,当甜甜圈, 旋转时. 2个部分重叠时. 此时OpenGL 不能清楚分辨 那个图层在前 那个图层在后.则会出现甜甜圈被啃⼀⼝的现象;
  • 隐藏⾯消除,除了使⽤ 正背⾯剔除, 还可以使⽤深度测试来解决。 

什么是深度?

深度就是在PpenGL坐标系中,像素点的 Z 坐标距离观察者的距离,当观察者可以放在坐标系的任意位置, 所以不能简单的说Z 数值越⼤或越⼩,观察者就越靠近物体;

如果观察者在Z轴的正⽅向, Z值越⼤则靠近观察者;
如果观察者在Z轴的负⽅向, Z值越⼩则靠近观察者;

什么深度缓冲区(DepthBuffer)?

深度缓冲区存储在显存中;
深度缓存区原理, 就是把距离观察者平⾯(近裁剪⾯)的深度值 与 窗⼝中每个像素点1对1进⾏关联以及存储; 如: 屏幕尺寸为120 * 120 ,则缓冲区里120 * 120 个存储深度值。深度值的范围:0~1。

清空深度缓存区 

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

开启深度测试

glEnable(GL_DEPTH_TEST); 

 步入主题 深度测试

深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的.颜⾊缓存区存储像素的颜⾊信息,⽽深度缓冲区存储像素的深度信息. 在决定是否绘制⼀个物体表⾯时, ⾸先要将表⾯对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较. 如果⼤于深度缓冲区中的值,则丢弃这部分.否则利⽤这个像素对应的深度值和颜⾊值.分别更新深度缓冲区和颜⾊缓存区. 这个过程称为 深度测试”。

开启了深度测试,则在绘制每个像素之前, OpenGL 会把它的深度值与⽬前像素点对应存储的深度值,进⾏⽐较. 如果 像素点新对应深度值 小于 像素点对应的深度值 . (⽐较当前2个图层时, 那个图层更加接近于观察者) 那么此时就会将该像素点的深度值进⾏取⽽代之; 反之,如果像素点上的新颜⾊值如果距离观察者更远,则应该会遮挡. 那么此时它所对应的深度值与颜⾊值就会被抛弃. 不进⾏绘制;

前面开启了深度测试,那么完成绘制后需要关闭关闭深度测试:

glDisable(GL_DEPTH_TEST);

深度缓存区的默认值为1.0. 表示最⼤的深度值. 深度值的范围是[0,1]之间。


可以通过 glDepthFunc(GLenum func) 来修改 深度测试的测试规则. 那么测试的规则主要在于该函数中的枚举值:


深度测试的潜在⻛险之 Z-fighting (Z冲突.闪烁)问题?

因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分. 这样实现的显示更加真实.但是由于深度缓冲区精度的限制对于深度相差⾮常⼩的情况下.(例如在同⼀平⾯上进⾏2次制),OpenGL 就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测.显示出来的现象时交错闪烁.的前⾯2个画⾯,交错出现. 


同⼀个位置上 出现的图层,且深度值出现精确度很低时,就会容易引起 ZFighting 现象. 表示2个物体靠的⾮常的近,⽆法确定谁在前,谁在后. ⽽出现显示歧义。

如果解决ZFighting 问题?

既然是因为靠的太近,⽆法区分图层先后. 那么此时,就可以在2个图层之间加⼊⼀个微妙的间隔. 那么⼿动添加,复杂且不精确. 此时OpenGL 提供⼀个解决⽅案, "多边形偏移" 。
  • 启⽤多边形偏移 Polygon Offset 
解决⽅法: 让深度值之间产⽣间隔.如果2个图形之间有间隔,是不是意味着就不会产⽣⼲涉.可以理解为在执⾏深度测试前将⽴⽅体的深度值做⼀些细微的增加.于是就能将重叠的2个图形深度值之前有所区分。

//启⽤Polygon Offset ⽅式 glEnable(GL_POLYGON_OFFSET_FILL) 参数列表: GL_POLYGON_OFFSET_POINT 对应填充方式: GL_POINT GL_POLYGON_OFFSET_LINE 对应填充方式: GL_LINE GL_POLYGON_OFFSET_FILL 对应填充方式: GL_FILL 

  •  指定偏移量 
  1. 通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units,
  2. 每个Fragment 的深度值都会增加如下所示的偏移量: Offset = ( m * factor ) + ( r * units); m : 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0, r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的⼀个常量。
  3. ⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉
  4. ⼀般⽽⾔,只需要将-1 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求.
关闭多边形偏移 Polygon Offset

glDisable(GL_POLYGON_OFFSET_FILL) 

 如何预防ZFighting闪烁问题 ?

  • 不要将两个物体靠的太近,避免渲染时三⻆形叠在⼀起。这种⽅式要求对场景中物体插⼊⼀个少量的偏移,那么就可能避免ZFighting现象。例如上⾯的⽴⽅体和平⾯问题中,将平⾯下移0.001f就可以解决这个问题。当然⼿动去插⼊这个⼩的偏移是要付出代价的。 
  • 尽可能将近裁剪⾯设置得离观察者远⼀些。上⾯我们看到,在近裁剪平⾯附近,深度的精确度是很⾼的,因此尽可能让近裁剪⾯远⼀些的话,会使整个裁剪范围内的精确度变⾼⼀些。但是这种⽅式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪⾯参数。 
  • 使⽤更⾼位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有⼀些硬件使⽤使⽤32/64位的缓冲区,使精确度得到提⾼ 。

颜⾊混合 


OpenGL 渲染时会把颜⾊值存在颜⾊缓存区中,每个⽚段的深度值也是放在深度缓冲区。当深
度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜⾊缓存区存在的颜⾊值,当深度缓冲区再次打开时,新的颜⾊⽚段只是当它们⽐原来的值更接近邻近的裁剪平⾯才会替换原来的颜⾊⽚段。
如果开启深度测试后.但是2个重叠的图层中, 有⼀个图层是半透明的. 有⼀个图层是⾮半透明的.
那么此时就不能进⾏单纯的 ⽐较深度值,然后进⾏覆盖. ⽽是需要将2个图层的颜⾊进⾏混合; 

1 glEnable(GL_BlEND);

目标颜色

  • 已经储存在颜色缓冲区的颜色值。

源颜色

  • 作为当前渲染命令结果进⼊颜⾊缓存区的颜⾊值

当在固定着色器或可编程着色器都可以使用开关的方式进行颜色混合,

当在可编程着色器里的片元着色器处理一张原图颜色混一层薄薄的绿色,就需要颜色混合方程式计算。

 当混合功能被启动时,源颜⾊和⽬标颜⾊的组合⽅式是混合⽅程式控制的。在默认情况下,混合⽅程式如下:

Cf = (Cs * S) + (Cd * D) Cf :最终计算参数的颜⾊ Cs :源颜⾊ Cd :⽬标颜⾊ S  :源混合因⼦ D  :⽬标混合因⼦ 

 设置混合因⼦ 设置混合因⼦,需要⽤到glBlendFun函数 
glBlendFunc(GLenum S,GLenum D); S:源混合因⼦ D:⽬标混合因⼦ 

 

表中R、G、B、A 分别代表 红、绿、蓝、alpha。
表中下标S、D,分别代表源、⽬标
表中C 代表常量颜⾊(默认⿊⾊)

总结颜⾊混合

源颜⾊的alpha值越⾼,添加的蓝⾊颜⾊成分越⾼,⽬标颜⾊所保留的成分就会越少。
混合函数经常⽤于实现在其他⼀些不透明的物体前⾯绘制⼀个透明物体的效果。

修改颜⾊混合⽅程式 

默认混合⽅程式:Cf = (Cs * S) + (Cd * D)

1 //选择混合⽅程式的函数: 2 glbBlendEquation(GLenum mode); 

可用的混合方程式


1】常量混合颜⾊,默认初始化为⿊⾊(0.0f,0.0f,0.0f,0.0f),但是还是可以修改这个常量混 合颜⾊。 
2】void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclam pf alpha );

主要代码

//颜色混合
#include "GLTools.h"
#include "GLShaderManager.h"

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

GLBatch    squareBatch;
GLBatch greenBatch;
GLBatch redBatch;
GLBatch blueBatch;
GLBatch blackBatch;

GLShaderManager    shaderManager;


GLfloat blockSize = 0.2f;
GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f,
    blockSize, -blockSize, 0.0f,
    blockSize,  blockSize, 0.0f,
    -blockSize,  blockSize, 0.0f};


void SetupRC()
{
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f );
    shaderManager.InitializeStockShaders();

    //绘制1个移动矩形
    squareBatch.Begin(GL_TRIANGLE_FAN, 4);
    squareBatch.CopyVertexData3f(vVerts);
    squareBatch.End();
    
    //绘制4个固定矩形
    GLfloat vBlock[] = { 0.25f, 0.25f, 0.0f,
        0.75f, 0.25f, 0.0f,
        0.75f, 0.75f, 0.0f,
        0.25f, 0.75f, 0.0f};
    
    greenBatch.Begin(GL_TRIANGLE_FAN, 4);
    greenBatch.CopyVertexData3f(vBlock);
    greenBatch.End();
    
    
    GLfloat vBlock2[] = { -0.75f, 0.25f, 0.0f,
        -0.25f, 0.25f, 0.0f,
        -0.25f, 0.75f, 0.0f,
        -0.75f, 0.75f, 0.0f};
    
    redBatch.Begin(GL_TRIANGLE_FAN, 4);
    redBatch.CopyVertexData3f(vBlock2);
    redBatch.End();
    
    
    GLfloat vBlock3[] = { -0.75f, -0.75f, 0.0f,
        -0.25f, -0.75f, 0.0f,
        -0.25f, -0.25f, 0.0f,
        -0.75f, -0.25f, 0.0f};
    
    blueBatch.Begin(GL_TRIANGLE_FAN, 4);
    blueBatch.CopyVertexData3f(vBlock3);
    blueBatch.End();
    
    
    GLfloat vBlock4[] = { 0.25f, -0.75f, 0.0f,
        0.75f, -0.75f, 0.0f,
        0.75f, -0.25f, 0.0f,
        0.25f, -0.25f, 0.0f};
    
    blackBatch.Begin(GL_TRIANGLE_FAN, 4);
    blackBatch.CopyVertexData3f(vBlock4);
    blackBatch.End();
}

#pragma mark - 上下左右键位控制移动
void SpecialKeys(int key, int x, int y)
{
    GLfloat stepSize = 0.025f;
    
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[7];
    
    if(key == GLUT_KEY_UP)
        blockY += stepSize;
    
    if(key == GLUT_KEY_DOWN)
        blockY -= stepSize;
    
    if(key == GLUT_KEY_LEFT)
        blockX -= stepSize;
    
    if(key == GLUT_KEY_RIGHT)
        blockX += stepSize;
    
    
    if(blockX < -1.0f) blockX = -1.0f;
    if(blockX > (1.0f - blockSize * 2)) blockX = 1.0f - blockSize * 2;;
    if(blockY < -1.0f + blockSize * 2)  blockY = -1.0f + blockSize * 2;
    if(blockY > 1.0f) blockY = 1.0f;
    
    
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    
    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    
    squareBatch.CopyVertexData3f(vVerts);
    
    glutPostRedisplay();
}

#pragma mark - 召唤场景void RenderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
   
    //1.开启混合
    glEnable(GL_BLEND);
    //2.开启组合函数 计算混合颜色因子
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    //定义4种颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
     GLfloat vRed1[] = { 1.0f, 0.0f, 0.0f, 0.5f };
    GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 0.5f };
    GLfloat vBlue[] = { 0.0f, 0.0f, 1.0f, 0.5f };
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 0.5f };
    
    //3.使用着色器管理器
    //*使用 单位着色器
    //参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
    //参数2:着色器颜色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    //4.容器类开始绘制
    squareBatch.Draw();
    
    //5.召唤场景的时候,将4个固定矩形绘制好
    //使用 单位着色器
    //参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
    //参数2:着色器颜色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen);
    greenBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed1);
    redBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue);
    blueBatch.Draw();
    

    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlack);
    blackBatch.Draw();
    
    //5.关闭混合功能
    glDisable(GL_BLEND);
    
    //同步绘制命令
    glutSwapBuffers();
}


void ChangeSize(int w, int h)
{
    glViewport(0, 0, w, h);
}

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("移动矩形,观察颜色");
    
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    glutSpecialFunc(SpecialKeys);
    
    SetupRC();
    
    glutMainLoop();
    return 0;
}

查看案列Demo,运行效果: