热门标签 | 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



推荐阅读
  • 本文将深入探讨 Unreal Engine 4 (UE4) 中的距离场技术,包括其原理、实现细节以及在渲染中的应用。距离场技术在现代游戏引擎中用于提高光照和阴影的效果,尤其是在处理复杂几何形状时。文章将结合具体代码示例,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文探讨了如何在PHP与MySQL环境中实现高效的分页查询,包括基本的分页实现、性能优化技巧以及高级的分页策略。 ... [详细]
  • oracle 对硬件环境要求,Oracle 10G数据库软硬件环境的要求 ... [详细]
  • ArcBlock 发布 ABT 节点 1.0.31 版本更新
    2020年11月9日,ArcBlock 区块链基础平台发布了 ABT 节点开发平台的1.0.31版本更新,此次更新带来了多项功能增强与性能优化。 ... [详细]
  • 视觉Transformer综述
    本文综述了视觉Transformer在计算机视觉领域的应用,从原始Transformer出发,详细介绍了其在图像分类、目标检测和图像分割等任务中的最新进展。文章不仅涵盖了基础的Transformer架构,还深入探讨了各类增强版Transformer模型的设计思路和技术细节。 ... [详细]
  • 服务器虚拟化存储设计,完美规划储存与资源,部署高性能虚拟化桌面
    规划部署虚拟桌面环境前,必须先估算目前所使用实体桌面环境的工作负载与IOPS性能,并慎选储存设备。唯有谨慎估算贴近实际的IOPS性能,才能 ... [详细]
  • CentOS下ProFTPD的安装与配置指南
    本文详细介绍在CentOS操作系统上安装和配置ProFTPD服务的方法,包括基本配置、安全设置及高级功能的启用。 ... [详细]
  • JUC并发编程——线程的基本方法使用
    目录一、线程名称设置和获取二、线程的sleep()三、线程的interrupt四、join()五、yield()六、wait(),notify(),notifyAll( ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • Beetl是一款先进的Java模板引擎,以其丰富的功能、直观的语法、卓越的性能和易于维护的特点著称。它不仅适用于高响应需求的大型网站,也适合功能复杂的CMS管理系统,提供了一种全新的模板开发体验。 ... [详细]
  • MySQL InnoDB 存储引擎索引机制详解
    本文深入探讨了MySQL InnoDB存储引擎中的索引技术,包括索引的基本概念、数据结构与算法、B+树的特性及其在数据库中的应用,以及索引优化策略。 ... [详细]
  • 流处理中的计数挑战与解决方案
    本文探讨了在流处理中进行计数的各种技术和挑战,并基于作者在2016年圣何塞举行的Hadoop World大会上的演讲进行了深入分析。文章不仅介绍了传统批处理和Lambda架构的局限性,还详细探讨了流处理架构的优势及其在现代大数据应用中的重要作用。 ... [详细]
  • 如何高效解决Android应用ANR问题?
    本文介绍了ANR(应用程序无响应)的基本概念、常见原因及其解决方案,并提供了实用的工具和技巧帮助开发者快速定位和解决ANR问题,提高应用的用户体验。 ... [详细]
  • 在现代Web开发中,HTML5 Canvas常用于图像处理和绘图任务。本文将详细介绍如何将Canvas中的图像导出并上传至服务器,适用于拼图、图片编辑等场景。 ... [详细]
  • IO流——字符流 BufferedReader / BufferedWriter 进行文件读写
    目录节点流、处理流读文件:BufferedReader的使用写文件:BufferedWriter的使用节点流处理流节点流和处理流的区别和联系字符流Buf ... [详细]
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社区 版权所有