Unite 2019 个人笔记小结

在5月10日到12日期间,有幸在上海国际会议中心参加了Unite Shanghai 2019大会。为期三天的Unite大会为大家带来了分享、交流和学习的平台,让开发者获取到最新的Unity技术知识和使用技巧。

因为没有参加10日的Training day,我就总结一下11日和12日的内容
这里大部分是本人的感想和一些笔记的记录,如果有问题,请大家见谅

技术专场——11日

11日的技术专场几乎都是外国大佬,本人英语不是很好,使用了同声翻译,但说实话同声翻译的并不是很好,外加上我看的内容并不是那么硬核,所以重点比较少。

1.Unity 科幻巨作《异教徒》影视级技术大起底

简单来说就是介绍了《异教徒》中的一些技术,主要集中于Timeline和摄像机后处理Volume

2.Unity Tiny Mode 概述

这方面的介绍也很简单,如果有兴趣可以看Unity官方的视频:https://www.bilibili.com/video/av41365817
我就不多赘述了

3.新一代后期处理栈:Post Processing v3

又放了一遍《异教徒》,这次主要集中在了后处理方面,比第一场要详细一些

4.Timeline 高阶使用方法

演讲者演示了他自己做的一个项目,从游戏过场动画的角度为大家演示了Timeline的使用方式

5.二次元向高品质着色器解决方案:Unity-Chan Toon Shader 2.0 介绍

日式英语,我哭了,简单介绍了NPR(非真实渲染),效果还是很不错的(虽然我觉得还是去年米哈游的技术更加让我感到惊艳),但是日式英语真的劝退。
如果想要了解UTS2可以去Github上下载该项目:https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project
当然大家也可以了解去年由米哈游带来的NPR。

6.对城宝具:CRIWARE 技术助力《FGO》高效开发生动音频

没有学到任何内容(等了40分钟,终于看到了1分钟的紫式部宝具动画),简单来说就是介绍了他们公司的
音频研发团队、技术、团队的构成和技术如何更新,但是音频这方面我真的无能为力。

7.基于 Vivox 实现语音和文字聊天:提升多人游戏体验

大意就是如题所示,介绍了Vivox的成熟技术,以及目前的广泛引用。

8.《网络奇兵 3》运用HDRP统一光照模型解析

没有学到任何内容,介绍了HDRP和PBR之间的关系,对比介绍了光照的内容(事实上我看不出来有什么区别)。

9.Unity 可编程渲染管线入门

最想了解的一项内容,但是讲的真的很差,并没有介绍SRP(可编程渲染管线),而是介绍了LWRP(轻量级渲染管线)和HDRP(高清渲染管线),然后就是ShaderGraph。
重点介绍了LWRP+ShaderGraph这个组合,然后在LWRP中自定义Render Passes,因为使用SRP重新构建渲染管线比较困难。

10.Unity 影视级工具链指南

我没看,回去了…


技术专场——12日

这一天就都是中文了,不需要翻译,我在技术专场1听了一整天,大部分内容都是干货,甚至有些干的连点水分都没有,当然也有一些比较水的内容。

1.《使命召唤手游》引擎技术升级与演化

因为吃早饭去晚了,只听到了后面的一部分,因此没有技术性的内容,我只总结一下观点:

  • 引擎技术移动端化
    1. 线性空间运算
    2. PBR渲染
    3. 雾和大气
    4. 角色渲染
  • 引擎与工具、硬件升级
    1. 引擎版本升级
    2. Substance和Houdini工具
    3. 芯片发展
  • 前沿技术推动
    1. RTX光线追踪
    2. 过程化制作
    3. 机器学习

2.Unity C# Job System 的功能使用

首先就是Unity目前推出的全新高性能多线程式数据导向型技术堆栈也就是DOTS(Data-Oriented Technology Stack),使用这个技术可以充分利用多核处理器,无需进行繁重的编程工作。
DOTS主要包含以下三个功能:

  1. C# Job System,用于高效运行多线程代码
  2. ECS(Entity Component System),用于默认编写高性能代码
  3. Burst编译器,用于生成高质量的本地代码
    而C# Job System可以利用计算机下的多个核心,拥有高效的多线程代码执行框架,管理着多个核的线程。而工作线程的数量,根据平台的不同,基于核的数量来规则,会取到CPU内核的信息,并合理分配内核,用于提升效率。
    在Job System的工作流中,有一个主线程和多个工作线程:
    Unite 2019 个人笔记小结_第1张图片
    首先在主线程中有Schedule,它会分配Job给各个工作线程,那么在这里,如果Job是并行计算,那就最好等并行计算结束,而这里Force就是为了等待并行计算结束,然后进入下一个Schedule;如果没有Force进行等待,那么连续的Job就会让效率变得非常高。
    这里还有一个机制叫做Batch(可以理解为批次),我们可以将Job打包成为一个Batch,不是马上进入队列,而是在我们想要的时候进入队列。
    举个例子,目前有500个Job,那我们可以打10个Batch,那么每个Batch就是50个Job,如果我们有12个线程,那每个线程就是4个Job。
    而这里就涉及到了多线程,为了多线程的安全,需要新的容器——NativeList和NativeArray,并且要注意这个容器是临时使用还是长时间使用,合理使用Dispose。NativeList即可读也可写,而NativeArray只可读或写。利用Allocator.Persistent/Tmep/TmepJob可以用来检测内存。
    如果当某个逻辑比较耗时,那就可以利用Job System,但是要注意,不要在Job System中使用托管内存。
    结构体在大于40Byte时就会变成内存赋值,因此要控制值的大小,也可以使用Ref Return来避免。
    利用Job Debugger可以观察内存的释放。

3. AI 赋能 AR 的开发与应用

来自商汤科技的AI、AR的开发技术,主要是技术展示,内容比较少。

4.Timeline 高级应用案例解析

有关Timeline的内容看情况我会再写一点东西。
这里的应用案例和上面的使用方法不太一样,这里主要介绍了Timeline的结构。Timeline的底层是PlayableAPI,是一套树形结构,允许用户通过脚本来定义行为(非线性编辑工具),可以自定义Track。
Track就是一组序列化的数据作用在时间轴上的操作集(记不太清了,大概是)。
在这里插入图片描述
Track主要有三部分

  1. Track,上图中左边部分,即TractAsset(binding)
  2. Clip,上图中a1部分,即PlayableAsset(data)和PlayableBehavior(logic)
  3. Blend,上图中a1和a2交叉的部分,即MixerBehavior(logic)
    这里举个简单的例子,如果你要自定义一个Track,那么只要继承自TractAsset即可。
public class MyTrack : TrackAsset
{
}

而Timeline主要分为两类:

  • Default Track
    1. Activation
    2. Animation
    3. Audio
    4. Control
    5. Playable
    6. Group
  • Default Playables
    1. Light Control
    2. NavMesh Control
    3. Screen Fader
    4. Text Switcher
    5. Time Dilation
    6. Transform Tween
    7. Video

这里还演示了一个Demo,主要是用Timeline实现了一个Dialogue系统,可以根据用户的输入做到非线性播放。还要注意:1.可以利用嵌套Timeline防止Timeline停止导致的BGM停止;2.可以在Timeline中嵌套AI;3.可以利用条件Timeline时间来控制AI的行为。(都还没验证)
未来我可能会介绍一个Timeline的使用,接着挖个坑。

5.Unity 轻量级渲染管线(LWRP)源码及案例解析

这一次讲解的就比上一次讲解SRP要细的多了。
首先LWRP是基于SRP的,而SRP的底层部分被封装了起来,然后上层使用C#。LWRP和HDRP都是基于SRP的,并且都是开源的,可以去Github上下载:https://github.com/Unity-Technologies/ScriptableRenderPipeline
LWRP支持全平台,而HDRP仅支持高端平台。
如果要使用LWRP那么首先需要CoreRP和LWRP,可以通过Package Manager获得,之前所说的Volume可以用来做后处理。
LWRP中提供了很多功能,这些都可以自己查看案例,或者自己实际操作。
由于这部分的内容比较复杂,我以后有空会再具体介绍,再挖个坑。

6.技术与文化的融合:Unity 引擎与数字故宫的实践结合案例分享

没什么实质性内容,主要讲的就是Unity+VR。

7.Unity 的 Asset 管理和序列化

究极硬核,超级干货,其实我都没法转述,我都想再听一遍,这里我也只能做到尽量总结。
什么是Unity序列化系统?
使用Unity的序列化系统来从硬盘或内存上保存和读取数据,比如说Instantiation(),再比如说打包之后的level0、sharedAssets0等文件,都是序列化之后的文件,而序列化的底层是C++,然后在C#中使用。简单来说就是将字段转换成文本,在这中间就需要一个transfer<>函数来帮助实例化的操作,而这个实例化的规则是由自己来规定的,以此做到文本的转换。
那么Unity中的各种Assets就是序列化后的产物,主要分为三种:

  1. Assets
    1. Native Assets(原生Assets),例如:Scene,Prefab…
    2. Import Assets(导入Assets),例如:shader,模型…
  2. meta 包括GUID和Import Setting,是文本文件
  3. Library/metadata 序列化后的资源,真正的数据

以下,我只是记录我的笔记,没什么逻辑,如果觉得看不懂就别看了

  1. Assets Pipeline 用于分配GUID,创建.meta文件,序列化之后保存
  2. Native Assets原生资源,可以保存为二进制格式
  3. Scene:主要由Setting和Object及其子项构成
  4. GUID:128bits/分配给Assets/保存在meta中
  5. FileID:分配诶给Object/保存在序列化文件中
  6. 通过GUID查找Asset文件;通过FileID查找MeshRenderer
  7. —!u!4 &456897987 其中4是ClassID即Transform,后面的一串是它的FileID
  8. Imported Assets是非原生资源,例如:Script,Shader,Model,Texture…根据setting和目标平台进行资源处理和序列化
  9. metadata序列化文件,文件名就是它的GUID,通过GUID的前两位hex进行分组,也就是共256个分组文件夹,metadata默认为二进制
  10. shader assets会根据不同的部分(关键字)进行序列化(序列化过程是多线程压缩数据),反序列化过程中会解压数据,从工作线程回到主线程,因此会出现尖峰,Unity Profiler不是原生查看shader的。优化建议:不要先看keyword,因为有的时候subshader多,pass多,会产生C++部分的对象。
  11. script assets是文本文件,会判断是否需要编译,从C#到IL,Tick->DirtyScript->c++激发->修改文本
  12. model 通过Mesh Compression /Vertex Compression/Optimize Mesh Data来优化model的数据,Vertex Compression通过通道(pos,uv,vertex)会影响压缩的序列化信息;Mesh Compression比较迷,我也没怎么听得懂,反正不好用(貌似是通过舍去浮点数信息);OptimizeMeshData会关闭掉一些channel来压缩数据

总之这一章节真的很硬核,如果需要了解的人还是等官方视频或者看看别人的研究,我这里就是记录我了解到的信息。

8. Unity 中的实时光线追踪技术剖析

显卡推销(大概)RTX系列,拥有新架构(Turing SM),实时光线追踪(RT Cores),加速AI计算(Tensor Cores)。

光线追踪原理

简单来说就是:发射光线=》递归找三角面=》交点处理(判断,计算,是否继续…)=》着色(是否有交点,如果没有交点就采用天空盒,有交点是采用最近的交点/忽视该交点并继续发射光线)
那么首先来说这个检测三角面的原理,主要是利用了BVH(Bounding Volumn Hierarchy)遍历,简单来说就是从大的包围盒一层层的到小的包围盒最后到三角面(可能可以参考八叉树的技术,大概吧),而这个递归找三角面的过程就是在Turing SM中利用RT Core来完成。
目前,在DirectX中有光线追中的接口,而问题就是会出现噪点(我认为是光线的数量太少导致的),利用RTX DENOISER SDK(Reflection/Shadow/AO)可以解决这个问题。

发射光线
ID3D12CommandList::DispatchRays()
  • 可以与Draw、Dispatch函数放在同一队列中
  • 即可以放在Graphics Context中,也可以放在Compute Context中
  • 声明一个线程阵列,调用Ray Generation Shader
HLSL函数TraceRay()
  • 追踪一条射线
  • 通过定义Payload结构返回数据结构
交点处理
Hit Group
  • 由Closest Hit,Any Hit,Intersection三种shader构成(这三种shader都不是必需的)
  • Intersection用于和自定义的几何体求交(基本不用)
  • Any Hit与任一交点接触就触发(不保证顺序,比如该射线穿透1、2、3共三个三角形,那么1、2、3都有可能触发)
  • Closest Hit一定在最近点,最多只有一次
加速结构
两级层次结构
  • 底层:基本的几何体信息,包含三角形或包围盒数据
  • 顶层:指向底层几何体的实例、包含相应的资源索引和坐标变换数据
  • 可以拥有相同的加速结构,也可以有不同的底层结构
  • 具体格式不可见
着色器表

着色器表(Shader Table)包含了一次光线追踪中可能用到的所有资源,每条记录可包含加速结构中的节点指向着色器表中的对应记录

Unity中的光线追踪(Experimental)
  1. 初级射线(半透明/折射)
  2. 刺激射线(反射/面光源软阴影/AO/间接光照/GI)
  3. 仅查询交点(面光源软阴影/AO)
  4. 查询交点+着色(反射/间接光照/GI/半透明/折射)《= 性能较差

可参考:https://github.com/Unity-Technologies/Unity-Experimental-DXR

9.HDRP 中的 FPTL 光照管理系统原理与实现

在说明HDRP之前,首先先要了解两种渲染管线:Forward Render和Deferred Shading

  • Forward Render:针对光照和对象进行迭代,片元着色器浪费严重,可用MSAA
  • Deferred Shading:先计算对象,再计算灯光,光源越多,开销越大,不可用MSAA
    那么接下来就是针对这两种渲染方式的优化
  • Tield Based Deferred Shading:逐个分方块进行着色,读取G-Buffer和光源列表及相关信息,对每个Tile进行计算,用Light Index List进行渲染。可以减少数据读取,降低内存带宽使用
  • Forward+ Rendering:基本同上,但使用Z-Buffer分Tile,得到深度包围盒,求得Light序列,同上使用Light序列进行渲染,但是随着场景复杂度的提升会有提升
    最后就是重点了:
Fine Prued Tiled Light Lists
  • FPT是在Tile裁剪的基础上在进行一次剔除
  • 可用于Forward Render和Deferred Shading两种渲染管线
  • 利用了并行架构进行优化
  • 将线程分为线程组再分为多个线程,Thread Group中线程可以同步,做到每个算法单元的同步(要考虑到硬件性能)。在指定线程组中的线程数量时,大小要小于等于1024,AMD中为64的倍数,intel中为32的倍数
  • CPU和GPU数据传递通过Computer Buffer(同步,需等待结束,在Editor中也可以使用,但是性能较差)
  • GPU和renderpipeline数据传递通过RWStructured Buffer(在GPU中传递)
一般步骤
  1. 对于每个相机
  2. CPU端查找所有与视椎体相交的光源列表,并排序
  3. GPU端计算在屏幕空间下光源的AABB
  4. 将屏幕分成16*16px per Tile
  5. 通过screenXY计算Tile的数量
  6. 那么1个Tile=1个thread group=64个线程
  7. 计算depth-buffer中的最大与最小深度(与线程同步)
  8. 得到min,max后,将相交光源的index存入Light List
  9. 做fine prued裁剪(判断是否在光源形状中,检查光源的类型,一条线程1个像素、一条线程4个光源,用Or操作最后储存的位数据)
  10. 在最终的渲染中使用该光源
Tips
  1. 首先在HDRP中要开启该功能(在Assets中)
  2. 因为需要深度值,所以只能用于不透明物体
  3. 在deferred中强制开启,透明物体使用clustered
  4. 在forward中选择开启,透明物体使用clustered

10.增量式垃圾回收

无论是Mono还是IL2Cpp,用的都是Boehm GC,这是一种保守式、标记-清除算法。
而目前GC的问题在于性能尖刺,目前的回收原理大致如下:

  1. Stop the World,挂起,保证安全
  2. 标记托管堆
    2.1 从Root触发
    2.2 检查
  3. 清除
  4. 继续

而标记的工程,也就是耗时最大的过程,使用的是深度优先的算法(非递归),使用堆栈辅助标记。

  • 一部分是托管代码(静态数据,由C#生成)
  • 另一部分是不在托管堆的代码

从mark stack中标记,扫描内存段,保守式的指针识别(因此可能会额外扫描内存块,也有可能出现误判),会有内存泄漏的可能(没有泄漏是因为有优化)
而Stop the World就是尖刺的罪魁祸首,有以下方法来解决:
分帧、多线程、并发式、分带式(也就是增量式GC,并且比较好)

Incremental GC

简单来说就是改造了标记的过程:

  1. 首先判断是否可以直接回收,如果会超时,那就进入增量式回收
  2. 反复标记
  3. 最后停止标记,完成

但是要注意避免在垃圾回收时产生大量垃圾导致回收失败,而且新增的引用也会导致危险(当其父项被标记清除时),而且工作量上升了,只是分摊了工作量。
对于内存是否被标记过是使用VDB(Virtual Dirty Bit),以内存页为单位:

  • 大对象——任意内存页有dirty——追踪引用变化(同时能发现回收时产生的变化)
  • 小对象——是否有dirty——追踪引用变化(同时能发现回收时产生的变化)
    这里重点在于WriteBarrier,这方面的内容可以自己查询
性能小贴士
  1. 释放或多增加内存
  2. 扩大托管堆(仍要关注GC分配,降低启动阈值)
  3. 避免在垃圾回收时产生大量垃圾导致回收失败,因为会退化成Full GC

总结

不想总结,累了(12号的内容太硬核了,感觉自己还有很多要学)

你可能感兴趣的:(Unity笔记)