JHHK

欢迎来到我的个人网站
行者常至 为者常成

3、OpenGL渲染技巧解析

目录

正背面剔除

一、出现问题的原因

在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部分是对观察者不可见的.对于不可见的部分,应该及早丢弃.例如在⼀个不透明的墙壁后,就不应该渲染.这种情况叫做”隐藏⾯消除”(Hidden surface elimination).

img

没有剔除背面导致显示出现问题

二、如何解决

如果我们能以某种⽅式去丢弃这部分数据就可以解决该问题,同时OpenGL渲染的性能也可提⾼超过50%。

通过正背⾯面剔除(Face Culling),OpenGL可以做到检查所有正⾯朝向观察者的面,并渲染它们.从⽽丢弃背⾯朝向的面. 这样可以 节约⽚元着⾊器的性能.

那如果告诉OpenGL 你绘制的图形,哪个⾯是正面,哪个⾯是背面?

通过分析顶点数据的顺序

img

正⾯: 按照逆时针顶点连接顺序的三⻆形面
背面: 按照顺时针顶点连接顺序的三⻆形⾯

img

分析

左侧三⻆形顶点顺序为: 1—> 2—> 3 ; 右侧三⻆形的顶点顺序为: 1—> 2—> 3 .

当观察者在右侧时,则右边的三⻆形为逆时针⽅向则为正⾯,⽽左侧的三⻆形为顺时针则为背面。

当观察者在左侧时,则左边的三⻆形为逆时针方向则为正⾯,⽽右侧的三⻆形为顺时针则为背面。

总结

正⾯和背面是由三角形的顶点定义顺序和观察者方向共同决定的.随着观察者的角度方向的改变,正⾯背⾯也会跟着改变

三、API接口

开启表面剔除(默认背⾯剔除)
void glEnable(GL_CULL_FACE);

关闭表⾯剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);

⽤户选择剔除哪个面(正面/背面)
void glCullFace(GLenum mode);
mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK

用户指定哪个绕序为正面
void glFrontFace(GLenum mode);
mode参数为: GL_CW,GL_CCW,默认值:GL_CCW

例如,剔除正面实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);

例如,剔除正⾯实现(2)
glCullFace(GL_FRONT);

深度测试

一、深度与深度缓存区

1、什么叫深度

深度其实就是该像素点在3D世界中距离摄像机的距离,在坐标系中,像素Z坐标距离观察者的距离,Z值.

2、什么叫深度缓存区

深度缓存区,就是⼀块内存区域,专⻔存储着每个像素点(绘制在屏幕上的)深度值.深度值(Z值)越⼤, 则离摄像机就越远.

3、深度缓冲区作用

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

二、深度测试

1、什么是深度测试

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

深度缓冲区,⼀般由窗⼝管理系统,GLFW创建.深度值⼀般由16位,24位,32位值表示. 通常是24位.位数越高,深度精确度更好.

开启深度测试
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);
清除深度缓冲区默认值为1.0,表示最⼤的深度值,深度值的范围为(0,1)之间. 值越⼩表示越靠近观察者,值越⼤表示越远离观察者

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

2、深度测试判断模式

void glDepthFunc(GLEnum mode);

函数 说明
GL_ALWAYS 总是通过测试
GL_NEVER 总是不通过测试
GL_LESS 在当前深度值 < 存储的深度值时通过
GL_EQUAL 在当前深度值 = 存储的深度值时通过
GL_LEQUAL 在当前深度值 <= 存储的深度值时通过
GL_GREATER 在当前深度值 > 存储的深度值时通过
GL_NOTHQUAL 在当前深度值 不等于 存储的深度值时通过
GL_GEQUAL 在当前深度值 >= 存储的深度值时通过

默认值是GL_LESS

三、ZFighting闪烁问题

1、为什么会出现 ZFighting 闪烁问题

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

img

当我们的图形不是特别复杂时这种问题基本不会遇见。

这个问题可以通过 启⽤ Polygon Offset ⽅式解决,思路就是让深度值之间产⽣间隔.如果2个图形之间有间隔,是不是意味着就不会产⽣干涉.可以理解为在执⾏深度测试前将⽴方体的深度值做⼀些细微的增加.于是就能将重叠的2个图形深度值之间有所区分. 这里就不细说了

2、ZFighting闪烁问题预防

不要将两个物体靠的太近,避免渲染时三⻆形叠在⼀起。这种⽅式要求对场景中物体插⼊⼀个少量的偏移,那么就可能避免ZFighting现象。例如上⾯的⽴方体和平⾯中,将平⾯下移0.001f就可以解决这个问题。当然⼿动去插入这个⼩小的偏移是要付出代价的。

尽可能将近裁剪⾯设置得离观察者远一些。上⾯我们看到,在近裁剪平面附近,深度的精确度是很⾼的,因此尽可能让近裁剪面远⼀些的话,会使整个裁剪范围内的精确度变高一些。但是这种方式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪⾯参数。

使用更高位数的深度缓冲区,通常使用的深度缓冲区是24位的,现在有一些硬件使⽤32位的缓冲区,使精确度得到提高

裁剪

一、什么是裁剪

在OpenGL 中提高渲染的⼀种方式.只刷新屏幕上发生变化的部分.OpenGL 允许将要进行渲染的窗⼝去指定一个裁剪框.

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

二、窗口、视口、裁剪区域

窗⼝: 就是显示界⾯

视口: 就是窗口中⽤来显示图形的⼀块矩形区域,它可以和窗口等大,也可以比窗口⼤或者小。只有绘制在视⼝区域中的图形才能被显示,如果图形有⼀部分超出了视口区域,那么那一部分是看不到的。通过glViewport()函数设置。

裁剪区域(平⾏投影):就是视口矩形区域的最⼩最⼤x坐标(left,right)和最⼩最⼤y坐标 (bottom,top),⽽不是窗口的最小最⼤x坐标和y坐标。通过glOrtho()函数设置,这个函数还需指定最近最远z坐标,形成⼀个⽴体的裁剪区域。

img

三、API接口

开启裁剪测试
glEnable(GL_SCISSOR_TEST);

关闭裁剪测试
glDisable(GL_SCISSOR_TEST);

指定裁剪窗⼝
void glScissor(Glint x,Glint y,GLSize width,GLSize height);
x,y:指定裁剪框左下角位置
width , height:指定裁剪尺寸

颜色混合

一、混合

我们把OpenGL 渲染时会把颜色值存在颜⾊缓存区中,每个⽚段的深度值也是放在深度缓冲区。

当深度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜色缓存区中的颜⾊值。

当深度缓冲区再次打开时,新的颜⾊片段只有当它们比原来的值更接近邻近的裁剪平面时才会替换原来的颜色片段。

开启混合模式
glEnable(GL_BlEND);

二、混合颜色

1、混合原理

⽬标颜色:已经存储在颜⾊缓存区的颜⾊值

源颜色:作为当前渲染命令结果进入颜色缓存区的颜色值

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

Cf = (Cs * S) + (Cd * D)

Cf :最终计算参数的颜⾊
Cs : 源颜⾊
Cd :⽬标颜⾊
S:源混合因子
D:目标混合因⼦

2、混合因子

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

img

表中R、G、B、A 分别代表 红、绿、蓝、alpha。
表中下标s、d,分别代表源、⽬标
表中c 代表常量颜色(默认⿊色)

下⾯通过⼀个常⻅的混合函数组合来说明问题

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

如果颜色缓存区已经有一种颜⾊红色(1.0f,0.0f,0.0f,0.0f),这就是目标颜色Cd,

如果在这上面用⼀种alpha为0.6的蓝⾊(0.0f,0.0f,1.0f,0.6f)进行混合,这就是源颜色

Cd (⽬标颜色) = (1.0f,0.0f,0.0f,1.0f)
Cs (源颜色) = (0.0f,0.0f,1.0f,0.6f)
S = 源alpha值 = (0.6f,0.6f,0.6f,0.6f)
D = 1 - 源alpha值 = (0.4f,0.4f ,0.4f,0.4f)
⽅程式Cf = (Cs * S) + (Cd * D)
等价于 = (Blue * 0.6f) + (Red * 0.4f)

最终颜⾊是以原先的红色(⽬标颜色)与 后来的蓝色(源颜色)进⾏组合。源颜色的alpha值越高,添加的蓝⾊颜色成分越高,目标颜⾊所保留的成分就会越少。

混合函数经常用于实现在其他一些不透明的物体前面绘制一个透明物体的效果。

3、混合因子设置

除了能使⽤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
值允许混合⽅程式中引⼊⼀个常量混合颜色。

常量混合颜色,默认初始化为⿊色(0.0f,0.0f,0.0f,0.0f),但是还是可以修改这个常量混合颜⾊

void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclampf alpha );

三、混合方程式

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

实际上远不⽌这一种混合⽅程式,我们可以从5个不同的方程式中进⾏选择。

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

img


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.