专栏首页小黑娃HenryOpenGL(六)-- 渲染技巧:正背面剔除、深度测试、多边形偏移OpenGL(六)-- 渲染技巧:正背面剔除、深度测试、多边形偏移

OpenGL(六)-- 渲染技巧:正背面剔除、深度测试、多边形偏移OpenGL(六)-- 渲染技巧:正背面剔除、深度测试、多边形偏移

OpenGL(六)-- 渲染技巧:正背面剔除、深度测试、多边形偏移、颜色混合

通过一个基础案例来了解这些渲染技巧:正背面剔除、深度测试、多边形偏移。应该更容易理解。

案例

通过使用系统几何图形,绘制并移动图形。下面放出核心代码。

void RenderScene(){
    ...
    //把摄像机矩阵压入模型矩阵中
    modelViewMatix.PushMatrix(cameraFrame);
    //使用默认光源着色器
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
}

void SetupRC(){
    ...
    //观察者向后移动
    objectFrame.MoveForward(-10.0);
    //创建圆环
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
}

void ChangeSize(int w, int h){
    ...
    //创建透视矩阵
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    //创建渲染管线
    transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
    //绘制
    torusBatch.Draw();
    //出栈 绘制完成恢复
    modelViewMatix.PopMatrix();
    //交换缓存区
    glutSwapBuffers();
}

void SpecialKeys(int key, int x, int y){
    ...
    //根据方向调整观察者位置
    if(key == GLUT_KEY_UP)
        cameraFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
    ...
}

int main(int argc, char* argv[]){
    ...
    glutReshapeFunc(ChangeSize);    //创建窗口回调
    glutSpecialFunc(SpecialKeys);   //创建键盘事件
    glutDisplayFunc(RenderScene);   //创建渲染回调
    SetupRC()
}

在你认为大功告成的时候,发现问题并不简单。

正背面剔除

移动后发现会有没有上色的部分,代码并没有问题。仔细观察后发现黑色部分是因为OpenGL认为那是你看不到的地方是隐藏⾯所以没有绘制。

举例: 一个苹果放在桌上,你不可能一眼看到苹果的所有面,你看到的相当于图中的有色一面,你看不到的部分就相当于黑色一面。

放到OpenGL里虽然作为观察者已经移动到隐藏⾯了,但是OpenGL还是认为它还是隐藏⾯也就是背面,不需要绘制,这就造成了我们看到的一幕。这就是OpenGL中的隐藏⾯消除

移动后的黑色是因为使用的光源着色器,使隐藏面可以观察到。即使不使用光源着色器,虽然察觉不到隐藏⾯,但是隐藏⾯消除问题依旧存在。如果小伙伴有更好的观察方式也可以私信我。

OpenGL中的正面、背面

上文中提到了一个概念背面正面 背面都是OpenGl人为定义的概念。

  • 正面:点的绘制顺序是:逆时针
  • 背面:点的绘制顺序是:顺时针

就像图中右侧三角形(图元)代表的是正面,左侧代表的是背面。 有一个很容易记忆的方式: 右手握拳后,如果绘制方向与手指方向一致则为正面,反之

正背面剔除

在了解正背面剔除之前,先了解一下OpenGL是如何绘制3D图形的,我们所知的油画算法在绘制下图这种情况时就派不上用场了,因为相互叠加无法区分图层的先后,所以OpenGl选择了正背面剔除的渲染方式。

  • 正背面剔除:只绘制我们可以观察到的面,这样做及解决了优化算法的问题,而且在渲染的性能即可提⾼高超过50%

使用正背面剔除的方式:

void RenderScene(){
    ...
    //开启表⾯面剔除(默认剔除背面)
    void glEnable(GL_CULL_FACE);
    //关闭表⾯面剔除(默认背⾯面剔除)
    void glDisable(GL_CULL_FACE);
}

很显然影藏面的问题已经解决,但是却发现了新问题,在某个角度下少了一块。

深度测试

在解决了隐藏面问题的同时,却引来了一个新的问题,先分析一下问题的成因。

从现在这个角度观察,图中的A、B面都是正面,而我们有开启了正背面剔除。导致OpenGl又不知道要绘制哪个面了,所以在某个角度下出现了绘制的错乱。

首先我们通过生活经验来思考,如果出现2个正面重叠的情况时,应该显示的是距离我们更近的那一部分,因为远的那一部分被遮挡了,相当更远的那一部分成了“隐藏面”绘制时应该被放弃。

  • 在3D模型中,距离观察者的距离表示为:深度。其实就是该像素点在3D世界中距离摄像机的距离,Z值。。
  • 所以在绘制之前需要知道每个点距离观察者的距离,而存放计算结果的区域叫做:深度缓冲区

当然这个深度可以简单总结为: 观察者在Z轴正方向, 图形的Z值越大。表示距离观察者越近 观察者在Z轴负方向, 图形的Z值越大。表示距离观察者越远 但是如果只是这样简答表示,当图形的Z值相同时又会有问题出现。

所以在OpenGL中深度值是这样计算的:

far、near是提供投影矩阵设置时使用的可见视图截锥的远近值。公式中的Z值也是矩阵变换后的值。 从公式中可以发现2点:

  1. 深度值在【0,1】之间
  2. 值越⼩小表示越靠近观察者,值越⼤大表示远离观察者

整个过程就叫做深度测试(Z-buffer)

  • 相对应的颜色缓冲区和深度缓存区是一一对应的。在进行深度测试的时候,深度值比较大的会被丢弃,相同的颜色缓冲区也会跟着进行修改。以保证深度缓存区和颜色缓存区中是同一个点的信息。
  • 通过测试利用深度测试在解决深度问题时还同时解决了隐藏面消除的问题,因为隐藏面一定是远离观察者的。

开启深度测试的方式:

void RenderScene(){
    ...
    //开启前进行重操作,重置后默认值为1.0,表示最大深度
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //开启表⾯面剔除(默认剔除背面)
    void glEnable(GL_DEPTH_TEST);
    //关闭表⾯面剔除(默认背⾯面剔除)
    void glDisable(GL_DEPTH_TEST);
}

也可以通过glDepthFunc(GLenum func)来修改深度测试规则,但是一般情况下很少进行修改.

多边形偏移

如果仔细思考深度问题的解决方案就会发现,既然是进行数值的比较,就会有相同、两者非常接近的情况出现。就会导致下图中的问题。 这个现象在游戏中非常常见,这种问题就叫做Z-fighting

Z-fighting出现情况
  1. 数值相同:这个很好理解
  2. 两数非常接近:如果在一个8位系统下一个值为:0.12345671,一个值为0.12345679,而系统中会表示为:0.1234567,这样就会造成相等的情况出现。
解决Z-fighting

当然OpenGL也帮我们想到了,并给出了解决方案多边形偏移,顾名思义就是对深度相同的物体进行微妙的移动。

//1,开启多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL)

绘制模式对应的参数列表:

绘制模式

参数

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

GL_POLYGON_OFFSET_FILL

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

GL_POLYGON_OFFSET_LINE

glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);

GL_POLYGON_OFFSET_POINT

//2,指定偏移量
glPolygonOffset(GLfloat factor, GLfloat units)
//一般设置为glPolygonOffset(-1,-1)

此API代表了公式:OffSet = (m * factor)+(r * units)

  • m代表了:多边形的深度的斜率最大值,理解为一个多边形越是与近裁剪面平行,m就越接近于0
  • r代表了:能产生空口坐标系的省渎职可分辨的差异最小值,r是一个OpenGl定义的常量,可以理解为当前系统的最小精度。

一般设置为(-1,-1)即可解决大部分问题。该参数为负值时表示Z轴离我们越近,反正。

//3,记得关闭
glDisable(GL_POLYGON_OFFSET_FILL)
如何预防

可以在开发初期进行以下3中手段来预防:

  1. 避免两物体靠的过近,毕竟开启多边形便宜是需要消耗性能的。
  2. 让观察者尽量远近裁剪面,这个位置可以回头看看深度测试的那个公式,也就是将公式中的near变大,近异步提高计算精度。
  3. 使用更高位数的深度缓存区。现在的计算机精度更高,所以这一条无形的帮我们避免大部分同类问题。
扩展

当然还可以利用多边形偏移来对我们绘制的多边形增加边框,具体实现的核心代码。

    // 偏移深度
    glPolygonOffset(-1.0f, -1.0f);
    glEnable(GL_POLYGON_OFFSET_LINE);
    // 开启抗锯齿锯齿,让黑边更顺滑
    glEnable(GL_LINE_SMOOTH);
    //绘制线
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    //绘制
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();
    //关闭
    glDisable(GL_POLYGON_OFFSET_LINE);
    glDisable(GL_LINE_SMOOTH);

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • OpenGL与OpenGL在移动端的应用

    OpenGL首先我们从字面意思来理解:Open Graphics Library,开放的图形库,图形库自然是处理图形的,所以简单来说OpenGL就是用来处理图形...

    清墨
  • OpenGL ES 2.0 (iOS)[05-1]:进入 3D 世界,从正方体开始

    a. 渲染管线的基础知识 《OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始》

    半纸渊
  • 浅谈 GPU图形固定渲染管线

    图形渲染管道被认为是实时图形渲染的核心,简称为管道。管道的主要功能是由给定的虚拟摄像机、三维物体、灯源、光照模型、纹理贴图或其他来产生或渲染一个二维图像。由此可...

    Tencent JCoder
  • 三维图形渲染显示的全过程

    图像中物体所处位置及外形由其几何数据和摄像机的位置共同决定,物体外表是受到其材质属性、光源、纹理及着色模型所影响。

    刘盼
  • 【专业技术】OpenGL操作技巧介绍

    存在问题: opengl中如何渲染管线? 解决方案: 绝大数OpenGL实现都有相似的操作顺序,一系列相关的处理阶段称为OpenGL渲染管线。图1-2显示了这些...

    程序员互动联盟
  • 20分钟让你了解OpenGL ——OpenGL全流程详细解读

    | 导语 对于开发者来说,学习OpenGL或者其他图形API都不是一件容易的事情。即使是一些对OpenGL有一些经验的开发者,往往也未必对OpenGL有完整、...

    腾讯Bugly
  • OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始

    1). 三个什么端点(屏幕坐标点)? 要回答这个问题要先了解 OpenGL ES 的坐标系在屏幕上是怎样分布的:

    半纸渊
  • 视频直播与虚拟现实的渲染 - OpenGL ES

    这是一篇OpenGL ES的学习笔记,介绍图像绘制里面用到的概念,学习OpenGL ES的基础知识备忘录。 教程 OpenGLES入门教程1-Tutorial0...

    落影
  • OpenGLES-05 立方体3D变换

    开始这篇文章之前,请先了解3D变换的相关知识,下面资料写得很好,请确保已经阅读过有关资料。 1.http://www.cnblogs.com/kesalin/...

    清墨
  • OpenGL ES 2.0 (iOS)[04]:坐标空间 与 OpenGL ES 2 3D空间

    第一次变换 模型变换(Model Transforms):就是指从模型空间转换到世界空间的过程

    半纸渊
  • Unity通用渲染管线(URP)系列(四)——方向阴影(Cascaded Shadow Maps)

    当进行物体渲染时,表面和灯光信息足以计算光照。但是在两者之间可能存在某些阻碍光线的东西,导致在我们需要渲染的表面上投射了阴影。为了使阴影能够正常表现,就必须以某...

    放牛的星星
  • 用OpenGL绘制平滑着色的三角形与相交区域的混合着色

    Zoctopus
  • OpenGL ES实践教程(三)镜子效果

    教程 OpenGLES实践教程1-Demo01-AVPlayer OpenGL ES实践教程2-Demo02-摄像头采集数据和渲染 其他教程请移步OpenG...

    落影
  • Unity可编程渲染管线系列(十一)后处理(全屏特效)

    这是涵盖Unity的可脚本化渲染管道的教程系列的第11部分。它涵盖了后处理堆栈的创建。

    放牛的星星
  • 万字长文详解如何用Python玩转OpenGL | CSDN 博文精选

    【编者按】OpenGL(开放式图形库),用于渲染 2D、3D 矢量图形的跨语言、跨平台的应用程序编程接口,C、C++、Python、Java等语言都能支持 Op...

    AI科技大本营
  • UE5的Nanite刷屏?Unity破解Nanite几十亿面渲染只需三招

    UE5宣传片发布之后,沸腾的不只是技术行业,很多其他行业的人都表示,朋友圈也都被刷屏,一脸懵逼。Nanite宣称可以渲染160亿的三角面,这些对你们行外人来说当...

    放牛的星星
  • 硬核干货丨游戏大世界的超远视距处理手法,建议收藏!

    | 导语   本文从浮点数精度、实时阴影、合批策略和剔除算法四方面阐述游戏大世界的超远视距处理的常用手法。 ? 当世界足够大的时候,浮点数的精度问题就会呈现...

    腾讯大讲堂
  • OpenGL ES (iOS) 学习笔记 — 基础篇(一)

    最近一直在做视频相关的工作,结合最近很火的AR技术,所以准备好好学习一下3D渲染的相关知识。因为一直在iOS移动端开发,所以学习一下OpenGL ES 技术。 ...

    MelonTeam
  • 基于UE4/Unity绘制地图基础元素-线(下篇)

    上篇中记录了绘制线的基本流程,而下篇主要是对绘制线中遇到的性能和效果问题进行阐述。在绘制完一条线并且希望给其加上描边样式时,会遇到不可避免的闪烁问题。而在绘制大...

    腾讯位置服务

扫码关注云+社区

领取腾讯云代金券