OpenGL学习- 5.渲染技巧

148

5.渲染技巧

正背面剔除(Face Culling)

在3D场景绘制的时候,我们需要确定哪些面对于观察者是可见的,哪些是观察者不可见的。对于不可见的面,我们就不需要渲染(例如:一面不透明的墙壁后面的就不需要渲染),这种情况就叫"隐藏面消除(Hidden surface elimination)" 我们在绘制一个3D图形时,如果不开启隐藏面消除会出现错误如下,这是因为本来不可见的一面错误的绘制出来了,因此3D绘制的时候我们要启用隐藏面消除功能(通过OpenGL的正背面剔除功能实现) 15719677238753.jpg 正背面剔除思路
1.首先思考,如果我们面前有一个不透明的立方体,那么我们最多能看到立方体的几个面?
答案是3个。
2.那么我们看不见的面还有必要绘制吗?
如果不绘制这些不可见的面,那么我们至少可以减少50%的绘制工作。
3.OpenGL能够正确的知道哪些面是可见的,哪些是不可见的吗?
是的,可以。

所以我们可以通过正背面剔除来节约片元着色器的性能,提高绘制的效率,同时可以防止上面的错误的出现。
OpenGL 正背面剔除原理 首先每个平面都有正面和背面,在任一时刻我们只能看到其中一面。OpenGL要正确的剔除不需要渲染的面,那么需要知道2个前置条件:

  1. 平面的哪个面是正面,哪个是背面(默认从当前观察者视角在不同的观察角度时,正背面是不同的,顶点逆时针连接的为正面)
  2. 想要剔除正面还是背面(默认背面剔除)

我们可以根据需求自己设置正背面,不过一般不太使用,因为OpenGL是状态机,我们改变了正背面原则,可能会影响其他图形的渲染 开启正背面剔除 glEnable(GL_CULL_FACE); 设置逆时针为正面glFrontFace(GL_CCW)GL_CCW:逆时针(默认)GL_CW:顺时针 设置剔除背面glCullFace(GL_BACK);GL_BACK:背面 GL_FRONT:正面 GL_FRONT_AND_BACK:正背面 关闭正背面剔除glDisable(GL_CULL_FACE);

深度测试

深度: 深度其实就是该像素点在3D世界中距离摄像机的远近(z轴方向)。深度值一般由16位,24位或者32位值表示,通常是24位。位数越高的话,深度的精确度越好。深度值的范围在[0,1]之间,值越小表示越靠近观察者,值越⼤表示远离观察者。要注意的是深度不是实际的z轴数值,它是z轴数值通过计算得到的一个[0,1]之间的值
深度缓冲区: 深度缓存区就是一块内存区域,专门存储着每个像素点(绘制在屏幕上的)深度值.深度值(Z值)越⼤, 则离摄像机就越远.
开启/关闭深度缓冲区写入void glDepthMask(GLBool value);GL_TURE 开启深度缓冲区写入; GL_FALSE 关闭深度缓冲区写⼊
深度测试: 深度缓冲区(DepthBuffer)和颜色缓存区(ColorBuffer)是相对应的。颜色缓存区存储像素的颜色值信息,深度缓冲区存储像素的深度值信息。在决定是否绘制一个物体的表面时,首先要将表面对应的像素的深度值跟当前深度缓冲区中的值进行比较。如果大于深度缓冲区里的值,那么就丢弃这部分。否则就利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓存区。这个过程就叫做“深度测试”。实际绘制是通过深度缓冲区和颜色缓存区里的值来进行的。
下图是未开启深度测试的甜甜圈,可见因深度问题导致绘制出现错误 15719701125756.jpg 开启深度测试glEnable(GL_DEPTH_TEST);
关闭深度测试glDisable(GL_DEPTH_TEST);
指定深度测试判断模式void glDepthFunc(GLEnum mode);

深度判断模式规则
GL_ALWAYS总是通过测试
GL_NEVER总是不通过
GL_LESS在当前深度值 < 存储的深度值是通过
GL_LEQUAL在当前深度值 <= 存储的深度值是通过
GL_GREATER在当前深度值 > 存储的深度值是通过
GL_GEQUAL在当前深度值 >= 存储的深度值是通过
GL_EQUAL在当前深度值 == 存储的深度值是通过
GL_NOTEQUAL在当前深度值 != 存储的深度值是通过

15719865314514.jpg 15719865614534.jpg 15719865787509.jpg 15719865981466.jpg 15719865438894.jpg

ZFighting闪烁问题
因为深度缓冲区的精度限制,当2个物体的深度值相差小于精度时,会导致OpenGL无法正确判断两者的深度值,使深度测试的结果不可预测,显示出来就是2个物体交错出现(闪烁)。不过一般很难遇到的。
解决办法:
让深度值之间产生足够的间隔,使之能被深度缓冲区正常识别出来就好了。分3步解决:

  1. 启用Polygon Offset方式glEnable(GL_POLYGON_OFFSET_POINT)GL_POLYGON_OFFSET_POINT 对应光栅化模式 GL_POINT; GL_POLYGON_OFFSET_LINE 对应光栅化模式 GL_LINE; GL_POLYGON_OFFSET_FILL 对应光栅化模式 GL_FILL
  2. 指定偏移量glPolygonOffset(Glfloat factor, Glfloat units)
  3. 关闭Polygon OffsetglDisable(GL_POLYGON_OFFSET_POINT)

裁剪测试

OpenGL中我们可以通过设置裁剪区域和开启裁剪测试,来实现局部绘制。其基本原理是:在渲染时限制绘制区域(矩形区域),在启用裁剪测试后,不在矩形区域内的片元被丢弃,只有在矩形区域内的片元才可能进入帧缓冲。实际达到的效果就是在屏幕上开辟了一个小的窗口,可以再在其中进行指定内容的绘制。

15719922792767.jpg 开启裁剪测试glEnable(GL_SCISSOR_TEST);
关闭裁剪测试glDisable(GL_SCISSOR_TEST);
设置裁剪区域glScissor(100, 100, 200, 200);
15719922601460.jpg

颜色混合

开启颜色混合功能glEnable(GL_BLEND);
设置混合方程式glBlendEquation(GLenum mode);
设置混合因子:
1.glBlendFunc(GLenum S, GLenum D);S:源混合因子D:目标混合因子
2. void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB ,GLenum strAlpha,GLenum dstAlpha); strRGB: 源颜色的混合因子 dstRGB: ⽬标颜色的混合因子 strAlpha: 源颜色的Alpha因子 dstAlpha: ⽬标颜色的Alpha因⼦
设置常量颜色glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclampf alpha)默认为黑色
关闭颜色混合功能glDisable(GL_BLEND);
颜色混合后显示的颜色是由源颜色、目标颜色通过混合方程式计算出来的。
目标颜色 已存储在颜色缓存区的颜色(先存在的)
源颜色 作为当前渲染命令结果进入颜色缓存区的颜色值(要添加的)
混合方程式 默认情况下为:Cf = (Cs * S) + (Cd * D) Cf:为最终计算参数的颜色;Cs:源颜色;Cd:目标颜色;S:源混合因子;D:目标混合因子

混合方程式函数
GL_FUNC_ADDCf = (Cs * S) + (Cd * D)
GL_FUNS_SUBTRACTCf = (Cs * S) - (Cd * D)
GL_FUNC_REVERSE_SUBTRACTCf = (Cd * D) - (Cs * S)
GL_MINCf = min(Cs,CD)
GL_MAXCf = max(Cs,Cd)
混合因子枚举RGB混合因子Alpha混合因子
GL_ZERO(0,0,0)0
GL_ONE(1,1,1)1
GL_SRC_COLOR(Rs,Gs,Bs)As
GL_ONE_MINUS_SRC_COLOR(1,1,1)-(Rs,Gs,Bs)1-As
GL_DST_COLOR(Rd,Gd,Bd)Ad
GL_ONE_MINUS_DST_COLOR(1,1,1)-(Rd,Gd,Bd)1-Ad
GL_SRC_ALPHA(As,As,As)As
GL_ONE_MINUS_SRC_ALPHA(1,1,1)-(As,As,As)1-As
GL_DST_ALPHA(Ad,Ad,Ad)Ad
GL_ONE_MINUS_DST_ALPHA(1,1,1)-(Ad,Ad,Ad)1-Ad
GL_CONSTANT_COLOR(Rc,Gc,Bc)Ac
GL_ONE_MINUS_CONSTANT_COLOR(1,1,1)-(Rc,Gc,Bc)1-Ac
GL_CONSTANT_ALPHA(Ac,Ac,Ac)Ac
GL_ONE_MINUS_CONSTANT_ALPHA(1,1,1)-(Ac,Ac,Ac)1-Ac
GL_SRC_ALPHA_SATURATE(f,f,f)*f = min(As,1-Ad)1
表中R、G、B、A为红、绿、蓝、alpha;下标s、d为源混合因子、目标混合因子;下标c代表常量颜色(默认黑色)

代码资源见:Github