Unity3D

Unity3D的SRP,Static Batching、Dynamic Batching和GPU instancing

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 所谓的draw call batching(绘制批处理),就是指对将要绘制的N个网格,在保持渲染结果正确的前提下,将draw call进行合并,以一整批,而不是单个的方式,向GPU提交这些draw call操作。 绘制批处理有若干种实现方式,有些方式只是减少了提交draw call的次数,但执行draw call的次数没变少。有些方式则是直接减少了draw call的数量。 在《Unity DrawCall、Batch、SetPassCall的区别》一文中对Batching的解释很到位,摘录如下: Batch 把数据加载到显存,设置渲染状态,CPU调用GPU渲染的过程称之为一个Batch。这其实就是渲染流程的运用阶段,最终输出一个渲染图元(点、线、面等),再传递给GPU进行几何阶段和光栅化阶段的渲染显示。一个Batch必然会触发一次或多次DrawCall,且包含了该对象的所有的网格和顶点数据以及材质信息。把数据加载到显存是指把渲染所需的数据从硬盘加载到内存(RAM),再将网格和纹理等加载到显卡(VRAM),这一步比较耗时。设置渲染状态就是设置场景中的网格的顶点(Vertex)/片元(Fragment)着色器,光源属性,材质等。Unity提供的动态合批(Dynamic Batching )合并的就是这一过程,将渲染状态相同的对象合并成一个Batch,减少DrawCall。 还有对SetPassCall的解释也很好,摘录如下: SetPassCall Shader脚本中一个Pass语义块就是一个完整的渲染流程,一个着色器可以包含多个Pass语义块,每当GPU运行一个Pass之前,就会产生一个SetPassCall,所以可以理解为一次完整的渲染流程次数。 由此可见,一个Batch包含一个或多个DrawCall,都是产生是在CPU阶段,而目前普遍渲染的瓶颈恰恰就是CPU,GPU的处理速度比CPU快多了,Draw Call太高,CPU会把大量时间花费在处理Draw Call调用上。如果Batch太大,CPU需要频繁的从硬盘加载数据,切换渲染状态,这个消耗要比DrawCall大,所以后面Unity才逐渐弱化了DrawCall的显示。 再提一下,优化的时候还要关注下Statistics窗口上的三角形数(Tris)和顶点数(Verts),这两个数据也是会影响到性能,比如单个物体的顶点数最好不要超过900,不然会影响到Unity的动态合批。Unity的Statistics窗口上的三角形数(Tris)和顶点数(Verts)并不仅仅是视锥中的梯形内的三角形数和顶点数,而是Camera中 field of view所有取值下的三角形数和顶点数。也就是说,即使当前Game视图中看不到这个 cube,只有 field of view在1-179 范围内都看不到这个cube,stats面板才不会统计,GPU才不会渲染,否则都会渲染,而且Unity不会把模型拆分,这个模型哪怕只有1个顶点需要渲染,Unity也会把整个模型都渲出来。 四种优化方式的适用场合 优化方式的优先级 一些细节笔记 参考网页 Unity DrawCall、Batch、SetPassCall的区别 浅谈Draw Call和Batch的区别 Unity渲染优化的4种批处理:静态批处理,动态批处理,SRP Batcher 与 GPU Instancing Draw call batching 为什么baches(draw calls)数那么高? 它们有什么意义? 请教Unity中的Draw Call Batch规则

批量设置FBX文件导入模型切线方式

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 原理 利用AssetDatabase.FindAssets("t:Model")找出所有的FBX模型的GUID。再利用AssetDatabase.GUIDToAssetPath将GUID转为文件路径。然后使用 AssetImporter.GetAtPath函数拿到 模型导入器(ModelImporter) 对象。模型导入期对象里面就有一个importTangents属性项,当此项为None时表示没切线,其他选项都表示带有切线信息。下面是查找所有带有切线信息的模型的函数。 /// <summary> /// 查找出带有切线信息的模型 /// </summary> /// List<string> mikkTSpace 使用Morten Mikkelsen切线空间(MikkTSpace)算法计算生成切线的模型文件名 /// List<string> calculateLegacy 使用传统的算法计算生成切线的模型文件名 /// List<string> calculateLegacyWithSplitTangents,使用传统的算法,并对UV图表进行拆分的方式,计算生成切线的模型文件名 /// List<string> imported 从原始文件中导入切线的模型文件名 private void FindAllModelsContainTangent(List<string> mikkTSpace,List<string> calculateLegacy, List<string> calculateLegacyWithSplitTangents,List<string> imported) { // 使用了库函数,用"t:Model"参数检索所有的模型文件。即FBX文件 // 返回的是所有fbx的GUID string[] allModels = AssetDatabase.FindAssets("t:Model"); for (int i = 0; i < allModels.Length; i++) { // 把GUID换成资源文件路径名,然后使用AssetImporter模块获取到模型导入器 string modelPath = AssetDatabase.GUIDToAssetPath(allModels[i]); var modelImporter = AssetImporter.GetAtPath(modelPath) as ModelImporter; if (modelImporter == null) continue; switch (modelImporter.

UGUI优化

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 影响性能的要素 像素填充率(FillRate) 概念:指每秒每秒显卡能填充的像素数 填充率和over draw息息相关,over draw由GPU影响 影响像素填充率的原因 半透明物体过多 绘制半透明物体需要: 1 开启ALPHA_BLEND 2 关闭Z TEST和Z WRITE 同一像素的over draw过高, 即重复绘制次数过多 优化方法 对于Alpha值为0,但是又要响应输入的不可见物体,采用 无绘制但能响应RayCast的组件来处理 减少填充区域的覆盖,比如一个矩形区域,它的周围空白不 可见区域比较多,那可以考虑把矩形转化为若干个三角形 Draw call draw call受CPU影响 UGUI引入了canvas的概念,提供给相同材质的 界面组件进行合批,减少draw call 优化方法 使用同一组材质球的UI组件,放置在同一个canvas下 动静分离:canvas可以分组,如果某个界面,存在着会动 的元素和不会动的元素,则考虑分成两组。但canvas如果 先SetActive(false),再SetActive(true)。则需要ForceUpdate 它强制重绘 绘制顺序不要穿插,比如A组件用了A材质球,B组件用了B材 质球,C组件用了A材质球,如果不存在相互遮盖关系的话, 那么应该在scene hierachy窗口中,将三个组件按上下顺序 排序为A,C,B 其他优化策略汇总 少用Mask组件 原理:Mask组件会额外消耗多一个Drawcall来创建遮罩做像素剔除。 Mask组件不利于层级合并。原本同一图集里的ui可以合并层级, 仅需一个Drawcall渲染,如果加入Mask,就会将一个ui整体分割 成了Mask下的子ui与其他ui,两者只能各自进行层级合并,至少 要两个Drawcall。 字体 尽量减少字体特效,outline,渐变等,可以使用本身带有描边的字体文件 ,尽量统一字体风格,减少不必要的额外字体文件,不用richtext 界面切换 原理:增加一个新层,并且将Camera的Culling Mask中取消勾选, 即不渲染该层,UI界面在切换时,将其Canvas所在Layer设置为该 层,看起来就像被隐藏了 优点:于切换时基本没有开销,也不会产生多余的 Draw Call 缺点:隐藏时依然还会有一定的持续开销,而其对应的Mesh 也会始终存在于内存中。 各种界面隐藏方案的优劣分析 SetActive产生GC,会导致画布丢弃它的VBO数据,从而重建和重新批处理,操作频繁时GC导致CPU占用可能会卡顿。 enable Canvas禁用画布,能降低dc,避免SetActive的GC开销,但测试依旧会有SendWillRenderCanvases函数的调用开销。 移出RootCanvas,似乎没有多大优化,理论上会降低dc且不会引起重建。 将单独界面使用Canvas,绑定一个Camera在隐藏时设置Layer进行CullingMask蔽,同时禁用GraphicsRaycaster,能避免重建,但随着canvas增多dc会增多,同时界面较时管理比较费力,注意动态UI的事件。 设置Canvas Group 的alpha为0:依旧会被渲染,但能避免setactive带来的巨大开销,且容易操作。 更改Scale的值为0:点信息被清除,这样不会减少dc开销,同时还会引起重建,不采用。 设置一个Canvas作为根节点,将其layer设置为相机屏蔽的层级,同时禁用GraphicsRaycaster,当需要隐藏某个界面时将其以该Canvas作为根节点移到其下,这样能避免重建同时减少dc,但会有一些SetParent带来的消耗。目前采用该方案。 如果只是隐藏某个Graphic类的,可以考虑获取canvasRenderer.

杂谈Unity3D的single channel texture

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 经实测,在Unity3D 2020.2.6f1版本中,在导入纹理的钩子代码中,如果设置TextureImporter.textureType = TextureImporterType.SingleChannel,以及TextureImporterPlatformSettings.format = TextureImporterFormat.R8是一张2048的贴图,导入后其格式 根据Unity的纹理格式文档描述: 当TextureImporter.textureType为TextureImporterType.SingleChannel时,除了R8之外,可选的格式有: 据UWA的人说,R Compressed 4 bit应该是但在本版本的Unity中,其对应的TextureImporterFormat.COM枚举值应该是EAC_R。 根据Unity的纹理格式文档描述: R Compressed EAC 4 bit是一个High-quality compressed R (single-channel) 纹理。一个256x256的纹理,在内存中其大小为32KB。在Android平台下,OpenGLES2.0版本是不支持该格式的,如果指定了该格式,则会自动解压为 【ETC2 fallback】 标签项所指定的纹理格式。 考虑到现在Android手机早就已经是支持OpenGLES3了,所以大胆使用此格式,作为单通道贴图的导入格式。 参考网页 best-texture-format-for-a-single-channel Crunched single channel texture an-introduction-to-texture-compression-in-unity Unity Editor下判断图片是否带alpha通道 Unity 中纹理压缩的方式 – 搁浅の砖厂 Recommended, default, and supported texture formats, by platform C# (CSharp) UnityEditor TextureImporterSettings示例 https://huailiang.github.io/blog/2022/astc/

Animancer的Transition参数手册(翻译)

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 原文地址 Transitions 与其硬编码淡入淡出的持续时间(hard-coding the fade durations),开始和结束时间,动画剪辑的播放速度和动画事件,不如使用transition机制。此机制可以在编辑模式下的可视化地编辑它们。由非程序员在Inspector面板中定义这些细节,无需编辑和重新编译任何脚本。 Animancer中包含的每个State类都有其自己的Transition Type,其中包含与之相关的各种详细信息,以便在将其传递给AnimancerComponent.Play(ITransition)时,这些详细信息可用来创建该State类型。例如,当播放一个AnimationClip对象(即一个动画片段剪辑)时,一个ClipState.Transition将创建一个ClipState实例对象。如下代码所示: using Animancer; using UnityEngine; public class TransitionExample : MonoBehaviour { [SerializeField] private AnimancerComponent _Animancer; [SerializeField] private ClipState.Transition _Animation; private void OnEnable() { _Animancer.Play(_Animation); } } 对应的inspector显示如下图: 这个示例详细演示如何使用transitions。 Fields 字段名字 代码值 inspector截图 Animation Clip Fade Duration FadeDuration Speed Speed Start Time NormalizedStartTime End Time Events.NormalizedEndTime Events Events 各个属性的说明如下表 字段名字 说明 Animation 要播放的那个animation clip Fade Duration 从上一个动画淡入淡出到新动画所需的时间。此值不能为负,并且将其设置为0 sill会使动画立即播放。不管使用哪个字段输入值, 此时间字段始终被序列化为秒 Speed 动画以其正常速度的倍数播放的速度。负值会使它向后播放(因此可能希望将Start Time设置为1x,以便从末尾开始向后播放 Start Time 如果启用,播放时动画时刻将立即跳到该值。否则,它将显示默认值:0x(正速度)或1x(负速度)。如果动画的AnimancerNode.

Animancer的States(翻译)

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 原文地址 State 当播放一段动画剪辑(animation clip)时,Animancer会创建一个ClipState来管理它并跟踪其进度。ClipState是最常见的一种AnimancerState,除此之外,有其他类型的AnimancerState,例如Controller States和 Mixer States。这些用来在一个state中管理多个动画剪辑。 执行AnimancerComponent.Play函数播放一个animation clip,就返回一个AnimancerState对象,如下代码所示: void PlayAnimation(AnimancerComponent animancer, AnimationClip clip) { // Play the animation and control its state: var state = animancer.Play(clip); state.Time = ... state.NormalizedTime = ... state.Speed = ... state.OnEnd = ... } 也可以自己访问和创建状态,而无需立即播放它们: code description var state = animancer.States.GetOrCreate(clip); 访问状态而不播放状态(该Play方法在内部使用此状态)。 var state = animancer.States[clip]; 获取状态(如果存在)(否则为null)。 var state = animancer.States.Create(key, clip); 即使该动画已经存在,也要创建一个新状态。注意必须为每个状态提供不同的key。 var state = new ClipState(animancer, clip); 创建一个新状态而不给它一个Key。可以使用其AnimancerState.Key属性分配一个。 Keys 当用AnimancerComponent.Play方法创建一个state时,这个state存储在一个内部的Dictionary容器中,通过指定某个key,用以在以后用这个key来检索使用该state。 默认地,该state所播放的那段动画剪辑所对应的AnimationClip实例对象,将作为该state的key值 NameAnimancerComponent将重载其GetKey方法,NameAnimancerComponent类不再使用它播放的AnimationClip对象,而是使用这个动画剪辑的名字字符串去作为key。但在启动时,可以在inspector面板中,在Animation中预先注册好这个 void PlayAnimation(AnimancerComponent animancer, AnimationClip clip) { // Trying to play an animation before registering it does nothing.

Unity3D的Animator中的animation transition参数细节

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 选项 描述 Has Exit Time 如果设置为true,则仅在指定的动画剪辑已经播放了给定百分比之后,才启用动画剪辑过渡(transition)。 如果禁用,则将在满足其过渡条件后,立即进行切换。 如果要创建由若干个动画剪辑组成的序列,请将其设置为true。 Exit Time 这是满足“Has Exit Time”选项勾选后,指定的播放动画长度的百分比(0-1范围)。 例如,如果将其设置为0.5,则在播放了50%的动画剪辑后,当满足转换条件后,动画的状态将转换。 Fixed Duration 这个属性将和下一个属性“ Transition Duration (s)”结合使用。如果本选项启用,的财产与下一个过渡期间结合在一起。 如果启用,则Transition Duration (s) 项以秒为单位;如果关闭,则以动画播放时间的百分比表示。 Transition Duration (s) 动画状态过渡所需的时间。 这是从一个动画剪辑过渡到另一个所用的时间。 在过渡期间动画剪辑会被混合在一起。 过渡时间越长,会让动画剪辑转换得更平滑些,否则就转换得更锐利。一个好的默认值是大约0.25。 Transition Offset 选项会把待处理的目标动画开始时刻做偏移。该值以动画剪辑的播放百分比表示。 比如将其设置为0.3。意思是说,当从动画剪辑A过渡到动画剪辑B时,那么A过渡到B之后,会从B的30%处开始播放,而不会从B的开始刻度处开始播放。 Interruption Source 此设置告诉Unity某个动画剪辑过渡。是否可以被其他过渡打断。 例如动画系统下有一个默认的Idle动画,Idle动画有Jump动画分支和Walk动画分支,而Walk动画分支下又有一个Jump1动画分支,当我们同时满足两个Jump动画的切换条件时,Interruption Source就可以判断优先从哪个动画源切换到Jump或Jump1。Interruption Source中四个选项如下表中的几个选项的一个 Interruption Source的各个选项表 选项 描述 None 关闭Interruption Source选项 Current State: 从当前源打断 Next State 从下一个源打断 Current State then Next State 优先从当前源打断,如果当前源不能切换到下一个状态则从下一个源打断。 Next State then Current State 和上一个选项相反 Ordered Interruption If set to false, this lets the transition be interrupted by other transitions independently of their order.

Unity3D的Color Space细节解析

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 参考网页 https://zhuanlan.zhihu.com/p/36507196 https://zhuanlan.zhihu.com/p/93216787 https://blog.csdn.net/candycat1992/article/details/46228771 https://developer.nvidia.com/gpugems/GPUGems3 http://www.klayge.org/2011/02/26/gamma%E7%9A%84%E4%BC%A0%E8%AF%B4/ https://zhuanlan.zhihu.com/p/36507196 https://www.kinematicsoup.com/news/2016/6/15/gamma-and-linear-space-what-they-are-how-they-differ https://zhuanlan.zhihu.com/p/66558476 https://www.cambridgeincolour.com/tutorials/gamma-correction.htm https://zhuanlan.zhihu.com/p/37679604 https://zhuanlan.zhihu.com/p/271011254

Unity3D动画中Root Motion的概念和使用

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com Unity3D的Mecanim动画系统可以直接复用3DS MAX中制作的动画文件中的位移,这个就是通过applyRootMotion选项来达成的,我们只需要在使用Animator控制动画播放的同时,设置Animator组件的applyRootMotion字段为true就可以了。 applyRootMotion,从字面上理解来看,是“应用于根节点的运动”,官方文档的解释如下: The Root Transform is a projection on the Y plane of the Body Transform and is computed at runtime. At every frame, a change in the Root Transform is computed. This change in transform is then applied to the Game Object to make it move. 直接翻译的意思是: root transform是指body transform在在Y平面上的投影(即Y=0的平面,可以理解为模型所站立的“地面”。且这个投影是在运行时执行计算。在每一帧,root transform的每一个变化都会被计算。然后这些变化将会作用到game object上,使得它发生运动 也就是说,root motion就是指:在动画中物体产生的位移,可以在运行时,让绑定了Animator组件的game object,也发生实际的位移。且这个位移,是根据播放动画中每一帧物体的位移,在 X 和 Z 轴上投影计算而得。 对于某些技能动画,整个动画是有一定位移的,但是动画的位移是动作设计师在设计时根据动作需要调出来的,位移是跟动作的幅度直接相关和匹配的。那么在释放技能的时候就只需要直接播放动画,只要应用这个 Root Motion 的特性,就可以很好的完成角色在播放动作的同时进行移动,动作播放完毕之后就在动画结束帧角色所在的位置。而不要额外地做计算工作。接下来看下animation clip inspector面板上若干和root motion有关的属性选项 animation clip inspector 界面上的一些属性介绍 以下是Unity 2019.

Unity3D Shader的内建multi_compile开关所涵盖的多样体

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com multi_compile_fwdbase 在Unity3D 2018.4.18f1版本下实测到,如果不做其他特别的指定,一个multi_compile_fwdbase编译指令,包含了以下 9 个内建的keyword: DIRECTIONAL DIRLIGHTMAP_COMBINED DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING LIGHTPROBE_SH SHADOWS_SCREEN SHADOWS_SHADOWMASK VERTEXLIGHT_ON 如果不做特别的指定的话,默认地启用了其中的 4 个内建keyword,分别是:DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN VERTEXLIGHT_ON组成了 8 个多样体,如下: 多样体(变体) DIRECTIONAL DIRECTIONAL LIGHTPROBE_SH DIRECTIONAL SHADOWS_SCREEN DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN DIRECTIONAL VERTEXLIGHT_ON DIRECTIONAL LIGHTPROBE_SH VERTEXLIGHT_ON DIRECTIONAL SHADOWS_SCREEN VERTEXLIGHT_ON DIRECTIONAL LIGHTPROBE_SH SHADOWS_SCREEN VERTEXLIGHT_ON multi_compile_fwdadd 在Unity3D 2018.4.18f1版本下实测到,如果不做其他特别的指定,一个multi_compile_fwdadd编译指令,包含了以下 5 个内建的keyword: DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SPOT 如果不做特别的指定的话,默认地启用了全部的 5 个内建keyword,组成了 8 个多样体,如下: keyword组成的多样体(变体) DIRECTIONAL DIRECTIONAL_COOKIE POINT POINT_COOKIE SPOT multi_compile_fwdadd_fullshadows 在Unity3D 2018.4.18f1版本下实测到,如果不做其他特别的指定,一个multi_compile_fwdadd_fullshadows编译指令,包含了以下 5 个内建的keyword: