图  一个使用gpu instance绘制4000棵树的场景

在3D渲染中,尤其是现代3D游戏中,我希望能够绘制越来越多的场景物体,这对于设备(尤其是移动端)的性能是个极大的考验,对于新一代的渲染api,都逐渐支持了Gpu Instancing技术,这对于大量相同物体的绘制提供了一个新的方案,在最新的unity5中也提供了对gpu instance 的支持,我尝试在unity5中利用gpu instance 技术来表现大量的植被,并对其性能进行了分析,以探索在3D手游中gpu instance的应用的可行性。

关于Gpu Instance

Gpu Instance是一种用来提高渲染大量物体效率的技术,随着手游游戏品质需求的提升,我需要在场景里绘制越来越多的物体,这里面主要涉及两个方面的性能瓶颈,一是cpu对gpu提交数据的次数(包括设置数据buffer,渲染状态以及调用对渲染原语的绘制即drawcall),二是gpu上的绘制(包括顶点处理和像素绘制),随着场景物体的提升,cpu和gpu的压力都会上升。目前在一些典型的3D游戏的制作中,我们的经验值是全屏不超过10万个顶点和200个draw call左右,不然对中端机器会有一定压力。

为了解决场景绘制效率这个问题,主要有以下几种优化方案:

  • static batching: 即静态合批,静态合批的原理即化整为零,将多个场景物体预先合成一个大的物体进行绘制,unity5的实现就是整合成一个大的vbo,而不整合IBO,一次性提交vbo给gpu,然后并不是把整个vbo都绘制,而是每次需要绘制其中某个某些物体时改变IBO,选择大vbo上的某一段进行绘制。静态合批可以将多个小物体的绘制合并成一个大物体的绘制,减少对渲染状态的改变,它一次并行绘制多个物体,理论上是最快的绘制方法,不过最大的缺点是因为合成新的大vbo需要耗费额外的大量内存,同时不能渲染动态物体,因为合并vbo的时候已经确定顶点数据了,顶点数据不能更改(例如unity5对LOD合批的实现也是讲所有层次的lod都预先合并进去),另外一个vbo的大小是有限制的,如果物体数量过多,也会被拆成多个绘制。
  • dynamic batching:动态合批,可以解决对顶点数据有变化的物体的合批,它动态的合并vbo进行提交,组建vbo的时间有消耗,为了减少这个消耗,unity对动态合批的vbo大小有限制,以致于很小顶点数的物体才有可能被动态合批。
  • vertex constant instancing:Instancing 是不同于batching的另一种方案,它的原理是对于模型一致的物体,只提交原始的模型的vbo给gpu,然后将每个物体不同的属性单独抽出来组成buffer发给gpu,在显卡中根据这一份vbo和每个物体不同的属性来绘制多个物体,即一次提交,在gpu上绘制多个,对于大量同样模型的物体绘制是一个很好的方案。vertex constant的instancing是利用顶点常量属性来存储这些per instance attributes,但是也需要一个大的vbo存储所有未经顶点变换的相同的n个原顶点数据,在shader里面读取不同的vertex constant内容绘制不同的instance
  • gpu instancing:这是最新渲染api提供的一种技术,如果绘制1000个物体,它将一个模型的vbo提交给一次给显卡,至于1000个物体不同的位置,状态,颜色等等将他们整合成一个per instance attributebuffergpu,在显卡上区别绘制,它大大减少提交次数,它在不同平台的实现有差异,例如gles是将per instance attribute也当成一个vbo提交,然后gles3.0支持一种per instance步进读取的vbo特性,来实现不同的instance得到不同的顶点数据,这种技术对于绘制大量的相同模型的物体由于有硬件实现,所以效率最高,最为灵活,避免合批的内存浪费,并且原则上可以做gpu skinning来实现骨骼动画的instancing。

Unity5中实现instance

unity5里面加入了对gpu instance的支持,而我们的一个项目中由于考虑到大量植被的表现,正好可以使用这个技术来提高渲染性能。

unity中提供了两种使用gpu instance的机制,自动和手动:
  对于自动,需要使用unity 标准的standar 或surfaceshader,然后在mat下面的instacne那里打勾,然后unity在条件合适的情况下自动instance,但是注意这种限制非常多,如不能static batch,不能liaghtmap,不能改变mat,不能带动作,不能cull,等等,非常难,详见https://docs.unity3d.com/Manual/GPUInstancing.html
  对于手动,通过使用Graphics.DrawMeshInstanced或者Graphics.DrawMeshInstancedIndirect这些底层api。

由于unity自动的instance不稳定且不能lightmap等等,于是我们的实现方案是自己用底层api去实现instance,并且自己去实现了支持lightmap和culling的instance。

我们实现了一套支持lightmap,culling以及lod的instance渲染方法

性能测试

我们希望通过实际的测试来了解对于大规模植被的渲染instance相对batching能够有多少的性能提升,以及在物体数量变化情况下性能的提升变化,以希望能够得到对instance和batching两种技术运用的一些经验数据。

测试环境:pc平台,场景中放置4种不同的树,每种树放置大量,简单直线光照,standard渲染,为了防止动态合批影响结果,关闭动态合批,为了尽肯能减少对帧率影响,关闭垂直同步,所有的树木有三层lod,其中最底层lod相同。

下面是几组测试结果

1)随着树的数量升高最低fps的变化

2)随着树的数量升高最高fps的变化

3)随着树木数量升高instance相比batching的帧率提高百分比

4)随着树木的数量升高cpurenderthread的所花时间变化

5)随着树木的数量升高drawcall的变化

6)随着随着树木的数量升高setpass的变化

7)随着随着树木的数量升高场景内存的变化

我们的测试结论

1)在大量重复物体的绘制上,在各种指标和树木的数量上,instance都明显优于静态合批

2)从帧率上,我们发现两种方式帧率都会随着渲染物体增加而降低帧率,但是在512棵树之内,instancebatching的帧率提升比随数量而升高,超过512这个比例有下降,可能说明在512树之内,drawcall是比较大的瓶颈,即cpu瓶颈很大,这时instance提升明显,超过一定数量,瓶颈会更多转移到gpu短的像素绘制,cpu有很多时间用来等到gpu,这时候instance的提升作用会降低。

3)渲染时间上看,instance即使1024棵树的时候,也几乎和128棵树相差不多,因为cpu需要做的事情,绘制100棵和1棵差别不大,但是batching会大幅上升

4)两个关键指标draw callsetpass上看,instance几乎不会随着数量增加而增加,而batching虽然也会对vbo合并,但是受制于一个vbo的大小,当物体过多时,合批将会部分失效,被迫拆成几个vbo,这样dc也会上升

5)从内存上看,instance在绘制1001000棵树占用的场景内存是一致的,都大约3M,只是树木的原始mesh,但是batching带来的内存占用是显著的,到1024棵树时已经需要大约500m内存了,这在手机上几乎是不可能的,因为batching采用的策略需要把所有的渲染物体预选合并成一个大vbo来减少drawcall

6)Instancing对超大量物体的容忍度极高,我们可以看到文章开头的图片是我们绘制4000棵数的时候,instancing的帧率仍然可以维持在70-80帧,render time不变,而同样情况下batchingfps只有7帧。

7)综上,我们得出更简单的三条结论:物体数量中等时,instance节省内存的意义更大,数量很多时,提升帧率的意义更大,数量逆天时,instance可以容忍的极限很高,然而无论怎样物体数量越少越好。不过各种技术的选择也都存在一个度,对于那些面数很少或者重复数量不多的静态的东西,就完全没有使用instance的必要了,因为如果能用一个static batching完成的绘制效率反而是更高的。static baching原理上毕竟是一次提交一次绘制,而instancing是一次提交,但是是顺序执行在硬件上的多次绘制。我们认为物体的重复数量必须达到一个static baching(大约6万多个顶点)无法容纳的程度或者基于更加节省内存的考虑才有必要使用gpu instancing。

可以看到,我们通过在unity中基于gpu instance 技术,可以对于植被这种需要大量渲染但是种类有限的对象,可以提高他们的渲染性能,并且当重复的数量达到一定程度时,instance相比batching相比可以节省内存,并且对数量的容忍度非常高,对于性能有限的移动设备是一种很好的选择。

下一步

目前只解决了静态物体的gpu instancing,我也正在尝试在gpu上实现骨骼的skinning来最终实现对带有骨骼动画对象的instancing,来用于大规模角色的绘制。

Unity中基于Gpu Instance进行大量物体渲染的实现与分析(一)相关推荐

  1. 【GDC翻译】“地平线零之曙光”中基于GPU的程序化实时放置系统

    原视频:GDC Vault - GPU-Based Run-Time Procedural Placement in 'Horizon: Zero Dawn' PDF:https://www.gdcv ...

  2. Unity中基于三角剖分 实现三维城市实时构建

    日常项目中我们所能拿到的GIS数据都是shp或者Genjson类型数据文件,之前的处理方式基本都是经过一些ArcGIS及建模软件的处理导出成模型,再放在Unity中产出项目. 后来经过研究,实现了基于 ...

  3. Unity中基于屏幕后处理的彩色与黑白渐变效果

    本文将介绍Unity屏幕后处理的基本方法与游戏角色死亡后的屏幕渐变到黑白效果的实现方法. 屏幕后处理是指整个场景每一帧渲染完毕后,再对得到的屏幕图像进行一系列处理并显示到屏幕上的过程.Unity中我们 ...

  4. 在 Unity 中基于 Oculus DK1 的开发

    开发环境: Windows 10 专业版 64位(GeForce GTX 970M,驱动版本:378.72) 大朋助手 1.3.2.10,大朋E2(http://www.deepoon.com/dap ...

  5. Unity中,实现鼠标点击物体,触发事件

    对于UI,很容易能够实现鼠标点击,从而触发事件,但是对于游戏中的物体,则需要多进行一些操作. 原理很简单,就是由鼠标点击处发射线,与游戏物体发生碰撞,碰撞到的物体,就是你点击到的物体.具体操作如下: ...

  6. Unity中添加组件/启用禁用游戏物体

    常用变量 transform组件 场景当中的任何游戏物体,必须有一个transform组件.用来定位位置,游戏物体存在场景当中不可能没有位置,所以每个游戏物体有且只有一个transform组件,且不能 ...

  7. Unity中GPUInstance详解

    为什么要用GPUInstance        在没有GPUInstance此技术之前,对于像草地.树木,割草游戏,它们往往是数据量很大,但同时又只存在微小的差别如位置.旋转.颜色等.如果像常规物体那 ...

  8. 基于GPU预计算的大气散射

    本文转载自:https://blog.csdn.net/qq_31615919/article/details/85938076 基于GPU预计算的大气层光效渲染 前言 大气物理模型 渲染方程及其实现 ...

  9. 在Unity中实现体素化

    在Unity中实现体素化 博客链接:在Unity中实现体素化 体素化 类似与用网格存储二维平面,将三维空间划分成大量尺寸相同的小方块的过程就称之为体素化. 为什么要体素化 以下是个人理解 当场景中多边 ...

最新文章

  1. angularjs 滑块验证码 移动端_SliderCaptcha
  2. 《统一沟通-微软-实战》-7-配置-3-响应组
  3. Swift之从SIL深入分析函数的派发机制
  4. Servlet到底是个什么东西???【【博采众长】】
  5. 样式和主题的区别(Styles and Themes)
  6. jQuery选择器的演示
  7. keeplive linux平台下,Linux下搭建keepalive+nginx
  8. html5元件的作用,寄存器的作用是什么
  9. 教你chrome浏览器断点调试理解闭包
  10. Butterfly-蝴蝶-主题优化、美化-Lete乐特
  11. 2021年N1叉车司机最新解析及N1叉车司机模拟考试
  12. [YYOJ]LZY喜欢的数字
  13. Linux重置root 密码
  14. python编程师app_python大师编程课app
  15. 宇宙飞机(space plane)
  16. 微服务构建思路与方法论
  17. 这是一则招聘贴——招聘区块链系统开发实习生
  18. Android 去掉锁屏
  19. Excel中VBA编程学习笔记(一)
  20. Ubuntu16.04中Docker的卸载和安装

热门文章

  1. C语言学习笔记---fseek()函数和ftell()函数
  2. Qt QLineEdit 输入框
  3. 《计算机网络》中英文对照
  4. 无线环境下NAS传输速度过慢
  5. 概率密度变换公式 雅可比矩阵_机器人雅可比矩阵的理解和常用公式
  6. 数据库 - 设计:逻辑设计
  7. C++中不允许定义引用数组
  8. 根据字幕/台词搜索影视剧的网站
  9. TCP和UDP是什么?区别是什么?
  10. 解决金山词霸2005序列号失效