目录

  • 1. 什么是UGUI的合批
    • 1.1 准备工作
    • 1.2 批处理
    • 1.3 批处理的意义
    • 1.4 UGUI的合批
  • 2 分析工具的使用
    • 2.1 Frame Debugger的使用
    • 2.2 Profiler-UI的使用
  • 3 UGUI合批规则
    • 3.1 UGUI合批初体验
    • 3.2 UGUI合批被打断初体验
    • 3.3 UGUI合批规则详解
      • 3.3.1 合批规则
      • 3.3.2 合批规则示例1
      • 3.3.3 合批规则示例2
  • 4 优化
  • 5 参考文章

1. 什么是UGUI的合批

1.1 准备工作

在正式开始之前,咱们先做个准备工作:创建一个新场景,然后把自带的平行光给删除,讲相机的Clear Flags改为Solid Color。

此时打开Game视图中的Stats面板,可以看到Batches数为1。
(Stats面板上的参数怎么看,以及什么是Batches,可参考之前写的博客《Unity3D客户端项目优化总结之Stats统计面板》《Unity3D客户端项目优化总结之静态批处理Static Batching》)

1.2 批处理

再说UGUI的合批之前,先看看什么是批处理。
在说批处理之前我们先看看一个普通的3D模型是怎么渲染出来的。
①首先是CPU这边先准备好这个模型的网格、用到的贴图和Shader,然后GPU将网格、贴图、Shader加载到显存里面。
②然后是CPU设置渲染状态
什么是设置渲染状态呢?就是CPU设置渲染这个网格的时候使用哪个Shader,使用哪几张贴图。第①步中我们可能会准备好很多的Shader(如Shader1、Shader2、Shader3),很多张贴图(贴图1、贴图2、贴图3),设置渲染状态这一步的作用就是告诉GPU,接下来你渲染这个网格的时候使用的Shader是Shader1,不是Shader2、Shader3,使用的贴图是贴图1而不是贴图2、贴图3。
也就是说CPU是老大,GPU是小弟,老大说你下次渲染这个模型的时候用这个Shader和这张贴图,那么小弟开始干活的时候就按老大的要求来。
③CPU设置完毕渲染状态后,GPU还没正式开始渲染这个模型,而是等CPU发号施令,CPU告诉GPU说"你可以渲染这个模型了”,然后GPU才开始按照②中设置的Shader和贴图真正渲染这个模型,并把渲染后的结果层递到屏幕上。CPU告诉GPU“可以渲染这个模型”的过程或者说这个命令叫做Draw Call(我们在Stats面板上看到的Batches其实就是Draw Call的调用次数)。
从上面的流程可以看出,每一个3D模型要被渲染都应该会走完一个完整的步骤①②③。也就是说一个模型要被渲染,按理说就应该调用一次Draw Call。比方说我们场景中有3000个模型,那么Draw Call应该是3000,但是我们看Stats面板会发现Draw Call(Stats面板上的Batches值)并没有那么多。为什么会这样呢?因为Unity进行了批处理。
批处理就是把渲染时使用相同材质(Shader)、相同贴图的3D模型的网格合并在一起,成为一个大网格,然后再调用一次Draw Call,直接渲染这一个大网格。
所以需要注意的是,一定要使用相同材质和相同贴图的模型才可以批处理,一个模型使用的材质或贴图与其他模型不同,那么CPU就得单独进行步骤②设置渲染状态,紧接着也就得单独进行步骤③调用Draw Call。

1.3 批处理的意义

从上面的分析可以看出,批处理的意义在于减少了Draw Call的调用。
因为CPU调用Draw Call之前,需要准备好数据,设置渲染状态,而准备数据和设置渲染状态特别耗时!如果Draw Call过多,那么CPU就会把大量的时间花在准备数据和设置渲染状态上,而造成性能问题。
举个例子,我们移动一个包含1024个1kb小文件的文件夹比移动一个1Mb的文件慢很多。因为计算机在移动文件的时候会有很多额外的操作,所以移动多个小文件比移动一个大文件更耗时。
那么渲染也可以这么理解,两个选项:
①CPU叫GPU渲染1000个小三角形
②CPU先把这1000个小三角形合并为一个大的网格,然后再叫GPU渲染这个大的网格
哪个更快?当然是②。因为①CPU要通知GPU1000次,而且每次都要花时间准备数据,设置渲染状态,而②CPU只需要通知GPU一次,也只需准备一次数据,设置一次渲染状态。即①的Draw Call是1000,②的Draw Call是1。
对于普通的3D模型,Unity内部做了静态批处理和动态批处理。
静态批处理和动态批处理的优缺点和限制可查看之前的博客,《Unity3D客户端项目优化总结之Stats统计面板》《Unity3D客户端项目优化总结之静态批处理Static Batching》。

1.4 UGUI的合批

从上面我们知道了一个3D模型是怎么被渲染出来的。那UGUI的渲染和3D模型的渲染有什么不同的吗?
答案是没有什么不同。
UGUI控件本质上也是网格,与3D模型不同的地方仅仅3D模型的网格是我们在3D Max或者Maya中建模建出来的,而UGUI控件的网格是控件代码代码里面去自动创建网格的。
比如我们创建一个Image和一个Text,将Scene视图的渲染选择为线框,可以看到其实Image和Text都是网格。

你可能会好奇Text的网格仅仅是一个矩形,怎么渲染出那么复杂的字呢?其实我们在Text上用的字体本质是个图集,渲染某个字就是把这个字对应图集上的图片渲染出来罢了,和普通的Image渲染本质其实没多大区别,区别在于有额外的模块去处理Text的字体图集与字对应的问题。
那既然UGUI的控件都是网格,那应该可以进行批处理吧?对的!对UGUI控件进行批处理就叫做UGUI的合批。
UGUI的合批就是把某个Canvas下满足合批规则的UI控件的网格合并为一个大的网格,然后将这些网格合并在一起,调用一次Draw Call,然后提交个GPU进行绘制。
那怎样才算满足合批规则呢?根据批处理的定义,只要两个网格使用的材质和贴图是一样的就可以进行批处理。
但是UGUI的合批还有其他规则,光满足材质和贴图相同还不行,具体是怎样的规则,我们后面会有一节专门讲这个事情。

2 分析工具的使用

2.1 Frame Debugger的使用

那么问题来了,我们创建的默认Image和Text能否进行合批呢?
合批的基本条件是什么?材质(Shader)和贴图要相同,那我们来看看刚创建的默认Image和Text是否满足。
两者用的Shader都是默认的UI/Default。

那贴图是否一样呢?我们这里的Image没有指定贴图,直接在Inspector面板看不出来,得去Frame Debugger里面看看。
Frame Debugger的作用就是方便我们查看点击Enable时那一帧屏幕是如何一步一步绘制出来的,也就是说通过Frame Debugger我们可以知道先绘制了什么再绘制了什么最后绘制了什么。
Frame Debugger面板路径位于Window 》Analysis 》Frame Debugger。(我用的Unity版本是2019.3.15,低版本的Unity路径上可能会有所不同)

打开Frame Debugger的窗口。

然后点击Enable,Frame Debugger会展示出当前屏幕是怎么一步一步绘制出来的。

Frame Debugger的左侧是树状结构的,从上到下表示绘制内容的先后顺序,在上面的先绘制,下面的后绘制。树的根节点一般是Camera.Render,表示某个相机看到的画面是如何一步一步绘制出来的。
由于我们这里UGUI的Canvas的Render Mode选择的是Screen Space - Overlay模式,此模式是在所有相机绘制完成后再绘制UGUI的内容,所以在Camera.Render下还有个UGUI.Rendering.RenderOverlays。UGUI.Rendering.RenderOverlays表示UGUI是如何一步一一步绘制的。

选择某一项,Game视图就会展示当前选中项此时的画面。
我们点击Camera.Rendering下的Drawing,可以看到Game视图变成纯色,为什么会这样呢?我们按照树状结构依次看下去,发现其实最后执行了Clear (color+Z+stencil),Clear就是清除的意思,括号里面的内容是颜色缓冲区、深度缓冲区和模板缓冲区。也就是说Drawing这个步骤下面执行了清除颜色缓冲区、深度缓冲区、模板缓冲区。由于它清除了颜色缓冲区,所以整个画面就变成我们相机设置的颜色。

Clear完毕后,接着执行了Camera.ImageEffects,表示相机的屏幕后处理,就是相机看到的内容全部绘制完毕后,再把这相机的画面来进行处理,屏幕后处理可以实现一些特殊的效果。
我们创建一个相机,其默认是开启了HDR和MSAA效果的,所以这里会多一个Camera.ImageEffects步骤。(HDR和MSAA具体是什么,这里就不展开说了)

如果我们在Camera那里关闭HDR和MSAA,Camera.ImageEffects就不会再调用了。如果没有用到HDR和MSAA可以把它们都关闭了,这也是个优化的地方。

相机绘制完毕后,就接着绘制UGUI了(UGUI.Rendering.RenderOverlays)。
绘制UGUI的时候,首先进行了清除模板缓冲区,上面我们看到Camera.Rener已经清除了依次模板缓冲区,为什么UGUI这里又再一次清除呢?是因为UGUI的Mask(遮罩)控件会利用模板缓冲来实现遮罩的效果。(当然我们是不建议使用Mask来实现遮罩的,因为它至少会增加两个Draw Call,后面我们会讲到这个问题。)

清除模板缓冲后,就开始绘制我们的UGUI控件啦,还记得我们上面说的UGUI的本质是网格吗?所以绘制我们这里的Image和Text其实就是两个Draw Mesh(绘制网格)。渲染引擎才不管你是图片还是文字,在渲染引擎看来,所有UGUI控件通通都是网格。
我们点击第一个Draw Mesh,可以看到先绘制的是Image,右侧展示了绘制此Image使用的Shader及其贴图等。我们可以按住Ctrl然后点击_MainTex后的贴图框预览此Image使用的贴图。

然后我们点击第二个Draw Mesh,可以看到绘制的Text。但是为什么从Frame Debugger看到的字体贴图大小为0×0,说实话我也没整明白。

Text绘制完成后,我们的整个场景都绘制完毕了。
细心的同学可能会问,场景中,我们的Text不是在Image上面吗,是不是应该先绘制Text再绘制Image,Frame Debugger里面怎么是先绘制的Image呢?这个涉及到UGUI的合批规则了,先别急下面我们会专门说这个问题。
从Frame Debugger可以看出,Image和Text是分别绘制的,也就是说它们没有进行合批。原因也很简单,因为Image用的贴图时Unity White,而Text用的贴图是Font Texture。

2.2 Profiler-UI的使用

Frame Debugger展示了我们看到的画面是怎样一步一步绘制出来,我们可以间接了解咱们制作的UI是否进行了合批。当然,除了通过Frame Debugger外,我们还可以通过Profier中UI模块更直观的了解咱们的UI是否进行了合批。
打开Profier的快捷键为Ctrl+7,菜单路径和Frame Debugger的路径一样,都是Windows》Analysis》Profiler。
具体操作如下。(ps:我用的Unity版本是Unity2019.3.15,Unity5的Profiler中好像没有UI这个模块)

下面我们来具体看看分析结果该怎么看。
我们这里主要看以下这几栏:Objcet、Batch Breaking Reason、GameObjects以及预览视图。
Object栏展示了批处理的顺序,每次合批都会有个编号,编号从小到大,编号越小的越先绘制。如Batch 0就比Batch 1先绘制,至于这个编号是怎么来的,等会儿说合批规则的时候咱们再讲。
Batch Breaking Reason展示了合批被打断的原因;GameObjects展示了每次合批合批的物体分别是哪些。从这里可看到,第一次合批(Batch 0)只有Image,第二次合批(Batch 1)只有Text,那为什么Image和Text为什么不能在同一个批次处理呢?看Batch Breaking Reason,可以知道原因是Differnt Textrure,就是说贴图不同导致了Image和Text不能合批,这与Frame Debugger的分析是一样的。

Frame Debugger与Profier UI模块的基本使用掌握了后,咱们就来看看UGUI合批的规则到底是什么。

3 UGUI合批规则

3.1 UGUI合批初体验

我们先来直观的感受一下UGUI的合批。如图,在1的准备工作之上(新建一个新场景,然后删除灯光,设置相机的Clear Flags为Solid Color)新建3个默认的Image,其中Image和Imge (1)有重叠部分)。

然后我们看Stats面板,Batches值为2(相机的MSAA没关闭的话会自带一个Batches),说明3个Image只调用了一次Draw Call,即这3个Image进行了合批。

然后我们再看看Profier-UI,可以看到,3个Image进行了合批,一次性把3个Image给绘制出来了。这就是UGUI的合批。

3.2 UGUI合批被打断初体验

然后,我们在Image和Image (1)之间创建一个Text,该Text与Image (1)有重叠部分。

此时我们再看Stats面板,会发现Batches值变成了4。

我们上面知道,因为Text控件和Image控件的渲染时使用的贴图不同,所以两者不能合批。但是在上面这个场景中,Image、Image (1)和Image (2)是能合批的,另外加了个Text,Batches值也应该为3才对(相机自带的一个+3个Image的+一个Text的),但Stats面板为啥显示为4呢,比我们分析的多了一个?
然后我们去看看Profier-UI。
可以看到,Image、Image (1)、Image (2)三者并没有合批,只有Image、Image (2)合批了,Image (1)是单独的一个批次。
也就是说,原本Image、Image (1)、Image (2)三者原本能够合批,但是由于Image (1)下多了个Text,就导致Image (1)不能和Image、Image (2)合批了。
换句话说就是,Text将Image、Image (1)、Image (2)三者的合批给打断了!

那这个Text为什么会打断它们的合批,以及我们该怎么去解决合批被打断的问题呢?
要回答这两个问题,我们就得先弄清楚UGUI的合批规则了。

3.3 UGUI合批规则详解

3.3.1 合批规则

两个UI控件能合批的基本条件是这两个控件使用的材质球(Shader)和贴图要完全相同。比如上面看到的,虽然Text和Image默认使用的材质球都是UI/Default,但是两者使用的贴图不同,所以注定Text和Image无法合批。材质和贴图相同这只是基本条件,还有其他规则。UGUI中完整的合批流程(规则)如下。
首先我们要明确UGUI中Canvas下可以嵌套子Canvas,但是合批是以Canvas(不包含子Canvas)为单位的(子Canvas会是另外一个批次了)。除此之外,合批的操作是在子线程完成的。
①既然合批是以Canvas为单位,第一步自然就是把所有Canvas给找出来,然后剔除掉不必渲染的Canvas(透明度为0,长宽为0,在RectMask2D控件下,且在RectMask2D的区域外)
②然后计算Canvas下各UI控件的深度值Depth(需要注意的是Image的属性里面也有个depth,两者不是同一个东西)
③Depth的计算规则如下:

  • 按照Hierarchy中从上往下的顺序依次遍历Canvas下所有UI元素
  • 对于当前的UI元素CurrentUI
    i.如果CurrentUI不渲染,则Depth = -1
    ii.如果CurrentUI要渲染,但CurrentUI下面没有其他UI元素与其相交,则Depth = 0
    iii.如果CurrentUI要渲染,下面只有一个UI元素(LowerUI)与其相交,且CurrentUI与LowerUI可以合批(材质和贴图完全相同),则CurrentUI.Depth = LowerUI.Depth;如果两者不能合批,CurrentUI.Depth= LowerUI.Depth + 1
    iv.如果CurrentUI要渲染,下面有n个元素与其相交,则按照步骤iii,分别计算出n个Depth(Depth_1、Depth_2、Depth_3…),然后CurrentUI.Depth取其最大值,即CurrentUI.Depth = max(Depth_1, Depth_2, Depth_3,…)
    上面步骤中的“下面”和“相交”要明确下意思,这两个概念很重要。
    CurrentUI下面的UI,指Hierarchy面板中,在CurrentUI之上的元素。

    两个UI元素相交,是指这两个元素的网格有相交(有重叠部分),一定要注意不是两个元素的Rect区域相交。

    在计算相交时,由于要遍历所有UI元素和已计算的底层UI元素(平方复杂度),源码中使用分组计算包围盒矩形的方法加快计算,即16个UI元素为一组计算Group 网格Rect,检查是否与底层UI元素相交时,先计算是否与底层Group相交,如果相交再与Group中的元素做判定。
    ④各个UI的Depth计算完毕后,依次按照Depth、material ID、texture ID、RendererOrder(即UI层级队列顺序,即Hierarchy面板上的顺序)排序(条件的优先级依次递减,且均为从小到大排序)。然后剔除Depth = -1的UI元素,得到Batch前的UI 元素队列,这个队列被称之为VisiableList
    上面这段话有些地方可能没太说清楚,解释一下排序:
  • 先按Depth从小到大的顺序排序
  • Depth排完之后,Depth相同的元素再按material ID从小到大排序
  • material ID排完之后,material ID相同的元素再按texture ID从小到大排序
  • textrure ID排完之后,textrure ID相同的元素最后再按在Hierarchy上的顺序排序(Hierarchy越上面的越在队列前面)

⑤得到VisiableList之后,判断VisiableList中相邻的元素是否能够合批(相同的材质和贴图)。需要注意这里不再考虑Depth是否相同,只要两个元素相邻然后材质和贴图相同,即使两个元素的Depth不相同,这两个元素也能合批。然后一个批次一个批次的合并网格,提交GPU进行渲染。
除此之外,需要注意的是,合批是将同一Canvas下多个UI的网格合并在一起,如果其中任何一个元素的材质、网格顶点、位置(Transform)甚至颜色或者在该Canvas下动态创建或删除UI元素都将导致该Canvas重新计算合批(需要注意的是仅仅会影响这一个Canvas,子Canvas或父Canvas以及其他Canvas不会重新计算),重新生成新的网格,这个重新计算生成网格的过程被称为rebuild。所以,这也是为什么做UI提倡动静分离(动态部分和静态部分分别用不同的Canvas),层级尽量减少(层级多了,重新计算更耗时)的原因。

合批的规则搞清楚了,但彻底弄懂还需要练习一下。我这里专门挑选了几个例子,跟着做一遍应该能大大加深理解了。
在开始之前,我们得先知道material Id和texture Id怎么获取到,其实很简单,直接GetInstanceID()就行了。

// materialId
image.material.GetInstanceID()
// textureId
image.mainTexture.GetInstanceID()

3.3.2 合批规则示例1


如图,三张Image使用的材质都是UI/Default,Image1和Imge3使用的贴图的texture Id = 13188,Image2的texture Id = -1136。
现在,请分析它们三者的合批情况和渲染顺序(先渲染哪张图,再渲染哪张图)?
①首先,先分别计算三张图片的Depth(还记得Depth是怎么计算的吗?忘了再去上面看看)。

  • Image1下面没有其他UI,所以Image1的Depth = 0
    (这里我再提醒一下,image有个depth字段,我们计算出的合批的Depth和image的depth字段不是同一个东西,虽然使用的变量名一样但不是同一个东西,千万不要搞混了)
  • Image2下面有Image1,但是Image1没有与Image2相交,所以Image2的Depth = 0
  • Imge3下面有Imag1和Image2,分别计算Image3下只有其中一个元素时Imge3的Depth,然后取其最大值。
    Image3与Imge1不相交,所以Image3的Depth = Image1的Depth = 0;
    Image3与Imge2相交,所以Image3的Depth = Image2的Depth + 1 = 1;
    然后取其最大值,所以Image3的Depth = 1。
UI 合批的Depth
Image1 0
Image2 0
Image3 1

②然后按照依次Depth、material Id、texture Id、hierarchy sort order进行升序排序,得到VisiableList。

  • 先按Depth排序,顺序为Image1》Image2》Imge3;
  • 再按material Id排序,由于三个Image的材质相同(即material Id相同),则顺序不变,仍然是Image1》Image2》Imge3;
  • 然后再按texture Id排序,由于Image2.textureId(-1136) < Image1.textureId(13188),所以Image1和Image2要进行交换,则顺序为Image2》Image1》Imge3;
    所以VisiableList = {Image2, Imgae1, Image3}。

③最后判断相邻元素是否能否合批,计算合批次数。
Image2和Image1材质相同但贴图不同,所以Image2和Image1不能合批;
Image1和Image3材质和贴图均相同,所以Image1和Image3可以合批(这里需要注意的是,虽然Image1和Image3的Depth不相同,但是到这一步是不再考虑这个问题的);
也就是说Image2单独绘制,Image1和Image3合批再绘制一次。
我们去Profiler UI看看咱们的分析是否正确。

可以看到咱们的分析是正确的。

3.3.3 合批规则示例2


如图,三张Image使用的材质都是UI/Default,Image1和Imge3使用的贴图的texture Id = -1136,Image2的texture Id = 13188。
现在,请分析它们三者的合批情况和渲染顺序(先渲染哪张图,再渲染哪张图)?
①首先,先分别计算三张图片的Depth。

  • Image1下面没有其他UI,所以Image1的Depth = 0
  • Image2下面也没有其他UI,所以Image2的Depth = 0
  • Imge3下面有Imag1和Image2,分别计算Image3下只有其中一个元素时Imge3的Depth,然后取其最大值。
    Image3与Imge1不相交,所以Image3的Depth = Image1的Depth = 0;
    Image3与Imge2相交,所以Image3的Depth = Image2的Depth + 1 = 1;
    然后取其最大值,所以Image3的Depth = 1。
UI 合批的Depth
Image1 0
Image2 0
Image3 1

②然后按照依次Depth、material Id、texture Id、hierarchy sort order进行升序排序,得到VisiableList。

  • 先按Depth排序,顺序为Image1》Image2》Imge3;
  • 再按material Id排序,由于三个Image的材质相同(即material Id相同),则顺序不变,仍然是Image1》Image2》Imge3;
  • 然后再按texture Id排序,由于Image2.textureId(13188) > Image1.textureId(-1138),所以顺序不变,仍然是Image1》Image2》Imge3;
    所以VisiableList = {Image1, Imgae2, Image3}。

③最后判断相邻元素是否能否合批,计算合批次数。
Image1和Image2材质相同但贴图不同,所以Image1和Image2不能合批;
Image2和Image3材质相同但贴图不同,所以Image2和Image3也不能合批;
也就是说Image1、Image2、Image3都是单独绘制,共三个批次,绘制顺序为Image1、Image2、Image3。
我们去Profiler UI看看咱们的分析是否正确。

可以看到咱们的分析是正确的。

4 优化

知道合批的原理之后,咱们就知道UI如何优化了。

  • 使用图集
  • 动静分离(动态部分和静态部分分别使用不同的Canvas)
  • Text如果可以用图片代替就用图片代替
  • 避免频繁删除/增加UI对象,UI层次结构变化会引起Canvas的更新(rebuild)
  • 避免UI元素数目过多和层次结构过于复杂影响Batch更新速度
  • 尽量不要使用Mask(其内部使用了模板缓冲,至少会造成增加2个Draw Call)

当然,上面这些只是一部分。
还有,尽量不要使用Outline、Tiled Sprite(这两者会多生成许多顶点),不需要响应点击事件的取消勾选Raycast等等,但是这些和UGUI合批的关系不大了,至于它们为什么可以优化,得从UGUI的源码入手了,有空我们再来说说吧。

最后,附上测试项目。
链接:https://pan.baidu.com/s/1Git6Qhr0Y8Lef8z7dtwddg
提取码:xfk3

博主本文博客链接。

5 参考文章

ps:前面3个文章内容其实有点问题的,大家可以和这篇文章对比一下然后实验一下看哪个是正确的。欢迎批评指正。

  • UGUI优化:批次合并源码分析及工具
  • UGUI合批源码分析及优化
  • UGUI drawcall合并
  • UGUI合批原理笔记
  • UI batching in Unity
  • Talking dirty – Unity UI optimisation

Unity3D UGUI系列之合批相关推荐

  1. Unity3d UGUI 通用Confirm确认对话框实现(Inventory Pro学习总结)

    背景 曾几何时,在Winform中,使用MessageBox对话框是如此happy,后来还有人封装了可以选择各种图标和带隐藏详情的MessageBox,现在Unity3d UGui就没有了这样的好事情 ...

  2. Unity3D Shader系列之描边

    目录 1 引言 2 顶点沿法线外拓方式 2.1 法线外拓+ZTest Always 2.1.1 代码 2.1.2 问题点 2.2 法线外拓+Cull Front 2.2.1 代码 2.2.2 改进点 ...

  3. Unity3d Ugui图片上制作点光 、棱形光效果shader,并具有裁切

    Unity3d Ugui图片上制作点光 .棱形光效果 实现的效果可以参考如下图所示 通过shader来实现上述的效果,为了大家的适应性,推荐在unity官方的默认ui shader上更改,我用的是20 ...

  4. 【Unity3D 教程系列第 15 篇】本地数据持久化之 Playerprefs 类

    这是[Unity3D 教程系列第 15 篇],如果觉得有用的话,欢迎关注专栏. 文章目录 一:什么是 PlayerPrefs 类 二:PlayerPrefs 类中的静态方法 1:写入数据 2:读取数据 ...

  5. 【Unity3D 教程系列第 12 篇】如何用Unity写一个简易的计时器工具?

    这是[Unity3D 教程系列第 12 篇],如果觉得有用的话,欢迎关注专栏. 平常关于延时的处理很多人都喜欢用携程去处理,但是在 Unity 里,并不建议大家用过多的携程,因为会出现一些莫名其妙的 ...

  6. 【Unity3D 教程系列第 10 篇】Unity 脚本中的生命周期流程图

    这是[Unity3D 教程系列第 10 篇],如果觉得有用的话,欢迎关注专栏. 文章目录 一:生命周期流程图 二:常用的生命周期函数 三:脚本初始化和销毁 四:脚本的动态添加与静态添加 五:Awake ...

  7. 【Unity3D 教程系列第 16 篇】Scene视图很清楚,但是Game视图却很模糊的解决方案

    这是[Unity3D 教程系列第 16 篇],如果觉得有用的话,欢迎关注专栏. 如果你用 Unity 时,Scene 视图上很清楚,但是 Game 视图却很模糊,恭喜你,你的问题即将被解决了,继续看- ...

  8. Unity3d UGUI基础控件使用(一)

    转载自:Unity3d UGUI基础控件使用(一) 一:UGUI介绍 UGUI是Unity4.6之后,经过多重测试,推出全新的UI系统,更灵活,快捷,易用的可视化游戏UI开发工具. 由于之前传统的UI ...

  9. Unity3D Shader系列之全息投影

    1 效果展示 2 实现原理 全息投影其实是几个效果的叠加:①半透明效果②上下条纹的扫描效果③边缘光效果④模拟信号传输不稳定的顶点偏移效果. 咱们依次来看看这几个效果背后的原理. ①半透明效果 在Uni ...

最新文章

  1. 两地控制的项目要求_项目两地控制灯照明线路的安装.ppt
  2. MVC开发中的常见错误-06-无法在发送 HTTP 标头之后进行重定向。
  3. Oracle中concat与||区别(以及与mysql中concat函数区别)
  4. boost::packaged_task相关的测试程序
  5. 一个参数一个Excel表,让你玩转Pandas中read_excel()表格读取!
  6. SAP ABAP实用技巧介绍系列之 在xslt里call ABAP method
  7. C语言算小数加减,C语言带小数加减乘除.doc
  8. 数论 欧几里得与扩展欧几里得
  9. IBM AIX RISC System/6000 Error: 110: Media surface error错误处理
  10. 设置对话框大小和位置
  11. 软件工程实践专题第三次团队作业
  12. 解决命令行的乱码以及编码的问题
  13. python 选择多个文件_python-PyQt QFileDialog-多目录选择
  14. mysql时间加8小时_劳斯莱斯库里南,超精致1:8模型车,组装时间长达450个小时...
  15. Building a Better Vocabulary: Lecture 1 Five Principles for Learning Vocabulary
  16. Atitit 提取sfit特征点,并绘制到原图上
  17. html链接到word文档,word做html超链接
  18. 计算机机械制图试题及答案,机械制图及计算机绘图试卷和参考答案1.pdf
  19. 程序员的爱情最纯洁?
  20. 图片png怎么转成pdf格式?

热门文章

  1. 班迪录屏,免费,易用
  2. 一对一视频app开发选择如何合适算法
  3. 计算机视觉算法有哪些?
  4. 南昌工学院计算机挂科率,南昌工学院的真实情况
  5. jmeter 监测服务器性能,24. Jmeter使用ServerAgent对服务器进行性能监控
  6. Ansys-静力学分析-平面桁架学习心得
  7. 广东工业大学网络安全实验
  8. matlab的from模块_matlab/simulink中goto/from模块的使用方法及问题解决
  9. 2020年11月虹科Pico汽车示波器简报—新能源车诊断套装发布
  10. STMARL:用于合作交通灯控制的时空多智能体强化学习方法