[Unity] 基于Unity3D的大地形研究(2):资源序列化与材质加载

查看:843 |回复:0 | 2020-12-9 20:52:50

您需要 登录 才可以下载或查看,没有账号?注册

x
基于Unity3D的大地形研究(2):资源序列化与材质加载
在上一篇文章中,我们已经实现了针对GPU Driven Rendering Pipeline的Cluster Scene Streaming load,然而在上一篇中却并没有提供出一套像样的资源管理方案,那么在本文中我们将介绍一套基于Unity Editor与.Net Framework的资源异步加载,序列化反序列化的方案。
因为目前Unity并没有理想的二进制加载方法,要么异步容易出现堵塞,要么GC极高造成卡顿,因此我们将直接使用.Net Framework提供的加载方法在异步线程加载二进制模型和贴图文件。是的,这种方法听起来非常的“反人类”,完全与Unity自带的人性化资源管理背道而驰。然而现在我们的GPU Driven RP的渲染方法可以认为本身就是反人类的存在,强行拆分所有静态模型,模型不再存在“物体”的概念,并且要求所有模型都使用统一的材质,最后所有模型还要暴力的塞到显存中,等待渲染调用。因此,我们必须在引擎中通过成熟的工具链保证引擎既对开发设计人员友好,又不破坏GPU Driven RP的。
其实这篇文章拖了非常久,原因在于为了实现合理的贴图材质加载和项目管理,本人也是尝试许许多多的方案,最先考虑的是早期主机上使用的Virtual Texture方法,然而这个方法也是首先被毙掉了,并不是说这个方案不用了,在之后的平坦地形绘制中可能依然会使用到,只是在建筑物中需要额外的CPU剔除和显存回读操作,之前的文章中介绍过,目前家用PC的主板IO速度非常令人窒息,所以实时的回读操作基本是不被允许的。然而现在显存容量却基本达到了一个够用的标准,6G-8G是一个比较普遍的理想的容量,部分旗舰级别的显卡可能在10G到12G左右,大多数情况下显存容量是绝对够用的。同时,我们渲染时的贴图量是不太可能太多的,因为这套管线依然是专注于绘制材质比较简单重复量比较大的部分,比如岩石,地上的石子,一些背景房屋等等静态部分,而游戏中一些可能有互动的部分则依然依靠传统的渲染管线来代替。
再来说项目管理方面,按照之前文章讲过的,在开放大地形的搭建中,我们将会把场景拆分成Sub Scene/Sub Level,可能每个sub level只有几百万面,以此来保证流式加载的精确性,因此在本文中我们将会对Sub Level进行统一的管理,保证项目管理的效率。
正如之前所说,传统的贴图加载方法是无法满足需求的,我们将使用二进制手动异步读取的方法完成加载。.Net提供给我们的API BinaryReader已经足够使用了,BinaryReader支持使用相对路径,这一点非常不错,在打包时基本上只需要把储存有项目二进制数据的文件夹复制到打包后的文件夹中即可,比如在现在的项目里这个文件夹被命名为BinaryData,那么我们直接暴力读取Assets/BinaryData/即可,在发布时也可以直接把该文件夹复制到发布的文件夹里,配合可视化的序列化工具,对项目管理非常友好。
首先,我们需要整理场景中的材质信息,把这套材质信息储存到Scriptable Object中,因为材质信息相对轻量级,所以Scriptable Object足可以应付,数据结构大概是这样的:
v2-d13de8cc7e1cd59b0188b1e3f2fc2fa1_720w.jpg 先从最上方往下看,ClusterProperties将储存所有场景的信息的索引,往下走Name和ClusterCount储存着场景的名称和cluster数量,加载方式在之前文章中已经讲到了,下方的Properties则是所有材质的属性信息,如_Color等都会被工具自动收集并存储到这里,那么材质使用的贴图呢,贴图则都储存在下方的TexPaths中,包括_MainTex, _BumpMap这些,都已GUID的形式储存起来,这样在加载时可以直接使用guid当文件名去读取,也可以使用guid查重防止重复加载,Unity自带的AssetDatabase基本可以保证guid的唯一性和可靠性。
有了这样的数据结构,接下来就是在异步加载中实现对位,之前提到过,这里使用gpu driven  rp绘制的部分贴图重复率比较高,所以我们也提供了一个比较笨拙但是勉强能用的方案来处理贴图的加载,也就是使用RenderTextureArray来储存贴图。在把贴图以guid的形式全部序列化成二进制信息之后,传递给StructuredBuffer,再使用Shader逐像素的写入到RenderTexture中,经过实际测试,这样的方法效率要远高于CopyTexture。
在经过异步线程读取二进制,主线程传显存,并使用字典去重,防止出现多余的加载,这时需要用到的所有的贴图都进入了同一个TexArray里,那么这时该如何使用这个贴图呢,在Shader中我们是这样实现的:
v2-da4823bc2ac8d1f28faa55ce23a0913d_720w.jpg 为了对团队内其他TA更加友好,在编写Shader时采用了传统Surface Shader的封装方法,这样输入输出可以做到和光照运算解耦合。传入的uv自不必说,index是来自appdata,也就是我们手动传入的cluster信息,这个在之前的异步线程中已经对接到属性了,因此这个index是直接指向属性的,这里读取PropertiesBuffer,获得属性。
属性的定义如下:
比较值得一提的是这里的textureIndex,textureIndex使用了一个int3的数据类型,其中包含的3个索引分别是Albedo,BumpMap和Specular Map,如果索引的值为-1,则表示当前材质并没有使用该贴图,否则必须强迫所有材质的贴图都非空,会对美术工作造成一定的困扰,这种情况我们是不想的。
这样,贴图加载的过程可以大概如下:
  • 异步读取硬盘二进制数据
  • 根据GUID获得贴图索引(若贴图已经存在,则跳过第5,6步)
  • 将贴图索引写入PropertyValue
  • 将PropertyValue写入全局统一的StructuredBuffer
  • 异步加载贴图数据(若贴图GUID已经存在,则跳过这一步)
  • 将数据写入到显存中(若贴图GUID已经存在,则跳过这一步)
  • 结束协程,完成加载
用UML Sequency Diagram表现一下,大概是这样的:
当然这一篇文章只能属于整个资源加载的序章,因为这方面太过庞大,对工具链依赖很重,所以目前真的只停留在可以画个材质球上(笑),在接下来的几篇文章中,我们将尝试使管线与一些现有的工程模式进行融合,以便使GPU Driven RP更快的投入到生产中。




2020-12-9 20:52:50  
 赞 赞 0

使用道具 登录

0个回答,把该问题分享到群,邀请大神一起回答。

CG 游戏行业专业问题

图文教程技术文章技术文库手机游戏引擎手游引擎
显示全部 9
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表