基于 Dots + GPU Instance 的大规模物体渲染

news2024/11/26 10:29:53

        之前写的两篇开放世界技术栈都是公司其他同事做的,所以很多细节了解不详细。但这次是全程我自己搭建的轮子,可以讲得稍微详细些。

        之前写的大规模物件渲染的 GPU 版本,虽然渲染量大效率高,但是有个很致命的缺陷:无法与游戏逻辑进行交互。因为主要渲染数据都是放在 GPU 中,为了效率要尽可能减少异步回读,也要尽量减少同步数据量,所以要物体与逻辑交互就基本不可能了。但是使用 Unity 的 Dots 系统再加上 GPU Instance 技术,就可以很好地解决这个问题。
 

0、观前提醒

        这篇文章不是写给初学者的,甚至对于有经验的程序员难度也不小。阅读前确保你已经对 Unity 的 Dots 系统非常熟悉,且对渲染管线有些许了解,更重要的是,要有优秀的抽象能力。

        后续我会把这套系统做成插件包放出来在下面的库中(现在还不是很完善),等我这段时间工作忙完了再来整理这个库。

魔术师Dix / Unity 通用库:紫苑 · GitCodeUnity 的各种通用库 : 紫苑(Aster),基于 ECS 1.0+ 2023.2.5+ 包含方便调用的简化API、数据格式。 会包含我所需要的通用工具、数学计算、编辑器方法、多线程辅助、ECS等; 未来会增加ECS渲染、寻路、BVH、OBB等功能;icon-default.png?t=N7T8https://gitcode.net/cyf649669121/Aster     等我把这个库整理完善之后,再开发一些工具、调试器等供大家使用。我最终的目的,还是希望这一套系统能达到傻瓜也能用的地步。

1、原理解释

        一般来讲,渲染一个物体,需要知道其网格、材质球、位置、旋转、缩放、材质球属性即可。而对于同一类物体(材质球与网格均相同),不同实体的区别也就是位置、旋转、缩放;也即 LTW: Local To World。

        Unity 的 GPU Instance接口: Graphics.DrawMeshInstanced ,其中需要动态改变的参数,大部分情况下只有 Matrix4x4[] ,可能还会有 MaterialPropertyBlock 的修改。


        Unity 的 GPU Instance接口有多个,这里只用 DrawMeshInstanced 举例。使用其他的方法也是可以的,但是使用条件会有所不同(比如需要Shader支持)。

        这里只用 Graphics.DrawMeshInstanced 进行设计,因其适配性最好:只需要材质球能勾上 Enable GPU Instancing 即可。当然,如果条件允许,使用 Graphics.DrawMeshInstancedIndirect 是性能最好的方案。


        因此,在ECS中,将所有待渲染部件的 LTW 记录下来,并计数,然后将数据传给主线程,调用GPU Instance 的API即可完成渲染。

2、部件与渲染数据

        在介绍业务流程之前,需要先了解一些概念。

        对于所有的预制体(Prefab),我按照单个 MeshRender 将其拆分成单个部件,以下图的一个农场模型作为示例:

        这个 Prefab 一共由3个部分组成:地板、风车、房子,也就是图中的3个 MeshRender。我将每一个独立的 MeshRender 的数据收集起来视为一个独立的渲染数据(下文中的RenderData):包含网格、材质球、阴影等配置,放在主线程以备上屏时调用。

        对于这个预制体,其父节点会生成一个空 Entity(Dots中的Entity),每一个子节点生成一个 Entity,然后与父节点关联。这里每一个子节点生成 Entity,就是渲染部件(RenderChild),是渲染的最小单位。

3、渲染流程

        这里要注意一点,在离线时,我会预先将所有的预制烘培成适合 ECS 的数据结构,所有预制都转换成只有一个父节点的层级关系(所有带MeshRender组件的父节点都是预制的根节点),这样就可以不用考虑父子节点的旋转问题了。

        之后根据离线数据和游戏逻辑(例如服务器下发单位),创建与 单位Entity(Rendre Parent Entity,游戏逻辑的最小单位)。之后根据离线烘培的数据,给单位Entity挂载渲染部件。之后经过 System 的逻辑处理,统计处需要上屏的单位,将其部件数据收集在各个渲染数据Entity(与 RenderData 对应)中。

        最终上屏时,按照 Unity 的接口提供对应数据,以 DrawMeshInstanced 为例,我这里直接将 ECS 里的数据拷贝出来了:

//内存拷贝
private static unsafe void CopyTo(NativeArray<float4x4> srcVectors, Matrix4x4[] outVectors)
{
    fixed (Matrix4x4* dest = outVectors)
    {
        void* sourceData = srcVectors.GetUnsafeReadOnlyPtr();
        UnsafeUtility.MemCpy(dest, sourceData, UnsafeUtility.SizeOf<float4x4>() * srcVectors.Length);
    }
}

        我这么写了还是很简略,毕竟这个不是手把手教程,而且毕竟我有计划写开放库放出源码,所以解释就较为简单。

4、一些细节问题

        最开始这一套是从 SLG 游戏做出来的,可以支持大量的单位渲染(包括下图中的树木、建筑、行军、地面装饰物等)。

  • 如何处理LOD?

        在每一个部件里都存储有 LOD 信息,LOD分级、以及各个LOD对应的部件ID(提前预烘焙好)。对于SLG游戏,一般是固定俯视角,会使用全局LOD,这种也是支持的。
       在 ECS 根据相机距离计算出当前的 LodID ,然后在收集数据的时候收集当前的 LOD 对应的 MeshType 即可。这种做法在 GPU 里也是通用的。LOD 的计算参考:

【Unity】LODGroup 计算公式_unity lodgroup-CSDN博客文章浏览阅读834次。Unity 在配置 LodGroup 时,其分级切换的计算方法是按照物体在相机视野中占据的比例计算的。在运行时,如果相机视野范围(Field of View)没有改变,那么这个值可以直接换算成物体距离相机的距离。这里就讨论下如何计算得到这个距离。_unity lodgrouphttps://blog.csdn.net/cyf649669121/article/details/133308591         也有一种实现方式,是将对应的部件新增一个,当做一个新的 Entity,并在运行时判定是否显示对应的部件。如下图所示,每一个矩形代表一个 Entity,在不同 Lod 的就显示不同的 Entity,其他组件则会隐藏。

        两种方式更建议第一种,但第二种实现难度小。       

  • 如何处理动画?

        如果是 SkinMeshRender ,也就是骨骼动画,可以使用 GpuSkin,网上有很多方案这里不细讲。使用 GpuSkin,因为其本身也是并行的,和 ECS 可以很好地结合起来。但缺点就是动画状态机的控制,一般是很简单的控制,否则在 ECS 里实现很困难。同样的,动画数量也不建议太多,否则需要烘培的动画贴图也会占用很大资源。

        如果是传统 Animation,这种就只有程序写动画了。所以复杂、特殊效果的动画,也不适合用这套系统。

  • 如何设置Shader参数?

        这里以面片树作为例子,所有的树都是用的同一个 Material 和 Mesh,只有贴图的UV不同从而实现不同树木的表现:

        这里我们需要将每一个渲染部件(也就是一个面片树)的Offset、Tiling离线收集起来,然后在收集最终上屏数据的时候,给渲染实体(RenderDataEntity)挂一个额外的 IBufferElementData,之后上屏之前读取出来,通过 MaterialPropertyBlock 进行赋值即可。这样处理仍然可以合批。

  • 如何定制单位渲染流程?

        参考上一个面片树的例子,还有一个问题就是需要对面片树增加一个特殊的 DrawCall 来执行 PreZ,否则面片树在矩阵变换后Z轴重叠导致闪烁。

        因为每一个部件都是单独的一套配置,我在这套配置里增加了一项,即可按照我配置的类型进行特殊预处理,增加一次 PreZ。

  • 单位剔除问题

        单位剔除没有按照部件,而是按照单位进行剔除的。一个单位(例如上面的一个农场)就按照其包围盒进行剔除,且使用 ECS 多线程并行。

【Unity】相机视锥体剔除算法_unity视椎体剔除-CSDN博客文章浏览阅读3.7k次,点赞2次,收藏10次。视锥体剔除是Unity常用的剔除方法,其原理就是通过判定目标包围盒与组成相机视锥体的6个平面进行同侧判定,只要在6个平面之间的包围盒即为可见。本文根据其原理,给出一个视锥体裁剪的剔除算法的实现,并兼容ECS。_unity视椎体剔除https://blog.csdn.net/cyf649669121/article/details/125779899        对于 SLG 游戏这种固定俯视角的,还可以使用更简单的剔除方式:

【Unity】俯视角相机地面视野范围的计算_unity相机视野范围-CSDN博客文章浏览阅读3.9k次,点赞4次,收藏16次。在SLG等游戏中,相机总是固定为俯视角(上帝视角)。为了更好地管理游戏数据,需要对地图进行分块,只处理视野内的部分。判定某个单位是否在视野内有很多方法了,但是要么不够精确,要么性能不够,要么无法与AOI配合。 一个可行的方案就是将相机在地面上的视野计算出一个AABB 2D 包围盒,然后基于此包围盒来计算 AOI、显隐等。这个方案效率够高,而且对俯视角适配较好。_unity相机视野范围https://blog.csdn.net/cyf649669121/article/details/127529668

  • 如果渲染单位超过1024个了怎么办?

        这里的处理方案就是在 RenderDataEntity 上挂载一个 ComponentData ,标记另一个同样类型的 RenderDataEntity ,形成一个类似链表的数据结构。如果自身需要渲染的部件数量超过设定值(例如1024),就切换到下一个渲染实体。

        在实际开发过程中,超过 1024 的情况还是很少的,更多的根据项目实测,为了节约内存会限定单个 RenderDataEntity 的最大渲染数量(例如128)。毕竟,记录各个部件的 Transform 信息的 Buffer 需要一开始就初始化好,自然能省一点是一点。

  • 如何将预制体转换成所需要的数据?

        我自己是写了一个工具进行转换(具体参考工程里的代码),在离线情况下收集所有需要渲染的预制体,然后烘培成纯数据。虽然理论上可以在线试试转换,但我不建议这样做。

        实际上转换后,原有的预制体就不需要了。所以后面我的做法,都是在专门的美术工程里进行数据整理、烘焙,正式工程里只有纯数据。

5、优势与限制

优势:

  1. 完美的合批!(除了 DrawMeshInstanced 有 1024 个的限制外,能完美合批);
  2. 高性能,支持大批量物件渲染;
  3. 能与主线程进行逻辑交互;
  4. 可以在 ECS 系统里可定制化一些特殊功能;

劣势:

  1. 技术水平要求较高,需要熟练掌握 Dots ECS 体系才能良好维护。
  2. 无法使用动画(只能程序实现一些简单效果)
  3. 不支持多材质球、多Mesh的模型(需要拆分)

除此之外,还有以下情况不建议使用这套系统:

  1. 独特物体:一般来讲,单个物体会有独特的模型、效果和控制方式,而在ECS控制起来较为困难,且没有什么效率提升。
  2. 静态物体:静态海量物体(不会移动、形变等)建议使用下面的方案使用GPU来处理,效率更高。GPU驱动的大规模静态物件渲染-CSDN博客文章浏览阅读745次,点赞9次,收藏18次。GPU Driven 的静态物件渲染,听起来很高级,其实具体操作很简单,基础就是直接调用 Graphics.DrawMeshInstancedIndirect 这个 Unity 内置接口就可以了。但我们项目对这个流程做了一些优化,使得支持的实体数量有大幅提升。这套系统主要也是公司的 TA 实现的,这里我也只简明扼要地介绍一下原理。https://blog.csdn.net/cyf649669121/article/details/141222437

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2063234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Azure OpenAI citations with message correlation

题意&#xff1a;“Azure OpenAI 引用与消息关联” 问题背景&#xff1a; I am trying out Azure OpenAI with my own data. The data is uploaded to Azure Blob Storage and indexed for use with Azure AI search “我正在尝试使用自己的数据进行 Azure OpenAI。数据已上传…

行业应用 | 桥田MMC磁力换模系统-冲压场景案例分享

随着市场竞争的日益加剧&#xff0c;制造形态从单品种大批量转变为多品种小批量&#xff0c;品种的多样化对模具的多样化产生了需求&#xff0c;在更换产品品类时&#xff0c;首先需要更换加工模具。冲压是金属加工中的典型场景&#xff0c;如何缩短冲压模具的更换时间&#xf…

【Hot100】LeetCode—23. 合并 K 个升序链表

目录 1- 思路优先队列 2- 实现⭐23. 合并 K 个升序链表——题解思路 3- ACM 实现 原题连接&#xff1a;23. 合并 K 个升序链表 1- 思路 优先队列 1- 提供的数据结构&#xff1a;ListNode[] lists2- 由于提供的数据结构已经是有序的&#xff0c;不能通过指针实现是因为不知道一…

基于I2S和esp-now协议制作esp32对讲机

基于I2S和esp-now协议制作esp32对讲机 1.概述 这篇文章介绍基于I2S和esp-now协议制作对讲机&#xff0c;他的实现需要在ESP32开发环境的2.0.10版本号下才能成功&#xff0c;高版本号的源码有改动会导致编译失败。 安装ESP32 2.0.10版本&#xff1a; https://brucelong.blog.c…

2 nestjs 设计模式

回顾 MVC MVC&#xff08;Model-View-Controller&#xff09;设计模式是一种常用于软件开发的架构模式&#xff0c;旨在分离应用程序的不同部分&#xff0c;使得它们可以独立地开发、测试和维护。它将应用程序分为三个主要组件&#xff1a; Model&#xff08;模型&#xff09;…

什么牌子的开放式耳机性价比高?五款高口碑精品推荐!

由于传统入耳式耳机可能对耳道健康造成长期影响&#xff0c;许多人开始偏好选择开放式耳机的非侵入式设计。这种耳机有助于减少耳内湿润、细菌增长&#xff0c;以及耳道闷热的不适感。为了帮助大家在众多产品中挑选合适的开放式耳机&#xff0c;我将列举一些市场反馈良好的款式…

C#中的WebClient与XPath:实现精准高效的Screen Scraping

在现代互联网中&#xff0c;Screen Scraping&#xff08;屏幕抓取&#xff09;已成为从网页中提取信息的重要技术。对于C#开发者来说&#xff0c;WebClient和XPath是实现高效抓取的重要工具。本文将概述如何使用C#中的WebClient类结合XPath技术&#xff0c;实现精准高效的Scree…

探索科技潮流新领地厦门凯酷全科技有限公司抖音小店

在这个日新月异的数字时代&#xff0c;科技不仅深刻改变了我们的生活方式&#xff0c;更成为连接消费者与未来生活的桥梁。今天&#xff0c;就让我们一起走进厦门凯酷全科技有限公司的抖音小店&#xff0c;感受一场科技与时尚的完美碰撞&#xff0c;体验未来生活触手可及的魅力…

Python 和 PyCharm 安装(傻瓜式)

为什么要安装Python&#xff1f; 当我们写 python 代码的时候&#xff0c;需要有环境的支持&#xff0c;才可以运行代码。而 python 的安装支持了两个主要部分&#xff0c;分别是解释器和标准库。当我们安装完成&#xff0c;就可以使用python里面的标准库来写代码&#xff0c;而…

Linux 内核源码分析---netfilter 框架

iptables是用户用来管理和配置防火墙规则的一种策略&#xff0c;但是实际解析规则并按照规则实施产生作用的是Netfilter。 iptables 与协议栈内有包过滤功能的 hook 交互来完成工作&#xff0c;这些内核 hook 构成了 netfilter 框架。每个进入网络系统的包&#xff08;接收和发…

Java Web —— 第七天(Mybatis案例 员工管理2)

新增员工 EmpController类 PostMappingpublic Result save(RequestBody Emp emp){log.info("新增员工操作,emp:{}",emp);empService.save(emp);return Result.success();} EmpServiceImpl实现类 //新增员工Overridepublic void save(Emp emp) {//补充基础属性 创…

jmeter中添加ip欺骗

1、首先在本机电脑中通过配置文件创建添加ip的配置文件&#xff0c;先创建一个txt格式的&#xff0c;直接修改文件名以及后缀为ips.bat 2、编辑该ips.bat文件&#xff0c;在文件中输入如下内容&#xff0c;用于快速给本机添加ip地址&#xff0c;&#xff08;2&#xff0c;1&…

今年奥运会的场馆设计,竟然藏着这样的黑科技!

随着奥运会的盛大开幕&#xff0c;全球观众不仅关注运动员的精彩表现&#xff0c;也被奥运场馆的壮丽设计所吸引。这些场馆不仅外观炫酷&#xff0c;功能齐全&#xff0c;更重要的是背后应用了一系列先进的技术和材料&#xff0c;其中最为亮眼的就是巴黎奥运会永久性体育场馆之…

“救命快刊”:升到2区,IF3.1,水平低也无俱,专家意见中肯不难为人

1、期刊简介&#xff1a; International Journal of Green Energy • 出版社&#xff1a;Taylor and Francis • 影响因子&#xff1a;3.1 • 期刊分区&#xff1a;JCR2/3区&#xff0c;中科院4区 • 检索数据库&#xff1a;SCI&EI 2、研究领域&#xff1a; 分享了能源…

开源好用的堡垒机工具Jumpserver

Jumpserver是一个由杭州飞致云信息科技有限公司&#xff08;FIT2CLOUD飞致云&#xff09;开发的开源堡垒机系统&#xff0c;旨在帮助企业构建和强化运维安全审计能力。 以下是关于Jumpserver的详细介绍&#xff1a; 一、基本概述 定义&#xff1a;Jumpserver是一个开源的堡垒…

CTFshow之RCE代码命令远程执行第53关到第64关详细讲解。可私信!

手眼通天和大人。 --真锅和 引言&#xff1a;今天继续ctf之旅&#xff01;&#xff01;解决53关到64关 ps&#xff1a;今天是8.22&#xff0c;重新回归开始填坑&#xff0c;先发个废品 一、实验准备 1、ctf网址&#xff1a;ctf.show 2、工具&#xff1a;fi…

cmd发送邮件:如何通过命令提示符发邮件?

cmd发送邮件的安全性考量&#xff1f;如何设置cmd发送邮件&#xff1f; 通过命令提示符发送邮件则提供了一种便捷且高效的方法&#xff0c;特别是在自动化任务和脚本化工作流程中。AokSend将介绍如何通过命令提示符实现发送邮件&#xff0c;并讨论其应用场景和注意事项。 cmd…

上线一天销售额超15亿!《黑神话:悟空》火爆全网的技术秘诀!

昨日&#xff08;8月20日&#xff09;上午&#xff0c;国产游戏《黑神话&#xff1a;悟空》正式发售&#xff0c;在全球游戏市场掀起巨大狂潮&#xff01;上线第一天在Steam的PCCU&#xff08;同时在线用户数峰值&#xff09;排名已成为第三&#xff01; 此款游戏上线即回本。…

Stable Diffusion【应用篇】【艺术写真】:超高相似度人物换脸写真,IP-Adapter与InstantID完美结合

艺术写真&#xff0c;以其独特的魅力&#xff0c;吸引了无数艺术爱好者和摄影爱好者。如今&#xff0c;借助Stable Diffusion的IP-Adapter和InstantID技术&#xff0c;你只需一键操作&#xff0c;就能轻松实现超高相似度的人物换脸写真。本文将带你深入了解Stable Diffusion的I…

如何建立Pod

文章目录 一、Pod的生命周期建立pod1. 提交 Pod 定义2. API 服务器处理3. 调度4. 节点准备5. 容器初始化6. 启动应用容器7. 持续管理 Pod结束Pod的探针1. 存活探针&#xff08;Liveness Probe&#xff09;2. 就绪探针&#xff08;Readiness Probe&#xff09;3. 启动探针&#…