热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

用c语言实现线画、填充图元生成算法多边形_【游戏场景剔除】剔除算法综述...

之前在做场景优化的过程中,看了不少论文和博客阐述不同剔除算法的原理和过程,自己参照着算法去实现了Hiz和软件剔除。一直想写一篇关于剔除算法的综述

之前在做场景优化的过程中,看了不少论文和博客阐述不同剔除算法的原理和过程,自己参照着算法去实现了Hiz和软件剔除。一直想写一篇关于剔除算法的综述,来总结常用剔除算法的实现原理和过程。

在游戏运行中,引擎渲染出数以万计的物体,场景复杂度往往是数千万面的级别,同时还需要处理千计盏灯光和数百种材质。因此,如何有效地减少不必要的绘制就显得格外重要。本文将就游戏引擎中用到的各种剔除技术进行概述,会较少涉及细节,感兴趣的同学可以去看文末的参考文献,文献中会有相关剔除算法的原理和具体实现。

我们将分为以下四个方面来介绍:

1.场景剔除工作原理

2.常用剔除算法

3.总结

4.参考文献

一、场景剔除工作原理

对于场景物体的剔除一般分为可见性剔除和遮挡剔除:

1.可见性剔除

可见性剔除通过判断物体与相机的距离(距离剔除)或者是否在相机的视锥体内(视锥体剔除)来对物体进行剔除。

ac55111da8d35f92ec10684ac4735f18.png

如图所示,不在相机视锥体内部的物体将被剔除不进行渲染。

2.遮挡剔除

遮挡剔除则是在相机可见范围内通过判断物体是否被其他物体遮挡来对物体进行剔除。遮挡剔除有基于整个物体是否被遮挡的剔除(Hiz、硬件遮挡查询等),也有基于像素级别的遮挡查询(Early Z)。

c8d9c411c77bbe38b592724e2d805e84.png

图中蓝色虚线的物体被相机前方的物体遮挡,并将剔除不进行渲染。

二、常用剔除算法

本文将大致介绍以下剔除算法的原理和实现过程:

(1).距离剔除

(2).视锥体剔除

(3).Occluder剔除(软件剔除)

(4).视口剔除

(5).背面剔除

(6).遮挡查询(Occlusion Query)

(7).Early Z Culling

(8).Hiz Culling

(9).PVS

1.距离剔除

剔除阶段:应用程序阶段。

通过物体和相机的距离进行判断物体是否被剔除,原理比较简单,剔除效率也相对较高。在UE4中可以通过物体属性设置剔除的最大距离和最小距离(如下图):

7e79007106f91471b569223e6473ffda.png

2.视锥体剔除

剔除阶段:应用程序阶段。

即简单的判断一个物体是否位于视锥棱台内。裁剪的依据主要是根据摄像机的视野(field of view)以及近裁减面和远裁剪面的距离,将可视范围外的物体排除出渲染。

fb0655f9734b6917e998152eed5bc131.png

上图中1为近裁剪屏幕,2为裁剪截面体,3为远裁剪平面

在实践中,由于模型往往是比较复杂的,很难精确计算它和视锥体的交集,因此一般是用轴对齐包围盒(AABB),有向包围盒(OBB)或者包围球(BSphere)代替模型本身进行相交计算。

66ef9723b5f889c2650b7e183bb1a24e.png

视椎体剔除是减少渲染消耗的最有效手段之一,可以在不影响渲染效果的情况下大幅减少渲染涉及到的顶点数和面数。

3.occluder剔除(软件剔除)

剔除阶段:应用程序阶段。

这个方案的思路是,首先利用CPU构造一个低分辨率的Z-Buffer,在Z-Buffer上绘制一些场景中较大的遮挡体:

c0339ef559dc61743d96b4b0e02279c4.png

在构造好的Z-Buffer上,绘制小物体的包围盒,然后执行类似于occlusion query的操作,查询当前物体是否被遮挡:

d07222d1b24e9d6dcadca07969edc45c.png

由于是纯CPU的,集成起来也比较简单,同时不会有GPU stall的问题。缺点是需要美术指定一些大的遮挡体,对CPU性能有一定的消耗。在UE4中通过物体actor的LOD For Occluder设置遮挡体。

71312ecdaef3e2e4b7bc76e3eec61016.png

4.视口剔除

剔除阶段:投影变换之后屏幕映射之前。

发生在几何阶段(Geometry Stage)后期,投影变换之后屏幕映射之前,是渲染管线的必要一环。只有当图元完全或部分存在于规范立方体内部的时候,才将其返送到光栅化阶段。其中,对于完全位于规范立方体内部的图元,则直接进行下一阶段;完全处于规范立方外部的图元则完全被舍弃;部分处于规范立方体内部图元,则会根据视口进行对应的裁剪,在这一过程中可能会产生新的顶点。通过视口剔除可以将视口外的图元舍弃掉,减小光栅化阶段的消耗。

5.背面剔除

剔除阶段:在光栅化阶段进行。

当我们观察场景中对象时,一般只能以一定角度来观察,那么对象的某些面我们是看不到的,例如你观察一个立方体,最多只能同时看到3个面,有时只能看到1个面,而我们绘制时如果不采取剔除背面的措施,则要绘制6个面,其中包括一些我们根本看不到的面。对于立方体这个面较少的几何对象,性能开销不明显,但是对于复杂的模型,开启背面剔除则能明显改善渲染性能。 背面剔除,就是早点丢弃对观察者来说是背面的片元的一种方法。

89f149441628ba3585c912b9a6fb31c6.png

剔除的基本原理是先判定多边形的朝向,并和当前的观察方向进行比较。opengl中设置背面剔除相关函数:

glFrontFace(GL_CW); 设置顺时针或者逆时针为正面

glCullFace(GL_BACK); 设置剔除正面或者背面

背面剔除在光栅化阶段进行,执行在Vertex Shader 之后,在Fragment Shader片元着色器之前。

6..遮挡查询(Occlusion Query)

剔除阶段:在深度测试时得到待剔除物体,在应用程序阶段执行。

参考步骤和代码:

https://developer.download.nvidia.cn/books/HTML/gpugems/gpugems_ch29.html

https://www.cnblogs.com/mazhenyu/p/5083026.html

简单来说,occlusion query允许你在绘制命令执行之前,向GPU插入一条查询,并且在绘制结束之后的某个时刻,从GPU将查询结果回读到系统内存里。这条查询命令得到的是某次DrawCall中通过Depth Test的Sample数量,当这个Sample的数量大于0时,就表示当前模型是部分可见的,否则当前模型完全被遮挡。

opengl中实现API接口:

//生成查询物体ID

glGenQueries(GLsizei n, GLuint *ids);

//开始遮挡查询

glBeginQuery(GL_SAMPLES_PASSED, 1);

//结束遮挡查询

glEndQuery(GL_SAMPLES_PASSED);

//根据Sample值param是否大于0判断查询号为id的物体是否被遮挡

glGetQueryObjectiv(GLenum id, GLenum pname, GLint *param);

对于复杂的场景,一个显而易见的优化策略就是用包围盒代替模型本身去做渲染,为了更加精确,我们也可以用多个紧贴的包围盒或者相对原模型更简单的Proxy Mesh去做occlusion query。基于这些API,我们就可以得到一个比较简单的遮挡剔除策略:

  1. 首先为这些物体生成查询对象ID 调用glGenQueries
  2. 调用glBeginQuery开始遮挡查询
  3. 渲染包围体
  4. 调用glEndQuery 结束遮挡查询
  5. 调用glGetQueryObjectiv,根据ID提取遮挡查询的结果,并根据结果绘制相应的物体。
  6. glDeleteQueries 删除ID,回收资源。

Occlusion query的另一个缺点(也是最致命的缺点)是,它需要将查询结果回读到系统内存里,这就意味着VRAM->System RAM的操作,走的是比较慢的PCI-E。

为了解决这个问题,比较常用的的方法是让CPU回读前一帧的occlusion query的结果,用来决定当前帧某个物体是否visible,对于相机运动较快的场景,用上一帧的结果可能会导致出错,但由于一般是用包围盒,本身就是保守的剔除,所以总体来说影响不明显,UE4默认使用的就是这样的遮挡剔除方案。

7.Early Z Culling

剔除阶段:在光栅化阶段后,片元shader执行前。

我们知道传统的渲染管线中,深度测试是发生在Pixel/Fragment Shader之后的但是,如果我们仔细想下,在光栅化的时候我们已经知道了每个片断(fragment)的深度,如果这个时候我们可以提前做测试就可以避免后面复杂的Pixel/Fragment Shader计算过程。

提到Early-Z就必须提对应的Late-Z:在图形管线中,逻辑上Depth Test和Stencil Test是发生在Pixel Shader的执行之后的,因为Pixel Depth在Pixel Shader阶段还有可能被修改,所以Pixel Shader->Depth Test的流程顺序就是Late-Z。但由于Pixel Depth修改的需求非常少(基于深度混合的Impostor和某些粒子效果),所以绝大部分情况下,Pixel Depth在Rasterization之后、Pixel Shader执行之前就可以被确定下来,如果我们能够把Depth Test放在Pixel Shader之前,对那些没通过Depth Test的像素不执行Pixel Shader,就能够一定程度上减少SM的压力,这就是Early-Z这个优化策略的初衷,现在已经是GPU的标配了。默认在Pixel Shader里没有修改Depth的操作时,这个优化就会开启。

UE4在Prepass中生成earlyZ Depth,然后在光栅化后执行EarlyZ Culling

b5b60d3ca7753c532dcdd81c7620f0dd.png

8.Hiz Culling

剔除阶段:在几何shader得到待剔除物体,在顶点shader执行。

参考步骤和代码:

https://github.com/nvpro-samples/gl_occlusion_culling

Hiz Culling同样是基于GPU但不同于EarlyZ Culling的剔除算法,Hiz Culling使用几何着色器先生成对应物体的包围盒,然后根据物体的包围盒选择对应层级的depth map。利用depth map 对应像素值对包围盒进行剔除,得到物体可见性并作标记。为了避免GPU返回标记到内存而造成时间消耗,通常使用Transform feedback将此数据流式传回到顶点shader中,也就是常使用的2-pass。

具体算法过程如下:

(1)拿到上一帧场景深度buffer,利用深度buffer构造分层深度图像,我们将其称为Hi-Z map。这些分层的深度图是对深度缓冲区进行mip-map得到,其中mip级别i中的每个像素包含mip级别i-1中的对应像素块的最大深度。

a0f0c7b4e1448155d256f3d75c026e32.png

(2)将当前待绘制的场景物体分为两个集合:集合1.上一帧已有的物体集合(这里不一定和上一帧已有物体数量相同,有可能上一帧在相机可视范围而当前帧不在等情况)。集合2.当前帧新增的待渲染物体

(3)处理集合1:在构建Hi-Z map后,根据集合1物体的包围盒大小取对应级别的Hi-Z map深度图,并通过比较物体的包围盒深度值和存储在对应深度图深度信息来执行遮挡剔除,通常我们比较包围盒六个顶点深度值与对应位置周围的四个像素的深度值判断物体是否被遮挡。

60dcaf7a3015b0304c1c102b5ed538b7.png

(4)根据(3)剔除的结果绘制集合1,更新深度buffer

(5)处理集合2:利用新的深度buffer建立mipmap深度图,对集合2进行剔除。

(6)绘制集合2中物体,更新深度buffer。

值得注意的是:我们对剔除的判断是在几何shader中进行,完成物体可见性判断后,利用transform feedback 将可见性数据流传回到顶点shader中,这样可以避免数据从GPU写回到内存。

9.PVS

剔除阶段:应用程序阶段。

像其他剔除方法一样,预计算可视性体积用于实现中小型场景的性能优化,通常用于因为硬件问题而使动态遮挡剔除受到限制的移动平台。预计算可视性体积根据玩家或摄像机的位置,将Actor位置的可视性状态存储在场景中。

由于预计算可视性是在线下生成的,因此可以省去用于硬件遮挡查询的渲染线程时间,但代价是会增加运行时内存和照明构建时间。基于这一点,建议仅在玩家或摄像机可访问区域放置体积来保持可视性剔除。

b98bb58322dbd648f3da4b6777cb517a.png

  标准 PVS分为两步:

  1. 先求解简易模型:减面,枚举模型上每个顶点,找到一个点使得删除该顶点,模型变形最小,不停的寻找并删除影响最小的点直到模型变形超过一定阀值。最终求解出简易场景模型,为第二步计算做准备。

  2. 划分成小的三维格子,在格子里面均匀或随机选取 N个采样点做为摄像机位置,每个采样点 360度全方向做一定数量的射线出去,和场景中的模型判断交点,求解出该采样点的PVS,然后合并格子里N个采样点的结果为该格子的PVS。有离线计算好的,也有实时计算摄像机周围空间未计算格子的,等摄像机移动到那里时已经计算好了,无外乎精度不同。实际绘制时将所在格子的PVS提取出来再做一次视锥剔除就行。

三、总结

本文主要对当前引擎常用的一些剔除算法做了综述。剔除的本质是消耗少量的计算剔除尽可能多的物体,如果场景物体不复杂或者说互相遮挡不多,此时用一些计算复杂的剔除算法反而可能使帧率降低。因此,需要根据不同的情况选择合适的剔除方法,例如对于有大量植被实例场景可以考虑设置距离剔除,场景中有比较大的遮挡物则可以考虑occluder剔除,在手机平台我们可以考虑基于预计算剔除PVS等,通过这些剔除算法来提升游戏场景帧率。

四、参考文献

1.https://docs.unrealengine.com/en-US/Engine/Rendering/VisibilityCulling/CullDistanceVolume/index.html

2.https://blog.csdn.net/game_fengxiaorui/article/details/79958722

3.https://zhuanlan.zhihu.com/p/48163037

4.https://software.intel.com/en-us/articles/software-occlusion-culling

5.https://bazhenovc.github.io/blog/post/gpu-driven-occlusion-culling-slides-lif/

6.https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter06.html

7.https://www.gamedev.net/articles/programming/graphics/coverage-buffer-as-main-occlusion-culling-technique-r4103/

8.https://gameinstitute.qq.com/community/detail/119431

9.https://www.khronos.org/opengl/wiki/Early_Fragment_Test

10.http://rastergrid.com/blog/2010/10/hierarchical-z-map-based-occlusion-culling/

11.https://zhuanlan.zhihu.com/p/47615677

12.https://www.zhihu.com/question/38060533



推荐阅读
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
author-avatar
手机用户2502917905
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有