UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技 - 博物纳新
type: channel
link: https://blog.uwa4d.com/category/博物纳新
items: DMotion - 基于DOTS的动画框架和状态机
items: Magica Cloth
items: URP下的OffScreen Particle Render
items: 雪地脚印 & 体积云
items: HDRP Water & 云影
items: GeoAO：一种快速的环境光遮蔽方案
items: 网格切割算法
items: Lua Profiler——快速定位Lua性能问题
items: 基于DOTS的UI解决方案
items: SpeedTree Shader开源库测评

DMotion - 基于DOTS的动画框架和状态机
type: item
date: 2023-02-22T03:43:00+02:00
description: 【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 今天推荐的项目来自UWA开源库： https://lab.uwa4d.com/Lab/62bbf82fa8103dabd0f87c8b 一、简介 自从Unity推出了DOTS（Data-Oriented Technology Stack，面向数据的技术堆栈）之后，关于其应用一直备受关注。虽然DOTS对性能的提升比较明显，但是由于其不算低的门槛，仍然让许多开发者难以上手。以动画为例，截至目前的Entities 1.0版本仍然没有官方的基于Entities的动画解决方案。本文介绍的插件DMotion就提供了基于DOTS的动画框架和状态机，可以帮助开发者更便捷地使用DOTS制作动画。 二、功能概览 DMotion的基础是另一个开源项目Latios-Framework的Kinemation部分。这个项目使用DOTS建立了一些常用模块的框架，包括物理、音频、动画等等，目前仍在频繁更新中，对于希望学习和使用DOTS的开发者也可以作为参考，链接如下： https://lab.uwa4d.com/Lab/5dffdd368bab6aaf026a65c3 由于Latios-Framework使用了另一种动画压缩方案，即使用了插件Animation Compression Library，这个插件虽然理论上可以用于移动端，但是目前还没有进行相应的适配，因此DMotion目前只能在PC平台上使用。作者提到之后会考虑扩展到移动端上。 目前DMotion的主要功能如下： Current Features (V0.3.4)： Fully Bursted Runtime State Machine Visual Editor Transitions: Boolean, Int, Enum And End Time Simple API For Playing Clips Through Code (See Samples) 1D Blend Tree Animation Events Root Motion (With Writegroup Support, If You Need To Override Default Behaviour) Object Attachment Support For Optimized And Non-Optimized Skeletons State Machine Visual Debugging 之后可能会更新的功能： Planned Features： 2D Blend Tree (Cartesian/Freeform) State Machine Override (A.K.A Animator Override Controller) Substates IK Support Multiple Layers Skeleton Masks 三、使用 Latios-Framework最新的版本（目前为0.6.4）已经支持Entities 1.0，但是DMotion还只支持Entities 0.51版本，因此安装DMotion的Package之后需要安装Latios-Framework的旧版本，建议使用0.5.8版本。 DMotion这个插件的使用整体上是比较简单的，可视化程度高，作者提供了Sample和详细的指导，需要使用代码的地方都有例子可以参考。文档链接如下： https://github.com/gamedev-pro/dmotion/wiki/1.1-Getting-Started:-The-Basics 这里只做一些简单的介绍，比如最简单地播放一个Animation Clip。 首先需要创建一个DMotion的Clip。 然后在Inspector界面中设置好要用到的Animation Clip。 再给需要播放动画的对象添加对应的组件，在组件中做一些设置，添加需要使用的Clip，就可以播放动画了。 点击Play即可播放动画。 动画状态机部分可视化程度很高，和Unity本身的动画状态机的使用方法很接近。 首先创建一个动画状态机。 创建一个新的状态。 添加对应的Animation Clip。 两个动画状态之间可以建立Transition。 Transition可以设置一些参数和条件。 这些内容都可以在文档中找到，因此不再赘述。 四、实现原理 DMotion是一个基于DOTS的框架，这里我们从播放动画的代码开始分析。 继承IConvertGameObjectToEntity是…

Magica Cloth
type: item
date: 2022-11-08T04:24:00+02:00
description: Magica Cloth当前在物理模拟的效果上相比Dynamic Bone插件更加强大，但是随之而来的性能消耗也不容忽视。本文旨在说明通过简单修改Magica Cloth插件中的部分设置项以及调整一些参数，就能够在它的表现效果和性能之间达到一个平衡。 一、Magica Cloth简介 Magica Cloth是一款用于Unity引擎中布料模拟的插件，提供了类似于Unity的Cloth组件的功能。由于效果逼真的布料模拟运算量巨大，耗时较长，每一帧可能需要几十秒的时间来进行模拟；而在游戏中实现的布料模拟因为需要满足帧率的要求，要在几毫秒的时间内进行物理解算才不至于影响其他系统。因此，游戏中的布料模拟往往是极度简化的，以满足实时性的要求。 图片来源Magica Cloth – Magica Soft 二、Magica Cloth使用 关于Magica Cloth的使用，可以参考官方文档，这里就不再赘述。 https://magicasoft.jp/en/magica-cloth-2 三、Magica Cloth的具体效果 Magica Cloth能够让角色的衣服、头发等受到重力、风等外部作用力的影响，表现出比较自然的运动；能够让衣服、头发在角色运动的时候随着角色的动作比较真实地改变自己的位置和形状。 Magica Cloth提供了四种基本的物理组件：Bone Cloth，Bone Spring，Mesh Cloth以及Mesh Spring。 Bone Cloth可以用来模拟有骨骼的头发的晃动，负载比较轻。 Bone Spring可以用来模拟有骨骼的胸部的简谐运动，负载最轻。 Mesh Cloth基于所选的网格的顶点来进行布料模拟，效果最好但是负载最重。 Mesh Spring可以用来模拟没有骨骼的胸部的简谐运动，负载中等。 和Dynamic Bone相比，Magica Cloth无疑功能更加强大，但是如果只是需要移动角色骨骼来实现物理模拟，Dynamic Bone也能做到比较好的结果并且性能更优。 同时，Magica Cloth相比其他物理插件功能更强大的地方在于它有相对而言比较容易调试的防穿模手段。 Magica Cloth还提供了换装系统，可以在运行时更换角色身上的服装，但是这些服装都是要在离线时做好预设，不能够在运行时修改。 四、Magica Cloth的实现原理 首先，它不依赖于Unity的物理系统，所以Unity自带的物理相关的设置不会影响Magica Cloth的表现。Magica Cloth通过Magica Physics Manager从而在运行时控制物理模拟的全部流程，所以需要在场景中存在这个Manager的时候物理模拟才会生效。 其次，它的实时运算使用了Unity的Job System和Burst Compile，能够把一些复杂的布料模拟的计算转移到工作线程中，降低了主线程的CPU占用。 当我们要使用Mesh Cloth或者Mesh Spring的时候，就需要添加额外的Render Deformer和Virtual Deformer。Render Deformer对模型的Mesh进行变形，而Virtual Deformer则是把所有的Render Deformer整合成一个虚拟Mesh并进行简化，也就是合并顶点和三角形，减少顶点和三角形的数量，降低数据量，然后把这个虚拟的Mesh对应的简化结果分配给各个Render Deformer用来进行Mesh的控制和变形。 为了能够尽可能地解决穿模问题，Magica Cloth可以针对性地设置Surface Penetration和Collider Penetration。其中，Surface Penetration在身体骨骼对裙摆等布料的运动产生影响时会比较有效，而当身体骨骼不会影响服饰的运动时，就需要使用Collider Penetration来手动设置碰撞体。Magica Cloth提供了三种Collider：Sphere、Capsule和Plane。因为主要是用于人物角色的Collider，所以常用的是Sphere和Capsule。对于部分布料模拟中分离的骨骼也可以通过设置Connection Mode进行合并，来防止运动时附加了碰撞体的躯干、大腿等从布料模拟骨骼的间隙中穿出去。 同时，除了防穿模设置外，也可以通过限制骨骼的最大位置和旋转偏移来尽可能限制骨骼节点可能的位置，这也有助于减少穿模现象的发生。 五、Magica Cloth的性能表现 在实际应用中可以发现，Magica Cloth虽然使用了Job System和Burst Compile的方法来进行物理计算，但是它造成的CPU耗时依然较高，官方文档中也提到Magica Cloth组件的耗时是稍高于Unit…

URP下的OffScreen Particle Render
type: item
date: 2022-10-21T06:27:00+03:00
description: 【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 随着越来越多的项目使用URP管线，以及更多的项目的特效压力越来越大，因此本篇文章基于GitHub上的一个项目将其从Builtin管线下移植到URP修改版，给大家提供了URP管线下降低特效GPU压力的做法，以供大家参考。 GitHub项目：OffScreenParticleRendering https://github.com/slipster216/OffScreenParticleRendering 这个库做的工作是将粒子系统或者半透明的渲染对象单独渲染到RenderTexture中，然后再将RenderTexture与不透明的渲染进行混合。单独渲染到RenderTexture的好处是可以通过降低RenderTexture的分辨率从而减少GPU的渲染压力。 Builtin管线下最终融合效果 Builtin管线下RenderTexture的渲染效果 为了能将此功能移植到URP下，我们先说明一下原始项目中Builtin管线下功能实现的关键点，具体细节在原GitHub项目的说明文档中有说明，主要步骤如下图1，2，3，4。 1、主相机中渲染不透明对象，且因为渲染半透明时需要做深度比较，因此主相机的depthTextureMode需要设置为DepthTextureMode.Depth。 2、在OnRenderImage中将_CameraDepthTexture进行DownSample。 3、在OnRenderImage中从主相机复制一个相机，此相机渲染到RenderTexture，且RenderTexture可以调整分辨率，在此相机中单独渲染粒子系统和其他半透明对象。调用拷贝的离线渲染相机的Camera.Render，这样可以保证离线相机的渲染顺序是在主相机之后渲染的，否则设置了RenderTarget的相机会优先比没有设置RenderTarget的相机先渲染。半透明的对象的Shader需要做特殊处理，需要将半透明对象和渲染场景生成的深度进行比较，比不透明对象离相机更近的像素才会被“绘制”到RenderTexture上。 PS：在原始项目中渲染Particle的Shader中少了“COMPUTE_EYEDEPTH(o.projPos.z);”，所以效果看上去不太对，在vert函数中添加上这句就OK了。 4、在OnRenderImage的最后通过OffScreenParticle_Upsample Shader将Particle等半透明的RenderTexture融合到不透明场景中。此Shader中将RenderTexture的颜色通过Blend SrcAlpha OneMinusSrcAlpha的方式进行原色混合，从而和主场景进行混合。 以上就是原始Builtin管线下的实现思路，转到URP下，有几个问题需要思考： OnRenderImage在URP下是不支持的。 是否还需要额外的相机来实现半透明对象的渲染。 如何处理最后的融合操作。 在URP下，对于第一个问题，有专门的RenderFeature可以对应OnRenderImage的操作；第二个问题，并不需要额外的相机来渲染半透明对象，只需要模仿URP自带的CopyColor来实现渲染另外RenderTexture的操作即可，只是BlitCopy使用的材质球不一样；第三个问题，同样用另外一个RenderFeature来实现即可。 URP下的实现效果： URP下RenderTexture中渲染OffScreen Particle的效果如下： 在此工程中，主要的点如下： 1、移除额外的Camera，取消了OffScreenParticleCamera脚本的作用。主相机需要开启Copy Depth。额外加了一个Particle的Layer，粒子特效的层改成Particle了。 2、新增了两个RenderFeature，OffScreenParticleFeature和OffScreenMergeFeature。前面的是用来绘制Particle的，后面的是用来做主场景和Particle融合的。在OffScreenParticleFeature中添加了2个Pass，一个绘制Particle到_ParticleRT，一个对Depth进行降采样，绘制到_CameraDepthLowRes，OffScreenMergeFeature中有一个Pass，使用MergeShader进行融合。 3、OffScreenMergeFeature用到的Shader是MergeS…

雪地脚印 & 体积云
type: item
date: 2022-08-31T05:20:00+03:00
description: 【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 今天推荐的两个项目来自UWA开源库：雪地脚印和体积云。 雪地脚印 一、概览 https://lab.uwa4d.com/lab/5b66026ed7f10a201ff999c2 该项目实现的功能是在雪地上踩出脚印，只支持PC平台。 效果如下： 二、实现原理 1. 脚印踩在何处？ 我们可以看到dynamic_texture这个父物体下有一个摄像机（暂时叫它副摄像机），并且在运行状态下，每一个实时踩出的脚印都一一对应着框中一个个实例化出的default_sprite预制体，如下图： 图1：dynamic_texture结构 在dynamic_texture附带的DistanceMapPainter中，最主要的工作就是把脚印的基本信息以颜色的形式附给预制体的颜色信息，如下图： 图2：颜色包含的信息（from DistanceMapPainter.cs） 那这些预制Sprite的意义是什么呢？不只是传递以颜色为表现形式的位置信息，也在通过a通道传递脚印的“寿命”信息。 图3：颜色的a通道（from sprite_fading.cs） 最重要的是，还会保存深度信息： 图4：Sprite中的深度信息（from DistanceMapPainter.shader） 2. 脚印信息如何传递？ 副摄像机在这里会专门“监视”这些实时生成的预制体，然后把它们渲染在PainterRT上（如下图）： 图5：副摄像机的渲染目标 那这张“PainterRT”记录着什么呢？直观上来讲，它记录着如图1中花花绿绿的方块图，上文也提到过了这些颜色包含的是各个脚印的位置等信息，但它们重叠怎么办呢？ 请注意我们刚才提到Sprite预制体中的Shader会保存深度信息，然后看PainterRT的信息： 图6：PainterRT基本信息 它是打开深度缓存的，所以这张RT上保存的是通过了深度检测的点的颜色信息。至于哪些点能通过深度检测呢？按照图4对于深度的写法，那就是处于每个Sprite中心位置最“白”的地方，也就是最靠近摄像机的地方，也就是最后能通过深度检测的地方，那最后这张图的中心处肯定能渲染到RT上。 图7：把深度作为颜色输出之后的PainterRT 为了直观地看到深度图的效果，我们暂时把深度作为颜色输出（看完之后记得改回来哦~），可以看到单个的Sprite是一个白色的“山丘”，越靠近中心越白，而我们看到的这些白色其实已经是通过了深度测试的了，可见每个山丘的中心是一定能通过深度测试的，而其他边缘处，即使记载了脚印信息，但是脚印也很可能覆盖不到那儿（当然，这得看你想把脚印画多大了）。 那么会存在两张图完全重合而使得信息读取不到的情况吗？从上图可知只要不是两张图完完全全重合，就不会互相影响，如果真的是两个山丘完全重合，那么的确会丢失其中一个脚印。这就是为什么操控着角色在一个地方反复徘徊，总有几个脚印画不出来的缘故。 而这张RenderTexture将会作为地面Shader的输入，这样就把位置、角度等信息通过一张图传给了地面Shader。 3. 曲面细分着色器 我们现在来看最重要的地面Shader——SnowGroundShader。 打开并浏览这个Shader，可以看到这个Shader中使用了细分曲面。 图8：曲面细分着色器和几何着色器的标志 关于曲面细分的教程并不多，可以参考《Unity Shader：曲面细分着色器》。 我们主要关注细分计算着色器，因为大部分重点在这一部分。 图9：细分计算着色器中获取脚印信息部分 其中_DistanceFiled就是上文中副摄像机渲染的图，centerPos和angle分别读取了其中保存的位置和角度信息。 至于地面的凹凸起伏，就是在顶点的纵向上加上高度，而高度就是从高度图读取的，如下图： 图10：细分计算着色器中顶点的起伏计算 顶点只要变换了，法线就得重构，这里切线空间的三个向量会根据高度图重新计算，存入o.tspace中： 图11：细分计算着色器中法线重构部分 4. 最后的效果 最后的效果在片元着色器中。 首先视觉效果上，上文的o.tspace中保存的因高度改变而重构的法线会与雪地和脚印的法线之和做一个点乘操作，如下图： 图12：片元着色器中法线合并部分 之后的光照，就是用了Unity封装的LightingStandard_Deferred（该项目用了延迟渲染），这里就不赘述了。 总之，整个流程就是确定位置和角度、根据高度图调整顶点高度、重构法线。对于曲面细分的部分，细分的越精细，效果就越精致。 …

HDRP Water & 云影
type: item
date: 2022-08-16T06:45:00+03:00
description: 【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 今天推荐的两个项目来自UWA开源库： 1）HDRP Water 2）云影 01 HDRP Water ShaderGragh是继2018版之后出现的一种针对Shader的可视化编程工具，通过在ShaderGragh面板中编辑各种效果模块的连接方式，可以实现不同的效果。该工具将编辑Shader代码的操作简单化和可视化，也提高了Shader的编辑效率。 该项目是通过ShaderGragh制作的简单水面着色器。通过对该项目的学习，读者不仅可以对该工具有初步的认识，还可以对水面的实现原理有所了解。 一、原理概述 该项目从原理上可以划分为三部分：顶点波动、法线流动和边缘着色。 顶点波动：该部分主要计算通过3个参数各不相同的波叠加之后，平面上各个顶点的纵向位移规律。 法线流动：顶点的法线信息是通过法线贴图获取的，通过控制法线贴图的UV坐标偏移来实现水面的流动效果。 边缘着色：该部分主要通过深度图检测水面边缘，而后在边缘处加上了颜色渐变效果。 二、具体实现 1. 顶点波动 在渲染管线中，对顶点的编辑通常会放在顶点着色器（下图中的Vertex模块）中： 图中顶点着色器主要就是在计算Position，而从上半部分可知，可控输入包括3个波的波长、波速和振幅。 打开上图的3WaveGenerator，可以发现这三个作为输入的波的处理方式几乎相同，只不过每部分对应的参数稍作变换，目的是使得三种波之后的叠加避免谐振，从而让最终叠加后的波形显得更加自然。拿其中一个波来看，可以看到只有波的x和z值参与了计算（如下图），因为这里要先模拟水波的横向传播部分： 之后，通过乘上时间参数产生实时偏移，来模拟波在xz面上的传播，也就是最终水波的流动效果： 但是我们知道，波的横向传播并不是真的把波上的某个顶点随着波的传播方向“运送”过去，其作用的只是顶点在纵向的振动位置而已。所以这里计算顶点的x和z归根结底是为了算纵坐标，这里通过正弦函数求得： 并且三个波经过计算而叠加之后，作用的也只有模型上各点的纵坐标而已： 但我们要知道该纵坐标是包含了波的传播特性的，因为它是根据x、z坐标和时间算出来的。所以此时如果观看模型，它已经可以波动，且参与叠加的波越多、差别越大，波动就会越自然，不过由于法线信息尚未更新，因此远处的顶点是看不出起伏状态的（如下图）。 2. 法线流动 之所以称顶点为“波动”，法线为“流动”，是因为顶点只是在纵轴上运动，而法线贴图是在水平方向运动。而贴图的“流动”，其实就是对贴图采样点的定时偏移。其主要部分如下图： 值得一提的是该Shader同时输入了两张法线贴图（如下图）。其中第一张法线贴图描绘的比较陡峭、颠簸（下图左），旨在塑造波长较大的水波，而第二张贴图比较轻微、柔和（下图右），旨在塑造细小的波纹： 一般情况下，水面的法线贴图是需要有两张的，因为这样可以塑造出不同波长的水波互相干涉的场景。下图为单张法线贴图输入（左）和两张法线贴图输入（右）的效果图，可以看出来右边的水面描绘了更多细小的波纹。 3. 边缘着色 边缘需要通过深度来判断，下图中，上面的框是在拿当前点的深度，下面的框是在计算当前点对应的像素在深度图中的深度： 这里用两个深度的差将作为边缘色的分母进行除法（下图中Divide部分），那么可以知道只有深度差比较小的时候才能显现出边缘色，也符合现实中“接近边缘才会明显变色”的情况。最后通过Clamp把颜色限制在合理范围： 下图分别是添加了边缘着色（左）与不添加（右）的效果，可见添加该功能会让场景变得更丰富、真实。 02 云影 在以自上而下视角为主的游戏（策略或建设类）中，云影是比较常见的功能。通过控制其形状、移动等各种参数，可以逼真地反映出天气的变化，这对游戏场景的视觉效果及气氛烘托都起到了比较重要的作用。 本篇的Unity Cloud Shadows就是一种动态、高效的云影工具，不仅可以很方便地调节云影的多种参数，也不会对性能造成很大压力。 一、原理概述 该工具的实现主要依靠的就是光源的Cookie功能。官网解释：“如果创建包含Alpha通道的纹理并将其分配给光源的Cookie变量，则会从光源投射剪影。剪影的Alpha遮罩会调制光源亮度，从而在表面上产生亮点和暗点。这是场景增加复杂性或氛围的好方法。” 该功能的位置如下： 二、具体实现 当运用光源的Cookie时，即使场景中没有物体，Frame Debugger中也会出现下图中下的Draw Dynamic，而Draw Dynamic就是在画云影，也就是下图中的噪声图：…

GeoAO：一种快速的环境光遮蔽方案
type: item
date: 2022-08-03T06:13:00+03:00
description: 【博物纳新】专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 今天推荐的项目来自UWA开源库：https://lab.uwa4d.com/lab/5b561f86d7f10a201fd8422e 一、概览 AO（Ambient Occlusion）是一种基于全局照明中的环境光（Ambient Light）参数和环境几何信息来计算场景中任何一点的光照强度系数的算法。AO描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比，因此使得渲染的结果更加富有层次感，对比度更高。 该项目介绍了一种快速的基于顶点的AO算法，将AO看做模型顶点的一个属性，通过深度相机对模型多位置的采样，结合模型顶点的深度校验来计算对应的AO值。该方法相比较于SSAO，是一种比较小巧快捷的简易方法。通过对该项目的学习，读者可以初步了解AO算法的思想，也可以学到该算法的具体简化策略。 二、原理概述 总体来说，该算法可以分为两个步骤： 1.采样：通过改变深度相机的位置并对目标物体渲染，把各个位置获取到的深度图保存下来作为采样数据。 2.计算AO：将采样数据通过深度校验来识别出需要遮蔽的模型顶点并按照一定权重累加遮蔽值，以达到阴影效果。 该方法并不是一个实时计算的方法，而是一个一步到位的方法，本质上是在运行刚开始时就计算好了一个带有AO的材质并替换掉模型原有的材质。所以它并不会随着光照的改变而改变，通俗来讲就是“把容易变暗的地方的颜色画得暗一点”。 三、具体实现 从geoAO.cs来看，所有的工作都是在Start()中完成的，如下图，而采样和计算AO的主要逻辑大都在DoAO()中。 1. 采样 该步骤需要一个深度摄像机来采样深度图，摄像机的创建在CreateAOCam()中完成： 该摄像机从创建开始就是隐藏状态（148行），因为我们并不需要OnRenderImage()每帧都执行，我们想要它“按需执行”，确切来说是为了让Blit函数“按需执行”（如下图），那么我们可以通过把相机隐藏，用Camera.Render()来手动调用OnRenderImage()。 除此之外，我们也注意到为了模拟平行光效果而把摄像机模式改成了正交（150行），且开启了深度模式（158行），这时一个深度相机就已经准备完成了。 采样环节最核心的环节如下图，该部分在DoAO()函数中： 首先，该for循环的循环次数对应着采样次数，也就是最终渲染出来的深度图样本数量，而样本个数决定了AO的精度高低： 在每一轮采样过程中，AO摄像机（AOCam）作为一个单独的深度摄像机，会到达一个新的已经计算好的位置（256行），然后看向模型（257行），接着把准备好VP矩阵传给Shader，最后渲染。 这里可以注意到276行就是在手动调用OnRenderImage()函数，但其实计算AO这一步就是在276行在Shader（VertexAOCompute.shader）中进行的，相当于每一轮循环都会做一次AO运算，然后每轮得出的AO值最终会累加在一起附给一个新的材质。 2. 计算AO AO的计算在VertexAOCompute.shader中。下图是片元着色器部分： 判断到底哪些点需要有“遮蔽”的部分就是120-121行。从118行的注释我们也可以知道，变量o“决定了遮蔽会有多暗”，而120行告诉我们，对于vertex.z和z相差过大的点，也就是被遮蔽的点，o不会参与累加（121行），因为o越大模型就会越亮。最后的累加会带上权重，只不过每次采样的权重都一样而已（124行）。 而上一步计算出的AO（65行）作为上图中的_AOTex参与了最终图像插值时的权重值（67行）。最终渲染出的图像会附着在一个新材质球上，而该材质球会替换掉模型原有材质（如下图），至此AO效果就添加到了模型上。 3. Tips 该工程计算AO的过程很直接地体现出了AO算法的思想。 概括来讲，环境遮挡被定义为从表面上某一点能够逃离场景的射线的比例。上文提到的每一轮AO采样计算相当于图中每条绿色的射线，而判断深度是在看该射线是否会被遮挡，最后的累加就是在计算比例，而这里通过样本均值来估算积分的过程也是蒙特卡洛积分思想的体现。 四、性能分析 本次测试用的是低端机型OPPO A32（4G RAM），测试分为开启和不开启GeoAO这两种情况。首先，下图显示的是开启情况下的FPS均值和GPU耗时情况： 下图是不开启该插件的情况： 可以看出即使在低端机型上，该工具的开启也没有为GPU和FPS造成压力，相反的是，GPU耗时反而比不开启的情况下要低，这是因为该插件只是在Start()…

网格切割算法
type: item
date: 2022-07-19T04:57:00+03:00
description: 【博物纳新】专栏回归啦～ 这个专栏是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 更多精彩内容请关注：lab.uwa4d.com 一、概览 该示例展示了一种简单的3D网格切割算法。在运行状态下，通过在屏幕上拖拽划线，被线条划过的物体就会被线条分成左右两部分。 如果某些游戏需要包含切割物体之类的玩法，或者项目需要对物体切割的辅助工具，那么该插件可以作为良好的借鉴材料。 开源地址：https://lab.uwa4d.com/lab/5b3e3197d6d8c0171a9469b5 二、原理概述 该工具的原理可以分为划线、切割、补齐三部分。 1、划线部分：通过捕捉鼠标在屏幕上的拖拽轨迹，从而获取到切割平面的基本数据信息（如法向量等）； 2、切割部分：通过添加点和三角面等操作，把原本的Mesh切割成若干个独立的Mesh，再附着给各个被切开的Objects； 3、补齐部分：把每个被切割的物体的切面补上新的面。 三、具体实现 3.1 划线 这一部作为一个直观的操作，逻辑并不多，主要是要清楚这一部在准备什么数据。下图就是该步骤的主要逻辑： 该方法在MouseSlice.cs脚本中，从中我们可以知道切割物体需要的就是三个三维向量start、end和depth。这三个变量的收集来源ScreenLineRenderer.cs的Update()： 从上图可知，start和end对应于上图的startRay和endRay，分别是从摄像机位置出发，指向划线的起点和终点的两条射线，而depth是endRay的归一化向量。 划线切割物体的本质是划了一个平面来切割3D物体，之所以看起来是一条线是因为摄像机恰好在该平面上，该平面如下图所示。这样看来图1中63行的输入（某个点和法向量）就容易理解了，因为一个点和平面的一条法向量可以确定一个平面。 3.2 切割 切割物体时，我们可以注意到每次切割物体其实都是在实例化一个新的物体： 对应代码来自MouseSlice.cs中的SliceObject方法（如下图），从该代码可知，原Mesh会被分开成biggerMesh和smallerMesh两个网格，然后分别更替原物体和新增物体的Mesh（130-131行）。 切割过程是一个添加点的过程。把过程聚焦到每个三角面，如果某个三角面会被切割，那该三角形必定会新增两个点。对应的代码如下： 图中遍历每个三角面，如果会与切割平面相交，那么新增的两个点会加入addedPairs，而关于相交计算的部分在TrianglePlaneIntersect()中。 下图是TrianglePlaneIntersect()中的片段： 这一段代码如果搭配着下图会比较容易理解： longlyPoint指的就是被切平面（红色实线）单独分到一侧的点，而127和128行得到的newPointPrev和newPointNext就是上图中两个红色的点newPrev和newNext。131-135行是在添加由新的点组成的三角形，以上图为例，从上到下分别添加了（longly，newPrev，newNext）、（prev，newPrev，next）和（next，newPrev，newNext）三个三角形，并且以是否与切割平面法线同侧为标准分别加入了不同的Mesh中。 这一步基本已经完成了切割，毕竟“添加点”和“添加三角形”就是切割网格的本质。但是如果只做到这一步，只会得到如下的效果图，所以对于切面的各个三角面仍然需要补齐。 3.3 补齐 该部分是在补齐由切割平面新增的两个截面，对应的代码段就是MeshCutter.cs中的FillBoundaryFace()： 该函数的详细代码如下： 基本思想就是在切割面所新形成的点集中，按序添加三角形。从158行可以看出，这里是双向遍历的。单向和双向所构成的三角面如下示例： 图中黑线是待补齐的平面，红线则是通过循环补的三角面。那么为何要用稍显复杂的双向循环来补齐呢？因为单个顶点的复用会使得大量三角形都比较“狭长”，那么被共用多次的顶点附近的像素在光栅化阶段参与三角形遍历的次数就会增多，也就是Overshading现象会比较严重，这会增大GPU的压力。这就是为何大多数建模软件中，Mesh网格都尽量避免多个三角形共用单个顶点的原因。 相关知识可参考：https://zhuanlan.zhihu.com/p/57661927 而该步骤之后，被切割平面分开的两个物体块就补齐了切割面： 四、性能分析 通过UWA GOT Online在中等配置机型OPPO A72（6G RAM）上做了测试，测试场景为同时切20个圆柱体，测…

Lua Profiler——快速定位Lua性能问题
type: item
date: 2019-12-20T04:03:00+02:00
description: 导读 随着Lua在项目中的大量使用，它所带来的性能问题也逐步成为了项目运行时的重大性能瓶颈之一。特别是内存相关的性能问题，无论是内存分配过大还是内存泄露无法回收，目前都已经在不少研发项目中集中爆发。 UWA推出的GOT Online中的Lua模式已经慢慢成为研发团队对Lua进行日常性能监控的有效手段。因此，也有越来越多的团队反馈，在监控到table数持续上涨，引用Mono对象持续增多等等问题时，应该如何快速地解决？ 本次博物纳新推荐的开源库项目：LuaProfiler-For-Unity，相信可以帮助到大家。 Lua Profiler For Unity支持XLua、SLua、ToLua，该工具是基于远程Socket的Profiler工具，因此它支持Android，iOS的真机Profiler。 小编将结合实际项目中遇到的问题，为大家介绍这款开源库的用法。 开源库链接：https://lab.uwa4d.com/lab/5bf38db072745c25a80c1276 作者Blog：https://www.zhihu.com/people/ElPsyConGree/activities 操作流程 一、部署和安装 参考项目中的Readme文档阐述的详细流程： 1、打开两个Unity项目，一个放进游戏客户端，一个用于展示数据 2、打开LuaProfiler文件夹 LuaProfiler文件目录 3、将 LuaProfilerClient 文件夹复制到游戏项目内，如果您的C＃Lua脚本位于Plugins文件夹中，则将 LuaProfilerClient 复制到插件。此工具必须确保该代码必须位于具有C＃Lua代码的同一DLL中。 4、使用 Unity5.6 or newer version Unity版本将 LuaProfilerServer 作为Unity项目打开。 5、如果Unity版本低于5，请在开始游戏前调用以下代码。 MikuLuaProfiler.HookLuaSetup.OnStartGame(); 注意：不要在static变量声明里面启动Lua流程（比如XLua的Demo），请在Awake或者Start里面进行调用。 二、使用教程 （该小节内容全部来自于项目Readme文档，想要阅读更详细教程的读者可访问项目主页：https://lab.uwa4d.com/lab/5bf38db072745c25a80c1276） 1、配置客户端 Lua Profiler Client界面 LuaProfilerClient插件所在的游戏项目工程，通过Editor界面的Window->Lua Profiler Window打开客户端设置界面。选择想要分析器的代码类型，C＃代码颜色为绿色，Lua代码颜色为蓝色。 2、配置服务器端 LuaProfilerServer插件所在的项目工程，通过Editor界面的Window->Lua Profiler Window打开服务器数据显示界面。 Lua Profiler Server界面 单击OpenService，等待客户端连接。 操作流程示意图 3、相关功能操作 使用示意图 3.1 相关基础数据统计 折线走势图展示了PSS、Mono内存、Lua内存、FPS的走势： 折线走势图 数据列表 数据列表中列举了当前帧下图所示的相关数据： 3.2 监控注册表 注册表引用的Lua对象统计 此处区域会显示当前被注册表引用的类型为Function和table的Lua对象。 3.3 Diff两个不同时期的Lua变量 选取一个适当的时机，比如配置表加载完后，准备打开一个新的UI的时候点击MarkLuaRecord按钮。 操作步骤 打开UI然后关闭并卸载掉UI资源，点击DiffRecord，工具将会对Mark时候的Lua变量与DiffRecord时候的变量进行差异比较。 数据显示界面 点击ShowLog按钮，将会把文件存盘打开之后将把对于变量的类型以及引用路径打印出来。注意_G表示全局表，_R表示被C#引用的对象。 引用链 3.4 Destroy null values统计 该模块展示了Unity已经将资源释放，而Lua仍然引用的变量。 引用链 功能原理详解 一、相关基础数据统计 查看这部分数据时，除了关注排序中开销较大、内存占用较高的函数外还可以关注一些重要的字段，例如： require字段，比较普遍的性能问题在于：加载配置表时产生一个内存占用较大的table，可以通过搜索require字段并进行排序查找内存占用较大的配置表，并对它进行针对性优化。 关于配置表的优化方案可以阅读《Lua配置表存储优化方案》。 [Lua]字段，按照Lua内存的self进行排序，可以定位GC比较严重的Lua函数，进行针对性优化。也可以查…

基于DOTS的UI解决方案
type: item
date: 2019-10-25T05:27:00+03:00
description: 自从在GDC 2019上，Unity分享了名为“连接DOTS：Unity面向数据技术栈”的技术演讲，关于DOTS的讨论和应用一直在业内备受关注。前段时间我们连载的“Unity手游实战”系列中，也有对于DOTS的相关论述。 本文要给大家介绍的是supron在社区中与大家分享的高性能UI解决方案：Pure DOTS UI System[3]。 The current Unity UI solution is very powerful but struggles with performance (especially with many objects instantiation). DOTS seems like a great solution to this problem. DOTS UI开源库：https://lab.uwa4d.com/lab/5d3a18365b9dec79de05d348 一、功能 DOTS UI System可以将Unity的UI系统UGUI的组件转化映射成Entity，利用ECS、JobSystem、Burst的性能优势，明显提升UI运行效率。除此之外，在网格重建部分还使用到了2019.3的新特性：Advanved Mesh API[5]，可以直接写入Mesh数据，运行效率更快。 目前DOTS UI的版本为0.3.0，功能还是非常不完善的。目前已支持的主要功能如下： Canvas Render mode: Screen space camera Screen space overlay Canvas Scaling mode: Constant pixel size Constant physical size Controls: Image TextMeshProUGUI (SDF fonts, not all features are supported) TMP_InputField (very simple implementation) Selectable Button RectMask2D CanvasScaler ScrollRect Input events: Down Up Click Enter Exit Selected Deselected BeginDrag Drag EndDrag Drop Button click InputField OnEndEdit InputField OnReturn 二、使用 在开源库中下载资源后，将com.dotsui.core和com.dotsui.hybrid两个资源包复制到项目工程（Unity 2019.3.0a8以上）的Packages路径下进行导入，则Unity会自动导入其依赖的Entities、Jobs等Package。 打开一个UI预制体，在Canvas节点挂上ConvertToEntity[6]脚本，表示需要将这个UI转换成Entity。默认选择的Conversion Mode为Convert And Destroy，即替换为Entity之后会把原有的UGUI组件销毁。 运行一下，即可看到运行时生效的效果。 由于测试的Prefab中的Text不是使用TextMeshPro做的，所以没有成功转换，但这并不影响其它组件的正常运行。 多做几次测试之后，还会发现RectTransform的Rotation和Scale属性没有被正确显示。这是因为作者在第一个版本中简化了很多属性，其类定义如下： public struct RectTransform : IComponentData { public float2 AnchorMin; public float2 AnchorMax; public float2 Position; public float2 SizeDelta; public float2 Pivot; } 作者还在工程中提供了Sample示例，展示了目前支持的几种效果。 如果需要使用脚本控制UI变化，也要用ECS的方式来编写。可以参考Sample中的简单例子（如：FpsCounter、FPSSystem）进行改写。 三、实现 1、Conversion Conversion的部分相对比较简单，核心在于将Canvas、Image等组件转换为Enity。 以上文提到的RectTransfrom为例，其Convert函数的代码如下： private void Convert(RectTransform transform) { var entity = GetPrimaryEntity(transform); DstEntityManager.AddComponentData(entity, new DotsUI…

SpeedTree Shader开源库测评
type: item
date: 2019-09-19T10:12:00+03:00
description: 可用于构建树木的工具，不仅能营造出真实的树木和森林效果，还可以方便地嵌入到其它引擎当中。在游戏领域中，很多艺术家青睐于它成熟流水线和真实自然的效果。 图片来源：SpeedTree在游戏场景中的应用 开源库链接：https://lab.uwa4d.com/lab/5c2e78a572745c25a849f614 NVGen介绍 本文将要介绍的开源库是一个适用于Unity SpeedTree的自定义Shader NVGen。 相比于Unity内置的Nature/SpeedTree Shader，NVGen 提供了更丰富的自定义风效。同时支持Occlusion Map环境光遮蔽贴图，并可以自定义调节法线贴图和遮蔽贴图的强度，让渲染效果更逼真。 移动端真机渲染静态截图对比 NVGen比内置Shader多出了20多个可调节参数，以下为参数面板对比： 内置Shader： NVGen Shader： 对于原本就使用了SpeedTree的开发者来说，在使用上，只要将NVGen资源包导入工程，并将导入Unity的.spm格式文件的Shader替换成NVGen即可，不需要再额外处理什么，十分简单方便。 动态效果展示 1、树随风摇摆效果 2、草随风摇摆效果 3、调节法线贴图强度 4、调节遮蔽贴图强度 5、调节Leaf Tumble参数，模拟树叶被吹动摇晃的剧烈程度 6、调节Leaf Ripple参数，模拟树叶波浪摆动的效果大小 性能测评 为了让帧率对比更明显，我们在场景中复制了5棵树，得到不同机型上的帧率对比如下： 从整体帧率对比来看，NVGen Shader确实会有一部分的性能损耗，但可以带给美术人员很灵活、自由的操作空间。研发团队可根据自己的需求来决定是否尝试和使用。 相关链接： 作者Blog：https://nvjob.pro/unity/nvgen-shader-for-unity-speedtree Unity API：https://docs.unity3d.com/Manual/SpeedTree.html SpeedTree官网：https://store.speedtree.com/ 封面图来源：SpeedTree在游戏场景中的应用 快用UWA Lab合辑Mark好项目！ 今天的推荐就到这儿啦，或者它可直接使用，或者它需要您的润色，或者它启发了您的思路...... 请不要吝啬您的点赞和转发，让我们知道我们在做对的事。当然如果您可以留言给出宝贵的意见，我们会越做越好。 【博物纳新】是UWA旨在为开发者推荐新颖、易用、有趣的开源项目，帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果，并探索将其应用到自己项目的可行性。很多时候，我们并不知道自己想要什么，直到某一天我们遇到了它。 更多精彩内容请关注：lab.uwa4d.com