入门必看:Unity资源加载及管理

入门必看:Unity资源加载及管理,第1张

Unity的资源加载及管理,基础很重要。此篇文章作为近期梳理项目内资源管理器的一个小总结,尝试尽量用人话将Unity对资源管理的关键点梳理清楚,个人觉得比较适合像我这样刚入门且对AssetBundle还不甚了解的家伙。

我理解的资源管理

举一个不恰当的例子来描述我所理解的资源管理(因为我实在想不出更合适的例子了),想象一个画面:一个表演者,站在一个台子后面,面向观众,按照规定的剧本, *** 作着台子后面不被观众看到的箱子,从里面不断的取出和放回各种新鲜的玩意儿,一会这么组合,一会那么拆散,博观众的眼球,最终完成表演。

我没有当表演者的经历,虽然我很想尝试,但想想也觉得这肯定不容易:

1、如果箱子里的东西都太大,拿起来会很费劲。

2、如果太小呢?恐怕会拿很多次。

3、不用的道具不收?放在台子上会影响接下来的表演。

4、用过的道具收了吧。万一收了后面需要的道具,一会儿用的时候还要再费劲拿一次,不拿的话吧还容易导致表演失败。

带着问题看文章

是选择合适的时间取出资源,并在合适的时候释放它们,尽可能保持较低的内存占用;还是选择让资源常驻内存,换取更快的读取和计算速度?如何在时间和空间上做出平衡,才能最大的提升游戏体验?我以为这些就是资源管理的目标和意义。可惜这并非易事。这不仅需要结合项目的实际情况,更需要丰富的实战经验。

但是在这里,你将不会看到任何可以参考的经验或建议,因为我也不知道啊。

无论你是否单身,在Unity的世界里,你都不愁找不到对象,因为一切都是对象。

无论是纹理、音乐还是预制体,在进入Unity的世界后,都变成了各种对象供我们使用,例如纹理转变为Texture2D或Sprite,音效文件转变为AudioClip,预制体变成了GameObject等等。这个由Asset(资源文件)转变为Object(对象),从磁盘进入内存的过程,就是实例化。而对资源进行的管理,本质上是对Object的管理。

“小当家,这个黄金炒饭是怎么加载出来的?”

简单介绍一下Unity加载资源的流程

在介绍Unity的资源加载机制之前,先举一个生活中的例子,来辅助我们了解Unity是如何工作的。

因为我从小热爱欧洲文学,所以在这就拿我最喜欢的《三国演义》做例子,我们都知道书中多次提到 “集齐七颗龙珠,就可以召唤神龙,并帮你实现一个愿望” 这种说法。

咸鱼都有梦想,何况一个上了岁数的程序员呢?但是很可惜,我们一颗龙珠都没有,为了凑齐这七颗龙珠,我们首先要知道它们分别在哪。一摸左兜,哎?发现了一本《召唤神龙的小诀窍》,里面记录了召唤神龙所必须七颗龙珠的 所在位置、大小、颜色以及如何使用等非常关键的信息 。

根据《召唤神龙的小诀窍》指引,我们知道原来第一颗龙珠藏在了素有小巴黎之称的北京通县,可是通县在哪儿呢?一摸右兜,原来这还有一本1986年出版的《中国地图》。那就放心了,出发吧!

终于,历经了81难,我们来到了目的地并最终找到了这颗龙珠。费这么大劲找到的龙珠,当然应该认真记录下来,于是我们马上掏出一个黑皮小本本,认真的记下: “第一颗龙珠放在背后小书包的左边缝有一个机器猫的侧兜里” 。

最终,经历了无数艰难险阻,我们凑齐了七颗龙珠(所以说人只要肯努力,老天就一定回馈你,至少让你知道你浪费了时间啊)。金光一闪,我们召唤出了神龙 后面实现了什么愿望我们不谈,因为谁没有点小秘密呢。

现在,让我们来回顾一下整个过程

1、这条召唤出来的神龙,就好比我们想要实例化的对象,就比如游戏对象吧,因为它相对复杂些。而这七颗龙珠呢,就好似组成这个游戏对象所必须的各种组件(Component)、纹理(Texture)、网格(Mesh)等等。

2、《召唤神龙的小诀窍》就好比我们读取的这个prefab文件,它记录了组成这个GameObject所必须的其他对象以及它们的位置。

重点来了:File GUID 及 Local ID。

File GUID

Unity会为每一个加入到Assets文件夹中的文件,创建一个同级同名的meta文件,虽然文件类型的不同会影响这个meta的具体内容,但它们都包含一个用来标记文件身份的File GUID。

例如,如果一个资源引用了另一个外部资源,比如一个Prefab引用了其他脚本、纹理或Prefab等,则一定会标明引用资源文件的File GUID。

Local ID

如果说File GUID表示为文件和文件之间的关系,那么Local ID表示的就是文件内部各对象之间的关系,打开一个Prefab文件可以很清晰的看到:

(点击上图,可放大查看)

一个对象通常是由 一个或多个 对象构成,每个记录在&符号后面的数字都是一个Local ID,每一个Local ID也表示这它将来也会被实例化成一个对象。也就是说,当一个prefab文件要实例化成一个GameObject时,它会自动尝试获取其内部Local ID所指的那个对象。如果这个所指的对象当前还没有被实例化出来,那么Unity会自动实例化这个对象,如此递归,直到所有涉及的对象都被实例化。

3、我们可以发现手中没有龙珠,是因为我们手中的黑色小本本,并没有记录龙珠装在书包的那个位置里;同样,Unity通过 Instance ID ,来获取或判断一个对象是否已经被加载完毕。 Instance ID由File GUID和Local ID转换而成,可以简单理解成是记录了资源所在内存地址的写着数字的钥匙牌。

每当Unity读入一个File GUID和LocalID时,就会自动将其转换成一个简单好记的数字牌,因为通过File GUID和Local ID定位资源的效率并没有直接解引用一个地址那么快。

如果发现这个牌上并没有挂着一把钥匙,表示当前这个这个资源还在磁盘中,尚不在内存里(没有加载);相反,如果这个牌子上有一把钥匙,表示这个资源已经被加载完毕,你可以快速的找到并使用它。

Unity会在项目启动后,创建并一直维护一张“映射表”,这张映射表记录的就是File GUID、Local ID以及由它们转换而成的Instance ID之间的关系,这样下次在请求资源时就可以快速的通过查看钥匙牌来获取资源了。

4、刚才的例子里,因为没有龙珠(资源没有加载),因此我们必须经历一场前往小巴黎的历险(LoadingAsset),而能够帮助我们准确定位北京通县的86版《中国地图》,可以近似理解成是 Unity维护的一套将GUID和FileID解析为数据源地址的机制, 这套机制中的信息,来自于:

(1) 场景加载时,Unity收集了与该场景关联的资源信息。

(2) 项目启动时,Unity收集了所有Resources文件夹下的资源信息。

(3) 读取AssetBundle时,Unity获取了AssetBundle文件的头部信息(Header)。

可以理解为:随着Unity知道更多的信息,这套机制将能够解析并定位更多的GUID和FileID。

5、当我们费劲千辛万苦找到龙珠后,记录在小本本上的7条位置,就好比7个能帮助够准确定位内存位置的Instance ID。想象一下,当我们下次再看到诸如《三颗龙珠召唤小神龙》这样的小诀窍( 另外一个prefab ),便可直接打开小本本( 查询映射表中的Instance ID ),对着编号及位置从书包里掏出龙珠( 对InstanceID所指的内存地址进行解引用 ),啪啪啪一 *** 作,小神龙这个游戏对象就能很快被召唤出来了,再也不用去什么通县了,可以节省大把时间,想想就觉的美滋滋呢。

AssetBundle

AssetBundle(阿赛特邦豆)是Unity官方推荐的资源加载方式,网上对AssetBundle的介绍有很多,且在了解了Unity对资源的加载机制后,其本身没有什么特别难以理解的地方了,因此在这不过多介绍,仅挑选几个关键点进行阐述。

AssetBundle的生成

生成AssetBundle有很多种方式,在此仅简单说一下比较常用的方式,使用BuildPipeline生成AssetBundle文件。

每一次调用BuildPipleLineBuildAssetBundles时,将会生成一批AssetBundle文件,具体数量根据传递AssetBundleBuild数组决定, 每一个AssetBundleBuild对象将对应一个AssetBundle及一个同名+manifest后缀文件 。其中AssetBundle文件的后缀用户自行设置,比如"unity3d","ab"等等;而manifest文件是给人看的,里面有这个AssetBundle的基本信息以及非常关键的资源列表。

除了AssetBundleBuild数组所定的AssetBundle外,还将额外在output路径下生成的一对与output文件夹同名的文件及一个同名manifest后缀文件。这个同名文件可厉害了,它记录了这批次AssetBundle之间的相互依赖关系。当然manifest文件还是给人看的,我们可以用它分析资源间的依赖关系,但是在项目实际运行时,Unity并不会关心它。

(点击上图,可放大查看)

可以通过这张图来看一下每次Build后资源的对应关系,当然这都不如你自己亲自Build一次看的清楚。

AssetBundle的加载

根据AssetBundle文件所在的位置(本地、远端),AssetBundle有不同的加载方式,在此仅总结最常用的本地AssetBundle文件加载。

我个人将AssetBundle拆分理解为: Bundle加载 和 Asset加载 两部分。因为AssetBundle文件可以从功能上分为两大块:

1、记录文件标记、压缩信息、文件列表的Header部分;

2、记录资源实际内容的Data部分。

当使用AssetBundleLoadFromFile或LoadFromFileAsync时,在pc平台及移动平台上,unity 仅会为我们读取AssetBundle的header部分,并不会将bundle的data部分整个读入内存。

当调用上一步生成的AssetBundle对象读取具体资源时(LoadAsset, LoadAssetAsync, LoadAllAssets),Unity会参考已经缓存的文件列表,找到目标资源在data部分的位置并读入到内存中。

如果一个资源引用到了其他资源,则必须要先读入被引用资源的AssetBundle文件,否则就会发生引用Miss。 这就好似召唤神龙时,通过《召唤神龙的小诀窍》得知第一颗龙珠在北京通县,但是当打开《中国地图》时,北京的地方被抠了一个窟窿,我去,这样我们就无法通过它准确定位龙珠位置了,只有六颗龙珠召唤出的神龙,当然有一部分是Miss喽。

为了避免上面Miss的情况,在加载资源时, 首先需要将该资源的依赖项全部加载完毕,不过仅需加载依赖资源的AssetBundle文件。 也就是说,我们只要将该依赖AssetBundle的Header部分加载(AssetBundleLoadFromFile或LoadFromFileAsync)就可以,这样在真正读取Asset时,Unity会自动处理好真实依赖的Asset,我们不用 *** 心。

AssetBundle的依赖关系如何读取呢?加载上面提到的那个很厉害的文件就可以了。

(点击上图,可放大查看)

非常简单的获取依赖关系的方法,通常会在项目启动时将全部依赖关系保存下来。

AssetBundle的使用

当AssetBundle被成功加载后,调用该Assebbundle对象的LoadAsset、LoadAllAssets或对应的异步版本即可加载资源,也就是实例化对象。如果这个对象已经被加载过,Unity并不会重复加载,还记得之前所说的映射表么,被加载过的资源就好比挂上了数字牌的钥匙,直接对地址解引用即可。

AssetBundle的卸载

如果说AssetBundle真的有什么容易出问题的地方,那恐怕就是卸载了。

在这里只说最常用的这个卸载方法吧:

public void Unload(bool unloadAllLoadedObjects);

一个被加载过的AssetBundle可以通过调用Unload来卸载这个Bundle下所有的Asset。但是调用这个函数时传入的参数 对卸载结果影响甚大。

Unity官方对这个函数的讲解非常详细,配图也非常直观,因此我只是简单总结一下。

相同点:

无论传入参数为 true 或是 false,调用Unload都可以Destroy当前AssetBundle对象,释放之前从AssetBundle文件中的Header部分所获取的信息。当然,被释放的AssetBundle对象无法再使用诸如LoadAsset、LoadAllAssets等函数加载资源。

不同点:

unloadAllLoadedObjects == true:

不仅Destroy了AssetBundle这个对象,而且 这个AssetBundle下包含的所有对象,只要实例化了,有一个算一个,统统释放掉。

感觉就像

foreach(Object  asset in assets)

if(asset != null)

delete asset;

asset = null;

比如你通过abLoadAsset(apple)后,将apple设置给go_0的一个Renderer,如果这时候abUnload(true),那go_0就傻了,咋回事儿啊,图咋没了呢?WTF啊。

它的好处是: 不会有重复资源问题的情况发生,每次都处理的干干净净。

unloadAllLoadedObjects == false:

仅仅Destroy了AssetBundle这个对象,但是并没有释放这个AssetBundle下的任何Asset,因此如果有对象引用了这些Asset,也不会有问题。

它的风险(代价)是:下次再Load这个AssetBundle,并且通过这个AssetBundle重新读取了这个Asset,会在内存中重新创建一份,这样如果之前的Asset没有被释放,那么现在内存中就有两份Asset了。

这种情况如果频繁发生,便意味着内存中有很多资源将“不受控制”,容易引发内存占用过高的问题,而释放这种不受控的资源,仅有两种方式:

1、当没有对象引用到这些不受控资源时,每次调用ResourcesUnloadUnusedAssets,回收之。

2、加载场景时,如果加载模式没有设置为LoadSceneModeAdditive,则会自动调用ResourcesUnloadUnusedAssets。

同样,再举一个生活中的小例子以阐述这两种释放的差异吧:

小A交女朋友时喜欢送心形的石头给对方,这天小A认识了一个女孩,并确定了关系,送了一个精心挑选的心形石头给她,海誓山盟又云雨一番后,第二天由于感情不和等原因两人分手了。小A是个暖男,他为了女孩能彻底忘记优秀的自己并开始一段新的感情,约见了女孩,将之前送给女孩的石头拿(搬)走了,从此注销了微信消失在茫茫人海中。

确实,小A喜欢强壮的女孩,因为这样比较有安全感。

小B交女朋友时也喜欢送石头给对方,周一小B认识了一个女孩,并确定了关系,送了一个精心挑选的石头给她,海誓山盟又云雨一番后,第二天由于感情不和等原因两人分手了。但是小B家里是开石材加工场的,他并不关心这块石头,”送了就送了吧,至少我经历了浪漫的爱情“,小B这么想。并注销了微信消失在茫茫人海中达1天之久。

周二的时候小B重出江湖,并认识了一个新的女孩,确定了关系,第三天第四天啪啪啪第七天,第二周的时候,江湖上就出现了一个传说,集齐小B凑齐的七颗石头,便可以召唤神龙,于是就回到了文章开头我们提到的那个故事。

没错,小A对应的就是Unload(true),而小B对应的则是Unload(false)。

补充三点

1、移动Unity资源时,要在Unity编辑器内拖动,不要在 *** 作系统下剪切粘贴。因为这样Unity会为这个文件生成一个新的File GUID及meta文件,它会打破之前建立好的关系,让所有引用过这个文件的prefab出现miss的情况。

2、实际上在项目build完成后,就已经不存在File GUID和Local ID的概念了,转而用相对简单方式建立映射,这也是为什么我们在项目运行的过程中无法获取到File GUID的原因,不过原理上它们是一样的。

3、尽管一个AssetBundle的Header部分非常小,通常只有几十KB,但是Unity并不能保证读入大量AssetBundle的Header部分后资源的加载效率。因此还是按需读取AssetBundle吧。

要注意根据平台选择支持的纹理压缩格式。

如果采用一个不支持的纹理压缩格式,U3D会自动将其转换成RGBA32或RGB24格式,并且转换前的那部分内存也会保留。将消耗更多解压时间、内存。项目中遇见的例子:在windows下,一些不是2的幂次方的纹理,在unity3d中查看的话,会多一些大小, 尺寸的改变在可接受的范围之内,但在mac下查看的时候,一个原先20k的,在windows下是120k左右,而在mac下,达到了12M。当然如果你是2的幂次方的话,unity会压缩成pvrtc格式4bits模式,会比原先小很多。所有设备对RGB 16BITS/ARGB 16BITS/RGB A16BITS/RGB 24BITS/ARGB 32BITS等支持都很好,只是这些格式算是非压缩格式,对内存消耗和渲染消耗非常不友好。

1Audio Source(声音组件)

AudioClip:声音片段,可对其直接赋值声音文件

Output:音源输出

Mute:是否静音

Bypass Effects:音源滤波开关,是否打开音频特效

Bypass Listener Effe:监听滤波开关

Bypass Reverb Zone:回音混淆开关,当勾选时,不执行回音混淆

Play On Awake:开机自动播放

Loop:循环播放

Priority:播放优先级

Volume:音量大小,范围0到1。

Pitch:播放速度,范围-3到3。

Stereo Pan:声道占比

Spatial Blend:空间混合

Reverb Zone Mix:回音混合

3D Soungd Settings:略

2Rigidbody(刚体组件)

Mass:质量。数值越大物体下落越快

Drag:阻力,数值越大物体反向加速度越大

Angular Drag:角阻力,数值越大自身旋转的速度减慢的越快

Use Gravity:是否使用重力

Is Kenematic:是否受物理的影响

Interpolate:设置图像差值

                    默认为none,由于图形更新比物理更新要快,可能会导致物体跳跃式前进。

                    Interpolate模式:物体会根据上一帧物体的位置进行平滑运动。

                    Extrapolate模式:物体会根据下一帧物体的位置进行平滑运动

Collision Detection:碰撞检测

                   对于高速运动的物体来说,当Collision Detection为默认的Discrete时,有可能存在一种情况:前一帧时,物体在碰撞器的一边,下一帧时,物体也已经穿越了碰撞器达到了另一边,以致于检测不到碰撞。这时,我们就需要连续碰撞检测。

                       Discrete:非连续型检测模式(默认)

                  Continuous:连续检测。

                                        更加精细的碰撞检测,但是很耗资源;在这种状态下,当这个刚体与其他普通刚体碰撞时,仍将使用Discrete的碰撞检测,但是与没有刚体的Mesh Collider碰撞时就会连续检测了。

   Continuous Dynamic:连续动态检测。

                                         对没有刚体的Mesh Collider或是对处在Continuous或是Continuous     Dynamic状态下的刚体使用连续碰撞检测,对其他的刚体使用普通的Discrete检测。

               Continuous和Continuous Dynamic的区别在于对Continuous使用何种检 测  ,Continuous使用Discrete,而Continuous Dynamic使用Continuous。 但它们对物理性能都有很大影响。

               刚体和刚体之间的连续碰撞检测,刚体的碰撞器必须是Box,Sphere,Capusle

               刚体和非刚体(静态碰撞器)之间的连续碰撞检测,刚体的碰撞器必须是                                                 Box,Sphere,Capusle,非刚体的碰撞器必须是Mesh

Constraints:冻结,停止某个轴向感应物理引擎的效果

 Freeze Position:冻结x轴方向,y轴方向,z轴方向

Freeze Rotation:冻结x轴旋转,y轴旋转,z轴旋转

3Mesh Collider(网格碰撞体)

Convex:凸起,勾选后,与其它基本碰撞体发生碰撞

    Inflate Mesh:网格膨胀,它有效地扩展了源数据的边缘宽度和斜切锋利的边缘,使所

                            得网格适合物理更好,将此设置为true可以降低碰撞网格的精度

        Skin Width:皮肤厚度,一个合理的设定是使该值等于半径(Radius)的10%

    Is Trigger:触发器

Material:材质,引用何种物理材质决定了和其他对象如何作用

Mesh:网格,获取对象的网格并将其作为碰撞体

4Capsule Collider(胶囊碰撞体)

Edit Collider:点击之后可编辑碰撞范围

Is Trigger:触发器

Material:材质

Center:碰撞体在对象局部坐标空间中的位置

Radius:碰撞体局部坐标宽度的半径

Height:碰撞体的总高度

Direction:对象局部坐标空间中胶囊纵向方向的轴

5Camera(摄像机)

Clear Flags:背景显示内容。默认是天空盒子

                Solid Color:纯色,选择此选项显示background颜色

                 Depth Only:仅深度,用于游戏对象不希望被裁减的情况

                  Don't Clear:不清除,不清除任何颜色或深度缓存,其结果是,每一帧渲染的结果叠加在下一帧之上

Background:背景显示颜色。没有天空盒子将显示这个颜色。

Culling Mask:用于选择是否显示某些层,默认是Everything

Projection:摄像机的类型(投射方式)。

      Perspective:透视,摄像机将用透视的方式来渲染游戏对象

                 Field Of View:视野范围,用于控制摄像机的视角宽度以及纵向的角度尺寸

          Orthographic:正交,摄像机将用无透视的方式来渲染游戏对象

                   Size:大小,用于控制正交模式摄像机的视口大小

Clipping Planes:剪裁平面。摄像机的渲染范围,Near为最近的点,Far为最远的点

Viewport Rect:视图矩形。用四个数值来控制摄像机的视图绘制在屏幕的位置和大小,使用的是                         屏幕坐标系,数值在0~1之间。坐标系原点在左下角。

Depth:深度。用于控制摄像机的渲染顺序,较大值的摄像机将被渲染在较小值的摄像机之上

Rendering Path:渲染路径。用于指定摄像机的渲染方法。

              Use Graphics Settings:选择图形设置中的方法

              Forward:快速渲染,传统的渲染路径,它支持每个像素的光照及平行光(directional light)的实时阴影

               Deferred:延迟渲染,会准确如实地渲染光照和阴影。如果有许多实时光照,最适合它,需要一定程度的硬件支持,不支持移动设备

              Legacy Vertex Lit:顶点光照,摄像机将对所有的游戏对象座位顶点光照对象来渲染

               Legacy Deferred(light prepass):具有最低照明保真度和不支持实时阴影的渲染路径。它是前向渲染路径的子集。

在使用正投影时不支持延迟渲染。如果相机的投影模式被设置为正交,则这些值被重写,并且相机将总是使用快速渲染。

Target Texture:目标纹理,用于将摄像机视图输出并渲染到屏幕。

Occlusion Culling:遮挡剔除

HDR:高动态光照渲染,用于启用摄像机的高动态范围渲染功能,因为人眼对的范围的光照强度更为敏感,所有用高动态范围渲染能让场景变得更为真实,光照的变化不会显得太突兀。

Target Display:目标显示

6Directional light(平行光源)

Type:光源类型,可选择不同光源

Baking:用于烘焙模式的,在Lighting面板下点击Bake进行烘焙光照贴图时,生成对应的反射贴图

Color:光照的颜色

Intensity:光照强度

Bounce Intensity:放射光的光照强度

Shadow Type:光源投射的阴影类型

Cookie:一个遮罩,使光线在不同的地方有不同的亮度。如果灯光是聚光灯或方向光,这必须是一个2D纹理。如果灯光是一个点光源,它必须是一个立方图(Cubemap)

Cookie Size:缩放Cookie投影。只用于方向光。

Draw Halo:是否在点光源中使用白雾效果

Flare:设置光源粒子效果

Render Mode:光源渲染模式

Culling Mask:通过层可设置某些地图层不受光照影响

7Point light(点光源)

8Spotlight(手电筒)

用tree功能啊 看下面。 画树模型 Hierarchy 标签栏中,点击Create – > Tree 可以创建一个树的模型,设置自己的树木模型,这里我们先导入系统自带的树木模型,以后在讨论自定义模型的制作。 因为新建的工程中是没有树木和草地的贴图元素

SSAO的计算需要摄像机空间的深度法线和位置信息,因此我们需要提前将场景的相应属性渲染到纹理中。

CameraRenderer 中声明两个纹理:

同时声明一个shadertag,用于自定义pass:

定义 SetupDepthNormal 方法:

声明两个颜色渲染目标,并声明一个深度渲染目标。注意,滤波模式为 Point 就行了,并且不需要用MSAA。

将渲染目标存在一个 RenderTargetIdentifier 数组中。 SetRenderTarget 的一个变体就是第一个参数可容纳多个颜色缓冲,第二个参数使用1个深度缓冲。

接着定义 DrawDepthNormal 方法:

注意目前只支持不透明物体,若是有进行透明度剔除的物体可以额外进行一个pass来提取。透明物体的话,不太好进行深度的渲染,目前暂时不考虑。

在 Render 中,根据一个布尔值控制渲染到深度法线和位置纹理:

新建一个 DrawDepthNormalPasshlsl 文件。定义结构体:

注意MRT结构体,对应两个颜色缓冲渲染目标。

顶点着色器中计算观察空间的深度、法线和位置:

unity_MatrixITMV 即 inverse(transpose(model view)) ,需要在 UnityInputhlsl 中先定义好。

_ProjectionParamsw 即 1/farPlane ,这里进行这样的 *** 作是变为线性深度。

片元着色器:

注意这里将法线映射一下,免得渲染到颜色纹理时数值丢失。

我们可以查看一下深度法线纹理和位置纹理:

首先定义三个Pass,分别用于计算AO值,模糊AO以及应用AO。

计算AO值。

首先从深度法线纹理获取深度和法线,从位置纹理中获取观察空间位置:

然后采样一个随机噪声纹理,它用于随机旋转采样核心:

纹理的生成在 PostFXStack 中,我定义了一个 SetupSSAO 方法:

纹理的大小为 44 ,也就是16个像素,因此定义了一个Vector3数组,大小为16。每个像素的值填入一个随机数。

声明一个Texture2D对象,格式为 RGB24 ,用于存储噪声数组。纹理的滤波模式设为 Point 即可,包裹模式设为 Repeat ,这样噪声纹理就可以平铺在屏幕上。使用 SetPixelData 设置存储的数据,然后 Apply 应用 *** 作。我们还需要传入噪声的UV缩放值,帮助平铺噪声纹理。注意该方法调用一次即可,我们可以使用布尔值 firstInit 控制。

在 SetupSSAO 中同样生成了采样核心。采样核心是一个法向半球,内含许多采样点,这里设置为至多64个。采样核心会根据周边的深度值确定采样点是否被遮蔽,以此来确定遮蔽值。

采样核心定义在切线空间中:

用随机数填充采样点。scale值用于缩放采样点,我们使用一个加速插值让采样点更靠近采样核心。

我们使用随机噪声来构建旋转TBN矩阵:

接着我们遍历一个采样半球:

对于每个采样点,应用旋转矩阵,将其变换到法线所在的观察空间。然后将采样点乘一个半径来调整并加上观察空间位置。

接着我们将采样点变换到裁剪空间,注意自行进行透视除法,并将坐标映射到0-1,毕竟要用来采样深度纹理。 offset 的xy用于采样当前摄像机观察到的最近的片段的深度。

然后进行范围检查,保证采样深度值在采样半径内。接着如果采样深度值大于所存储的采样点中的深度值,也就是被遮住了,那么就贡献遮蔽值,注意乘上范围检查值以保证边缘不会出现遮蔽不当的问题。

最后,除以采样数,用1减去,遮蔽值越大,片段越黑。我们可以使用一个幂来控制强度。

AO图:

接着模糊一下AO图,淡化噪声的影响:

最后的pass合并:

注意,由于没有进行延迟渲染,我们无法直接用AO值去修改环境光值,因此我只好简单地根据阈值叠加,防止灰度叠加在场景中的发光区域上。

shader的定义很简单,在前面加上那三个pass就可以了。

PostFXStack 中,SSAO的渲染定义在 DoSSAO 中:

主要是三个 Draw 函数的调用,对应三个Pass。

PostFXSettings 中添加了SSAO可调节的几个属性,采样核心数量,半径,以及强度:

目前的SSAO效果不是很理想,毕竟那张AO图目前我只是单纯地拿来修改一下场景的灰度。有无AO的对比(未开启发光):

可以看到这么做的话就直接亮了。效果也不是不行,只不过需要好好去调整一下。加上发光的最终效果:

项目地址: >

以上就是关于入门必看:Unity资源加载及管理全部的内容,包括:入门必看:Unity资源加载及管理、unity无法从脚本访问纹理内存、Unity常用组件参数详解等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9580365.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-29
下一篇2023-04-29

发表评论

登录后才能评论

评论列表(0条)

    保存