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 第2章

Understanding C# async / await Understanding C# async / await 第1章原文地址 Understanding C# async / await 第2章原文地址 Understanding C# async / await 第3章原文地址 2 The Awaitable-Awaiter Pattern 2.1 What is awaitable 第1部分显示了所有Task类都是awaitable的。实际上,还有其他awaitable类型。如下所示: Task<int> task = new Task<int>(() => 0); int result = await task.ConfigureAwait(false); // Returns a ConfiguredTaskAwaitable<TResult>. The returned ConfiguredTaskAwaitable<TResult> struct is awaitable. And it is not Task at all: public struct ConfiguredTaskAwaitable<TResult> { private readonly ConfiguredTaskAwaiter m_configuredTaskAwaiter; internal ConfiguredTaskAwaitable(Task<TResult> task, bool continueOnCapturedContext) { this.

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.

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.

移植代码到64位平台

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com C和C++大量依赖于指针的使用,即包含内存地址的变量。尽管指针类型在概念上与整数类型不同,但其物理地址值 始终是一个整数 。开发人员在C/C ++中大量使用整数类型而不是指针类型,即使在编译器随附的系统头文件中也是如此。在过去的几十年中,大多数计算机处理器都使用32位内存地址,因此开发环境是 围绕地址长度为32位的假设 建立的。C/C++代码中存在着大量 指针和整数之间的隐式和显式转换 。使用指针,或union的重叠内存结构方式(overlapping memory structures),去访问变量,也是司空见惯。 当然,32位架构有其局限性。具体来说,它们将计算机内存限制为4GB。应用程序需求不断增长,迫切需要迁移到更长的地址。经常使用的旧式类型带来了新的含义和价值。最具戏剧性,最明显的的变化,是从32位转向64位指针。 不再适用相同的规则 基本类型的某些大小已更改。其中一些保持不变。但是,突然之间,在C和C++代码库中使用的许多隐式假设不再有效。在32位系统中编写和测试的代码在迁移到64位计算机时不再有效。 幸运的是,已经识别并分类了代码可能易于从32位计算机迁移到64位计算机的大多数区域。一些编译器使用主动检查技术来捕获在迁移期间可能出现的漏洞。静态分析工具的供应商(包括Coverity和Klocwork)可以提供进一步帮助将代码从32位机器平稳迁移到64位机器的机制。尽管这些工具不能直接解决代码迁移问题,但它们提供了便利的可扩展性和API,可以创建与32位到64位迁移相关的自定义检查程序。增强这些工具可以进一步帮助发现并有效解决这些兼容性问题。 本文的目的是帮助您使代码库与体系结构无关,因此可以在32位计算机或64位计算机上构建相同的代码库,并为每台计算机生成可行的代码。此外,如果32位程序通过二进制数据交换(通过文件或套接字)与64位程序进行通信,则有多种方法可以确保二进制结构不受两种体系结构类型之间的迁移的影响。 本文包含详细的分析和建议,以解决与32位到64位迁移有关的众多问题。 为了使您的程序与体系结构无关,有两个目标需要实现。 其中之一是内部一致性,即确保程序中使用的所有类型都是一致的,而不管代码是在32位还是64位计算机上编译的。 让我们看一下两种架构上基本类型的大小(请参见表1)。 此处的显着区别是指针类型和long类型的大小变化。由于大小更改,某些基于隐式依赖关系构建的代码可能无法正确迁移。可能的问题主要与整数与整数之间,或者是整数和指针之间的数据交换有关。 整数大小的差异 在32位系统上,int和long类型都是32位,这意味着程序员可以互换使用这两种类型。此外,C没有严格的输入规则,因此函数中的int参数可以传递long参数而不会出错。尽管这不是一个好的编码实践,但是此代码可能会在数年内完美运行。但是,在64位系统中,在某些编译器中,int和long具有不同的大小,并且代码在迁移后将无法正常工作。将long值分配给int变量可能会导致截断。以下代码在32位系统中可以正常工作,但在64位系统中则极有可能正常工作。 int sum; long val1, val2; sum = val1 + val2; // possible truncation in 64-bit system 以下代码可能会在某些编译器中引起编译器警告,但是此代码(在32位模式下工作)可能将无法在64位模式下工作,因为堆栈上的值大小会发生变化。 int add(int parm1, int parm2); // ..... long val1, val2; long sum = add(val1,val2); // mismatch of parm size on 64-bit machine 可能还会发生其他一些细微的事情,从而影响代码的完整性。例如,编译时函数sizeof()返回类型为size_t的结果,size_t类型的占据的字节大小,则取决于该程序中指针类型变量占据的字节数的大小。将此值分配给整数可能导致被截断。如下代码所示: int mySize = sizeof(MyStructure); // possible size truncation 另一个更细微的错误是位字段的符号扩展。 例如: