Posts

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

Understanding C# async / await 第3章

Understanding C# async / await 第1章原文地址 Understanding C# async / await 第2章原文地址 Understanding C# async / await 第3章原文地址 以下是基于你提供的内容的总结和解读,保留示例代码和关键细节: 理解 C# 的 async / await (3):运行时上下文 1. 概述 在前两篇文章中,我们探讨了 async 和 await 的编译过程和 Awaitable-Awaiter 模式。本文将深入探讨 await 在运行时的上下文处理机制,特别是如何通过 ExecutionContext 和 SynchronizationContext 解决跨线程问题。 2. 跨线程问题 在 WPF 应用程序中,以下代码可以正常工作: this.Button.Click += async (sender, e) => { string html = await new WebClient().DownloadStringTaskAsync("https://weblogs.asp.net/dixin"); this.TextBox.Text = html; }; 但如果将其改写为 Task.ContinueWith() 的回调形式: this.Button.Click += (sender, e) => { new WebClient().

Understanding C# async / await 第2章

Understanding C# async / await 第1章原文地址 Understanding C# async / await 第2章原文地址 Understanding C# async / await 第3章原文地址 理解 C# 的 async / await (2):Awaitable-Awaiter 模式 1. 什么是 Awaitable? 在 C# 中,任何具有 GetAwaiter() 方法的对象都是 awaitable 的。Task 是最常见的 awaitable 类型,但其他类型也可以实现这一模式。例如: Task<int> task = new Task<int>(() => 0); int result = await task.ConfigureAwait(false); // 返回 ConfiguredTaskAwaitable<TResult> ConfiguredTaskAwaitable<TResult> 是一个 awaitable 类型,但它并不是 Task。 2. Awaitable-Awaiter 模式 通过观察不同的 awaitable / awaiter 类型,可以总结出以下规则: Awaitable 对象必须有一个 GetAwaiter() 方法(实例方法或扩展方法)。 Awaiter 对象必须满足以下条件: 实现 INotifyCompletion 或 ICriticalNotifyCompletion 接口。 有一个 IsCompleted 属性,返回布尔值。 有一个 GetResult() 方法,返回 void 或一个结果。 这种模式与 可迭代-迭代器模式(IEnumerable / IEnumerator)非常相似。

Understanding C# async / await 第1章

Understanding C# async / await 第1章原文地址 Understanding C# async / await 第2章原文地址 Understanding C# async / await 第3章原文地址 以下是基于你提供的内容的总结和解读,保留示例代码和关键细节: 理解 C# 的 async / await (1):编译过程 1. 概述 C# 的 async 和 await 关键字为异步编程提供了极大的便利。本文将深入探讨这些语法糖背后的实际代码实现。 2. 准备工作 首先,定义一些辅助方法: internal class HelperMethods { private static void IO() { using (WebClient client = new WebClient()) { Enumerable.Repeat("http://weblogs.asp.net/dixin", 10).Select(client.DownloadString).ToArray(); } } internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // 模拟长时间运行的 IO 操作 return result; } internal static Task<int> MethodTask(int arg0, int arg1) { Task<int> task = new Task<int>(() => Method(arg0, arg1)); task.

lua优化经验总结

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 这是我在做一些手游项目时的lua优化经验总结。 注意清空table操作的效率问题 在Lua中,通常要清空一个table,很常见的方法就是直接赋值一个新的空table给指向table的变量:例如: local t = {“I am a table”} t = {} –- 现在t指向的table就是一个空的新建的table了。 这样子虽然逻辑上没什么问题,但t原来指向的那个table实质上就是一个“野table”了,它的何时被清理掉依赖于lua的gc操作。如果t本身就是存储着一些原生数字类型的数组的话,这种={}的清空操作是比较低效的。如果在每帧每秒都被调用的函数中,清空一个既有的table,可以采用以下的代码: -- [[这样子才是全部清空一个table中的所有项,如果将pairs函数改用ipairs函数则只会清空数组项,而不清空hash项]] for i, _ in pairs(t) do -- 不需要kv对中的value值,所以用哑元_代替 t[i] = nil end 利用逻辑判断的短路判定特性优化代码 对于逻辑或(or)操作,只要有一个判断条件为true,则结果为true。逻辑与操作,只要有一个判断条件为false,结果即为false,根据这个特性,在多条件判断时,将获取条件耗费性能最大的函数,挪到最后,尽可能地减少计算。例如: 逻辑与(and)操作,只要有一个判断条件为false,结果即为false。所以对于全逻辑与的操作,在计算判断条件时,应该每计算一次就判断一次,只要有一个为false,就立即得到判定逻辑与计算最终结果为false: 字符串常见问题 和Java,C#,Python等语言类似,一个字符串(对象)对应着一个常量字符串,一旦该字符串声明完毕,其指向的字符串内容即不可更改,所以如果有以下的代码: local s1 = “I” local s2 = “ am” local s3 = “ Lua” local s = s1..s2..s3 在第一个连字符操作完成后,会生成一个匿名的常量字符串,尔后该匿名字符串再和s3进行拼接,再生成一个新的字符串赋值给变量s。也就是说,Lua中的所有字符串,都不会发生“in place”操作,一切会对本字符串的内容发生了修改的操作,必然会导致一个新的字符串的产生。如果是存在着较多的字符串连接操作的话,可以使用table来模拟C#的StringBuilder,如下: -- table.concat函数所处理的表中,表项值只接受字符串和数字 function concat_string(s1,s2,s3,s4,s5,s6) -- 如果预先知道了要拼接多少个字符串,在定义table时可以预先 -- 开好多少个空位而不是直接创建空表,这样子有利于性能提升 local t = {nil,nil,nil,nil,nil} t.name = “I am table t” -- table.