OpenGL-10-渲染技巧:深度测试、多边形偏移、颜色混合

上篇文章提到了 深度测试 可以解决隐藏面消除。这次来深入理解一下深度测试,以及深度测试带来的问题和解决方法:多边形偏移、颜色混合等技巧

一、深度测试

image.png

上篇文章提到,由于两个正面叠加在一起,出现了混合, 此时OpenGL不能清楚分辨 哪个图层在前 哪个图层在后,于是就会出现甜甜圈像被啃⼀⼝的现象。我们通过开启深度测试就能解决这个问题。

1、什么是深度?

深度就是在openGL坐标系中,像素点的 Z 坐标距离观察者的距离。

  • 因为观察者可以放在坐标系中的任意位置,所以不能说Z的值越大/越小,就代表观察者越靠近物体
  • 如果观察者在Z轴的正方向,图形的Z值越大,越靠近观察者
  • 如果观察者在Z轴的负方向,图形的Z值越小,越靠近观察者

2、什么是深度缓冲区?

深度缓冲区:就是⼀块内存区域,存储在显存中。专⻔存储着每个像素点(绘制在屏幕上的)深度值,深度值(Z值)越⼤,则离观察者就越远。

原理:把距离观察者平面(近裁剪面)的深度值 和 窗口每个像素点1对1进行关联及存储。

//清空深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

3、为什么需要深度缓冲区?

  • 在不使⽤深度测试的时候,如果我们先绘制⼀个距离⽐较近的物体,再绘制距离较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。
  • 有了深度缓冲区后,绘制物体的顺序就不那么重要了。实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写⼊到缓冲区中. 除⾮调⽤【 glDepthMask(GL_FALSE) 】来禁⽌写⼊。

4、深度测试

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

5、深度值计算

  • 深度值⼀般由16位,24位或者32位值表示,通常是24位。位数越⾼的话,深度的精确度越
    好。深度值的范围在[0,1]之间,值越⼩表示越靠近观察者,值越⼤表示远离观察者。
  • 深度缓冲主要是通过计算深度值来⽐较⼤⼩,在深度缓冲区中包含深度值介于0.0和1.0之间,从观察者看到其内容与场景中的所有对象的 z 值进⾏了⽐较。这些视图空间中的 z 值可以在投影平头截体的近平⾯和远平⾯之间的任何值。我们因此需要⼀些⽅法来转换这些视图空间 z 值 到 [0,1] 的范围内,下⾯的 (线性) ⽅程把 z 值转换为 0.0 和 1.0 之间的值
image.png

6、深度测试的使用

  • 深度缓冲区,⼀般由窗⼝管理系统,GLFW创建.深度值⼀般由16位,24位,32位值表示. 通常是24位.位数越⾼,深度精确度更好
  • 清除深度缓冲区默认值为1.0,表示最⼤的深度值,深度值的范围为(0,1)之间. 值越⼩表示越靠近观察者,值越⼤表示越远离观察者
//开启深度测试
glEnable(GL_DEPTH_TEST); 
//关闭深度测试
glDisable(GL_DEPTH_TEST);
//在绘制场景前,清除颜⾊缓存区,深度缓冲
glClearColor(0.0f,0.0f,0.0f,1.0f); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

指定深度测试判断模式

void glDepthFunc(GLEnum mode);
image.png

打开/阻断 深度缓存区写⼊

void glDepthMask(GLBool value); 
value : GL_TURE 开启深度缓冲区写⼊; GL_FALSE 关闭深度缓冲区写⼊

7、深度测试潜在风险:Z-fighting 闪烁问题

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


    image.png

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

  • 那么,如何解决这个问题呢?
    既然是因为靠的太近,⽆法区分图层先后. 那么此时,就可以在2个图层之间加⼊⼀个微妙的间隔. 那么⼿动 添加,复杂且不精确. 此时OpenGL 提供⼀个解决⽅案, "多边形偏移"

  • 如何预防ZFighting闪烁问题
    1、避免两个物体靠的太近:在绘制时,插入一个小偏移(例如:0.001f就可以解决上面问题)
    2、将近裁剪面(设置透视投影时设置)设置的离观察者远一些:提高裁剪范围内的精确度
    3、使用更高位数的深度缓冲区:提高深度缓冲区的精确度(现在大多是24位,提升到32/64位)

二、多边形偏移

1、启⽤多边形偏移 Polygon Offset

苹果针对Z-fighting的解决⽅法:让深度值之间产⽣间隔。如果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

2、指定偏移量

  • 通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units
  • 每个Fragment 的深度值都会增加如下所示的偏移量:
    Offset = ( m * factor ) + ( r * units);
    m : 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0.
    r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的⼀个常量.
  • ⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉近
  • ⼀般⽽⾔,只需要将-1.0 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求
void glPolygonOffset(Glfloat factor,Glfloat units);
应⽤到⽚段上总偏移计算⽅程式:
Depth Offset = (DZ * factor) + (r * units);
DZ:深度值(Z值)
r:使得深度缓冲区产⽣变化的最⼩值
负值,将使得z值距离我们更近,⽽正值,将使得z值距离我们更远

一般来说,(-1,-1)就行
glPolygonOffset(-1,-1);

3、关闭多边形偏移 Polygon Offset

glDisable(GL_POLYGON_OFFSET_FILL)

三、颜色混合

OpenGL 渲染时会把每个像素点的颜⾊值存在颜⾊缓冲区中,每个像素点的深度值也是存在深度缓冲区中。当深度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜⾊缓存区存在的颜⾊值,当深度缓冲区再次打开时,新的颜⾊⽚段只是当它们⽐原来的值更接近邻近的裁剪平⾯才会替换原来的颜⾊⽚段。

如果在开启深度测试后,2个重叠的图层中,有⼀个图层是半透明的,有⼀个图层是⾮半透明的,OpenGL 需要怎么处理?
此时就不能进⾏单纯的⽐较深度值,然后进⾏覆盖。 ⽽是需要将2个图层的颜⾊进⾏混合。

注意:针对有两个图层叠加,一个半透明,一个不透明。

  • 如果半透明图层在非透明图层的上层,进行颜色混合
  • 如果半透明图层在非透明图层的下层,不需要考虑

1、混合开关

  • 如果单纯讲两个图层进行颜色混合,在固定着色器和可编程着色器下,都可以直接打开开关进行。
//开启,
glEnable(GL_BlEND);
//使用着色器
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
//绘制
squareBatch.Draw();
//关闭
glDisable(GL_BlEND);

2、混合开关+颜色混合方程式

//开启,
glEnable(GL_BlEND);
//开启组合函数 计算混合颜色因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//使用着色器
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
//绘制
squareBatch.Draw();
//关闭
glDisable(GL_BlEND);
  • 如果只是给了2个颜色进行混合,就需要用到颜色混合方程式
默认方程式:Cf = (Cs * S) + (Cd * D)
Cf :最终计算参数的颜⾊ 
Cs :源颜⾊ (作为当前渲染命令结果进⼊颜⾊缓存区的颜⾊值 [例如,现⼥友])
Cd :⽬标颜⾊ (已经存储在颜⾊缓存区的颜⾊值 [例如,前⼥友])
S:源混合因⼦ 
D:⽬标混合因⼦
这里我们只能指定 S 和 D
设置混合因⼦,需要⽤到glBlendFun函数 
glBlendFunc(GLenum S,GLenum D); 
S:源混合因⼦ 
D:⽬标混合因⼦
image.png
  • 表中R、G、B、A 分别代表 红、绿、蓝、alpha。
    表中下标S、D,分别代表源、⽬标
    表中C 代表常量颜⾊(默认⿊⾊)

我们在这里举个例子
利用常见组合:glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

如果颜⾊缓存区已经有⼀种颜⾊Cd:红⾊(1.0f,0.0f,0.0f,0.0f),如果在这上⾯⽤⼀种alpha为0.6的蓝⾊(0.0f,0.0f,1.0f,0.6f)进行混合。
Cd (⽬标颜⾊) = (1.0f,0.0f,0.0f,0.0f);
Cs (源颜⾊) = (0.0f,0.0f,1.0f,0.6f);
S = 源alpha值 = 0.6f
D = 1 - 源alpha值= 1-0.6f = 0.4f
⽅程式Cf = (Cs * S) + (Cd * D) =(Blue * 0.6f) + (Red * 0.4f)

  • 最终颜⾊是以原先的红⾊(⽬标颜⾊)与 后来的蓝⾊(源颜⾊)进⾏组合。源颜⾊的alpha值越⾼,添加的蓝⾊颜⾊成分越⾼,⽬标颜⾊所保留的成分就会越少。
    混合函数经常⽤于实现在其他⼀些不透明的物体前⾯绘制⼀个透明物体的效果。

3、拓展

a、设置组合方程式

选择混合⽅程式的函数:
glbBlendEquation(GLenum mode);
image.png

b、设置混合因⼦
除了能使⽤ glBlendFunc 来设置混合因⼦,还可以有更灵活的选择

void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB ,GLenum strAlpha,GLenum dstAlpha);
strRGB: 源颜⾊的混合因⼦
dstRGB: ⽬标颜⾊的混合因⼦
strAlpha: 源颜⾊的Alpha因⼦
dstAlpha: ⽬标颜⾊的Alpha因⼦
  • glBlendFunc 指定 源和⽬标 RGBA值的混合函数;但是glBlendFuncSeparate函数则允许为RGB 和Alpha 成分单独指定混合函数。
  • 在混合因⼦表中,
    GL_CONSTANT_COLOR、
    GL_ONE_MINUS_CONSTANT_COLOR、
    GL_CONSTANT_ALPHA、
    GL_ONE_MINUS_CONSTANT
    的值允许混合⽅程式中引⼊⼀个常量混合颜⾊。

c、设置常量混合颜⾊

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

四、裁剪

裁剪:在OpenGL 中提⾼渲染的⼀种⽅式,只刷新屏幕上发⽣变化的部分。OpenGL 允许将要进⾏渲染的窗⼝只去指定⼀个裁剪框。

基本原理:⽤于渲染时限制绘制区域,通过此技术可以在屏幕(帧缓冲)指定⼀个矩形区域。启⽤剪裁测试之后,不在此矩形区域内的⽚元被丢弃,只有在此矩形区域内的⽚元才有可能进⼊帧缓冲。因此实际达到的效果就是在屏幕上开辟了⼀个⼩窗⼝,可以再其中进⾏指定内容的绘制。

    //1、设置裁剪区域颜色
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
    //2、设置裁剪区域大小
    glScissor(50, 50, 200, 200);
    //3、开启裁剪测试
    glEnable(GL_SCISSOR_TEST);
    //4、开启清屏,执行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    //5、关闭裁剪测试
    glDisable(GL_SCISSOR_TEST);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348