1. 项目概述:为什么Niagara粒子性能优化是技术美术的必修课?
在虚幻引擎项目里,Niagara粒子系统是创造视觉奇观的利器,从漫天飞舞的魔法光点,到爆炸后四散的碎片,再到环境中的雾气尘埃,都离不开它。但很多技术美术和特效师都踩过同一个坑:在编辑器里预览时效果华丽流畅,一打包到真机或者复杂场景里,帧率就直线下降,甚至直接卡成幻灯片。这背后,往往不是GPU算力不够,而是对Niagara内部机制,特别是从Emitter Properties(发射器属性)到Renderer(渲染器)这一整条数据处理流水线的理解不够深入,配置不当导致的性能瓶颈。
我自己在多个中大型项目里负责特效性能攻坚,发现超过70%的粒子性能问题,根源都出在发射器设置和渲染器选型这两个环节。一个看似不起眼的“Spawn Rate”(生成速率)参数,或者一个误选的“SubImage”设置,就可能让CPU或GPU的负载飙升数倍。这篇文章,我就结合实战中踩过的坑和优化经验,带你系统性地拆解Niagara粒子性能优化的核心路径。我们不会空谈理论,而是聚焦于那些在项目后期最容易引发性能警报的具体模块和属性,提供一套从问题定位到解决方案的“避坑指南”。无论你是刚接触Niagara的新手,还是希望提升项目性能的老手,都能从中找到可以直接落地的优化策略。
2. 核心性能瓶颈分析与定位思路
在动手优化之前,盲目调整参数就像蒙着眼睛修车。我们必须先建立清晰的性能分析思路,知道该看哪里,以及数据意味着什么。
2.1 CPU与GPU开销的初步判断
粒子系统的性能开销主要分布在CPU和GPU两端,症状和成因截然不同。
CPU瓶颈的典型表现:
- 游戏逻辑线程(Game Thread)卡顿:表现为整体帧时间(Frame Time)不稳定,但GPU渲染时间(GPU Time)可能并不高。当你使用Unreal Insights或Stat Unit命令查看时,Game Thread的耗时波动很大。
- 常见成因:粒子数量过多(尤其是需要每帧进行复杂逻辑运算的粒子)、发射器生成(Spawn)逻辑复杂、频繁的事件(Event)触发与处理、不当的碰撞检测设置(如使用复杂的物理碰撞而非简单的碰撞查询)。
GPU瓶颈的典型表现:
- GPU渲染线程(Render Thread)或GPU本身卡顿:使用Stat Unit或GPU Profiler(如RenderDoc)查看时,GPU Time很高。在复杂场景中,即使粒子数量不多,也可能因为过度绘制(Overdraw)或复杂的材质/shader导致GPU不堪重负。
- 常见成因:单个粒子使用的材质过于复杂(多层混合、大量纹理采样、复杂光照计算)、粒子数量巨大导致顶点/像素着色器负载过重、渲染器类型选择不当(如本应使用Ribbon却用大量Sprite模拟)、开启了不必要的后期处理效果(如每个粒子都接受动态光照和阴影)。
实操心得:优化第一步永远是“ profiling”(性能剖析)。不要凭感觉猜。在编辑器里,多使用
stat Niagara(查看Niagara自身开销)、stat Unit(查看线程开销)、以及stat SceneRendering(查看渲染开销)这几个命令。在打包后的版本中,务必使用Unreal Insights进行深度分析,它可以精确到每个发射器、每个模块的CPU/GPU耗时。
2.2 Niagara性能分析工具链详解
虚幻引擎为Niagara提供了强大的内置分析工具,但需要正确解读。
Niagara系统编辑器中的“性能”选项卡:
- 这是最直接的入口。在Niagara系统编辑器的右上角,找到“性能(Performance)”选项卡并启用它。
- 关键指标解读:
- Avg. Time (ms):该发射器平均每帧消耗的CPU时间(毫秒)。这是核心指标,数值越高,对CPU压力越大。通常需要关注排名靠前的发射器。
- Max. Time (ms):该发射器单帧最大消耗时间,用于发现峰值性能问题。
- Particle Count:平均粒子数量。结合时间消耗,可以评估每个粒子的CPU成本是否合理。
- Memory (KB):该发射器占用的内存。粒子数量多、属性复杂的发射器内存占用也高。
Unreal Insights 深度剖析:
- 这是项目级性能分析的黄金标准。通过命令行
-trace=niagara, cpu, gpu启动游戏并录制数据,然后在Unreal Insights中分析。 - 在“Timing Insights”视图中,可以展开“Niagara”轨道,看到每个Niagara系统、甚至每个发射器在每一帧的CPU执行耗时柱状图。颜色越深(红/黄),耗时占比越高。
- 在“GPU”视图中,可以查看GPU端的耗时,分析是否是渲染器或材质导致了瓶颈。
- 这是项目级性能分析的黄金标准。通过命令行
控制台命令的灵活运用:
stat Niagara: 显示所有活动的Niagara系统和发射器的汇总信息,包括粒子总数、内存使用等。stat NiagaraDetailed: 显示更详细的信息,包括每个发射器的模拟(Simulation)和渲染(Render)耗时。这对于区分CPU模拟开销和GPU渲染开销非常有用。stat NiagaraMemory: 显示Niagara系统的内存使用情况。
避坑指南:分析时一定要在目标平台(如PC、主机、移动设备)上运行,并且场景要尽可能接近真实游戏状态(如相同的视口、相同的特效播放频率)。在编辑器单独预览一个特效和它在复杂关卡中运行,性能表现可能天差地别。
3. Emitter Properties(发射器属性)的精细化调优
发射器属性是粒子行为的“总开关”,很多全局设置在这里,它们对性能的影响是基础性的。
3.1 生命周期与生成率:控制粒子数量的源头
在发射器属性的“Emitter Properties”面板中,Spawn Rate(生成率)和Lifetime(生命周期)共同决定了场景中同时存活的粒子最大数量。这是影响性能最直接的参数。
- 计算公式与影响:
最大粒子数 ≈ Spawn Rate * Lifetime。例如,每秒生成100个粒子(Spawn Rate=100),每个粒子存活2秒(Lifetime=2),那么稳态下场景中大约有200个粒子。这个数量直接乘以每个粒子的计算和渲染成本,就是总开销。 - 优化策略:
- 动态生成率(Dynamic Spawn Rate):不要总是使用固定值。利用Niagara的参数绑定功能,将
Spawn Rate绑定到一个曲线或由游戏事件(如距离、伤害值)驱动的动态变量上。例如,距离摄像机远的特效,可以降低其生成率。 - 使用“Burst”(爆发)替代持续生成:对于瞬间效果(如击中火花、爆炸闪光),优先使用“Burst”列表来一次性生成一批粒子,而不是让发射器持续运行。这能显著减少不必要的持续计算。
- 合理设置生命周期:在满足视觉效果的前提下,尽可能缩短粒子生命周期。一个存活5秒的淡出粒子,其最后2秒可能几乎看不见,但却仍在参与计算和渲染。可以考虑使用
Kill Particles模块,在粒子透明度低于某个阈值或速度接近零时直接销毁它。
- 动态生成率(Dynamic Spawn Rate):不要总是使用固定值。利用Niagara的参数绑定功能,将
3.2 模拟与渲染目标定位:CPU与GPU的职责划分
在“Simulation Target”(模拟目标)和“Renderer Simulation Target”(渲染器模拟目标)这两个下拉菜单中,选择正确的目标至关重要。
- Simulation Target (模拟目标):
- CPU:粒子的位置、速度、颜色等属性的计算在CPU上进行。这是默认选项,兼容性好,便于与游戏逻辑交互(如事件、碰撞查询)。
- GPU:将粒子模拟完全转移到GPU计算。这是性能优化的王牌手段之一。GPU并行计算能力极强,可以轻松处理数万甚至数十万粒子的物理模拟,而CPU几乎零负担。
- Renderer Simulation Target (渲染器模拟目标):
- 这决定了渲染器从哪里读取粒子数据。如果“Simulation Target”是GPU,那么这里通常也应设为“GPU”,以避免在CPU和GPU之间拷贝数据(即“回读”,Readback),这种回读操作非常昂贵。
如何选择与避坑:
- 对于大量、行为规律(如受重力、噪声力影响)的粒子(如烟雾、尘埃、雨雪),强烈推荐使用
Simulation Target = GPU。你会在stat Niagara中看到CPU开销骤降。 - 重要限制:GPU模拟的粒子无法与场景进行复杂的CPU端碰撞检测(如Physics Collision),也无法直接触发需要CPU逻辑的Niagara事件(如
Generate Location Event)。它们通常使用“Depth Collision”等GPU友好的方式进行简单交互。 - 避坑操作:如果你为一个GPU模拟的发射器添加了
Collision模块(其默认是CPU碰撞),Niagara会给出警告,并且该模块会失效。你需要使用GPU Collision相关的数据接口或模块。 - 混合模式:一个系统内可以同时存在CPU和GPU模拟的发射器。例如,一个爆炸效果,核心的烟雾用GPU模拟,而少数需要与场景物体精确交互的碎片用CPU模拟。
3.3 固定边界与剔除:减少无效计算
Fixed Bounds(固定边界)是一个常被忽略但极其重要的优化选项。
- 原理:默认情况下,Niagara会动态计算粒子系统的边界框(Bounds)。这个计算本身有开销,而且动态边界可能导致渲染管线进行不必要的剔除判断。当你明确知道粒子效果的活动范围时(比如一个固定在角色手中的火焰特效),可以手动设置一个固定的、紧凑的边界框。
- 设置方法:在发射器属性中勾选
Use Fixed Relative Bounds,然后设置Fixed Bounds的Min和Max值。这个边界是相对于发射器本地空间的。 - 性能收益:
- 减少CPU计算:免去了每帧重新计算边界框的开销。
- 优化渲染剔除:渲染引擎(如遮挡剔除、视锥体剔除)使用这个边界框来判断整个粒子系统是否可见。一个紧凑的固定边界比一个松散的动态边界更容易被正确剔除,从而避免系统不可见时GPU仍在为其准备渲染数据。
- 避免闪烁:动态边界在粒子突然移动到远处时可能会剧烈变化,导致剔除系统判断失误,引起粒子闪烁。固定边界则更稳定。
注意事项:固定边界一定要设置得足够大,以包含粒子生命周期内所有可能到达的位置,否则粒子在边界外的部分会被“裁剪”掉,无法渲染。建议在特效设计完成后,在预览窗口中观察粒子的最大活动范围,并以此为基础适当放宽一些作为固定边界。
4. 核心模块的配置陷阱与优化技巧
发射器堆栈中的每个模块都可能是性能杀手,下面重点分析几个高频“案发地”。
4.1 Spawn(生成)模块:避免生成风暴
Spawn Rate模块是性能的“水龙头”。除了控制速率,其Burst(爆发)功能如果用不好,会造成瞬时卡顿。
- Burst瞬时压力测试:一个设置
Burst Count=1000的爆发,意味着系统试图在一帧内生成1000个粒子。如果每个粒子的初始化计算复杂,这一帧的CPU峰值会非常高。在移动端或低端PC上,这足以造成一次明显的帧率骤降。 - 优化方案:
- 将大爆发拆分为小爆发:使用
Spawn Burst Instantaneous模块,但将Burst Count设为100,然后通过循环或多个轻微延迟的小爆发来达成总数。可以结合Delay模块或通过Event来分帧触发。 - 使用“Rate”平滑生成:对于非必须的瞬间效果,考虑用较高的
Spawn Rate持续极短时间来模拟爆发,这比单帧巨量爆发对帧时间的冲击更平滑。
- 将大爆发拆分为小爆发:使用
4.2 Update(更新)模块:简化每帧运算
粒子存活期间的每一帧,Particle Update组中的模块都会执行。这里的优化在于做“减法”。
- 审查每一个“Force”(力)模块:
Drag(阻力)、Gravity(重力)、Vortex(漩涡)、Curl Noise(旋度噪声)等。每个力模块都意味着每帧对每个粒子进行一次向量运算。问自己:这个力效果是否肉眼可见?Curl Noise的强度是否过高?能否用更简单的Constant Acceleration(恒定加速度)替代复杂的噪声力? Collision(碰撞)模块的代价:这是CPU开销的大户。它需要每帧对每个粒子进行物理场景查询。- 优化策略1:降低频率。使用
Collision模块的Collision Interval(碰撞间隔)参数。设置为0.1秒意味着每秒只检测10次,而不是60次(假设60帧),能大幅降低开销。 - 优化策略2:简化碰撞体。在
Collision Settings中,使用简单的Collision Shape(如Sphere, Box)而非复杂的Mesh。并尽可能使用World Static等简单的碰撞通道,避免与动态物体进行复杂交互。 - 优化策略3:考虑GPU碰撞。对于GPU模拟的粒子,使用
Depth Collision(深度碰撞)来实现粒子与场景深度的交互,这是一个纯GPU方案,没有CPU开销。
- 优化策略1:降低频率。使用
4.3 Event(事件)与数据接口:谨慎使用重型功能
事件和数据接口功能强大,但滥用会导致性能耦合和额外开销。
- 事件(Event)的性能成本:事件是发射器或粒子间通信的桥梁,但其触发、传递和处理都需要CPU调度。如果一个事件每帧被成千上万的粒子触发,监听该事件的处理器就需要处理成千上万次回调。
- 建议:对事件使用“过滤”条件。例如,不是每个粒子死亡时都触发事件,而是只有特定类型(如速度大于某值)的粒子死亡时才触发。或者,考虑使用更轻量的方式,如通过共享参数(
User.参数)来传递状态信息。
- 建议:对事件使用“过滤”条件。例如,不是每个粒子死亡时都触发事件,而是只有特定类型(如速度大于某值)的粒子死亡时才触发。或者,考虑使用更轻量的方式,如通过共享参数(
- 数据接口(Data Interface)的加载开销:例如
Grid2D(二维网格)或NeighborGrid3D(三维邻居网格)数据接口,它们用于实现粒子间的空间查询(如聚集、排斥),功能强大,但计算复杂度是O(N²)或更高。- 建议:严格控制使用这类数据接口的粒子数量。可以将其应用在少数“领导粒子”上,而不是全体粒子。或者,显著增大网格的单元格大小(
Cell Size),减少需要计算的邻居数量。
- 建议:严格控制使用这类数据接口的粒子数量。可以将其应用在少数“领导粒子”上,而不是全体粒子。或者,显著增大网格的单元格大小(
5. Renderer(渲染器)选型与参数调优
渲染器决定了粒子如何被绘制到屏幕上,是GPU开销的主要来源。选错渲染器,前面所有的CPU优化都可能前功尽弃。
5.1 渲染器类型选择:对症下药
Niagara提供了多种渲染器,它们的性能特征差异巨大。
| 渲染器类型 | 典型用途 | 性能特点 | 适用场景与避坑 |
|---|---|---|---|
| Sprite Renderer(精灵渲染器) | 2D平面贴图粒子(烟雾、火焰、魔法光点) | 性能最优。每个粒子由1个四边形(2个三角形)构成,顶点数固定且少。 | 绝大多数粒子效果的首选。避免用它来模拟需要体积感或复杂变形的效果。 |
| Ribbon Renderer( ribbon渲染器) | 轨迹、光束、闪电、拖尾 | 性能取决于Ribbon Link Order(分段数)。分段越多,形成的带状网格越平滑,但顶点和三角形数也线性增长。 | 用于连续的轨迹效果时效率远高于用大量Sprite粒子模拟。关键优化点是减少不必要的分段数,在视觉可接受范围内使用最低值。 |
| Mesh Renderer(网格体渲染器) | 使用3D模型作为粒子(碎片、飞鸟、树叶) | 性能开销最大。每个粒子实例化一个完整的静态网格体,其顶点数、三角形数、材质复杂度直接决定开销。 | 绝对不要滥用。仅用于必须展现三维形状且数量很少的粒子(如大型爆炸中的少数碎片)。务必使用LOD(细节层次)最简单的网格,并使用最简单的材质。 |
| Light Renderer(光源渲染器) | 让粒子作为动态光源 | 开销极高。每个光源粒子都会增加渲染管线的光照计算负担,特别是动态阴影。 | 在移动平台或性能敏感场景中尽量避免。如果必须使用,严格控制数量(个位数),并关闭阴影投射(Cast Shadows)。 |
| Decal Renderer(贴花渲染器) | 在场景表面投射贴花(如弹孔、血迹) | 开销取决于贴花大小、覆盖范围和材质复杂度。大量重叠的贴花会导致Overdraw(过度绘制)。 | 注意管理贴花的生命周期和淡出,避免永久留存。使用简单的材质,并利用贴花衰减(Decal Fade)来平滑边缘。 |
核心原则:能用Sprite解决的,绝不用Mesh;能用Ribbon高效表达的,绝不用一堆Sprite去拼。
5.2 材质与纹理:GPU的沉重负担
即使选择了正确的渲染器,一个复杂的材质也能瞬间拖垮GPU。
纹理图集(Texture Atlas/SubImage)的陷阱:
- 功能:允许一个粒子在其生命周期内播放一个序列帧动画(如爆炸动画)。
- 性能代价:每帧,每个粒子都需要根据当前生命期计算应该显示图集中的哪一帧(SubImage Index)。这个计算本身不重,但它破坏了GPU的实例化(Instancing)优化。GPU喜欢绘制大量完全相同的物体,而每个粒子显示不同子图像时,它们就被认为是“不同”的,实例化批次会中断,导致多次Draw Call。
- 优化建议:
- 评估必要性:真的需要序列帧动画吗?能否用纹理滚动(Panner)、缩放、旋转等更GPU友好的方式来模拟动态效果?
- 减少子图像数量:如果必须用,尽可能减少纹理图集的行列数(即总帧数)。一个4x4的图集(16帧)比8x8(64帧)对实例化的破坏更小。
- 考虑替代方案:对于简单的两三种状态变化,可以用粒子颜色(Color)或动态材质参数(Dynamic Material Parameter)来驱动,而不是切换子图像。
材质复杂度的控制:
- 精简材质节点:检查粒子材质,移除所有不必要的纹理采样、复杂数学运算和高级光照模型。粒子材质通常不需要法线贴图、高光、复杂反射。
- 慎用“Translucent”(半透明)混合模式:半透明物体渲染顺序依赖从后往前排序,且无法写入深度缓冲区,会导致大量Overdraw和性能下降。优先使用
Additive(叠加)或Modulate(调制)等更高效的混合模式。 - 关闭阴影:在材质或渲染器设置中,确保
Cast Shadows选项被关闭。动态粒子投射阴影的性价比极低。
5.3 渲染器特定参数优化
- Sprite Renderer:
Alignment(对齐方式):默认的Velocity(速度对齐)或Custom Alignment(自定义对齐)需要每帧为每个粒子计算旋转矩阵,而Screen(屏幕对齐)或Fixed(固定对齐)的计算更简单。在效果允许的情况下,选择计算量小的对齐方式。SubImage设置:如上所述,谨慎使用。SubImage Size设置必须与纹理图集的实际布局完全匹配,否则会导致采样错误和性能浪费。
- Ribbon Renderer:
Ribbon Link Order(链接顺序)与Max Tessellation(最大细分):这两个参数共同控制带状网格的细节程度。在预览效果时,逐步调低这两个值,直到看到明显的棱角为止,然后稍微回调一点作为最终值。对于远处的拖尾,这个值可以设得更低。Draw Direction(绘制方向):设置正确可以避免不必要的三角形翻转。
6. 实战优化流程与常见问题排查
掌握了各个部分的优化点后,我们需要一套系统的实战流程来应用它们。
6.1 五步性能优化工作流
- 建立性能基线:在目标平台上,运行包含待优化特效的典型场景,使用Unreal Insights记录性能数据。记下整体的帧时间、Game Thread/GPU Time,以及
stat NiagaraDetailed中该特效系统的具体耗时。 - 定位主要瓶颈:
- 如果
stat Niagara显示某个发射器的Simulation(模拟)耗时很高 -> 重点检查发射器属性(Simulation Target)和Update模块。 - 如果
Simulation不高但Render(渲染)耗时很高 -> 重点检查渲染器类型和粒子材质。 - 如果GPU Time整体很高,且Overdraw严重 -> 重点检查粒子数量、半透明混合和渲染器复杂度。
- 如果
- 实施针对性优化:根据定位结果,按照前面章节的指南,从发射器属性到渲染器参数,逐一进行修改。一次只修改一个变量,并观察性能变化,以确定该变量的影响权重。
- 验证视觉质量:每做一次优化,都要在目标平台上实时预览视觉效果。优化的目标是在尽可能保持视觉表现的前提下提升性能,而不是单纯地削减效果。有时需要和美术师沟通,找到质量和性能的平衡点。
- 回归测试与迭代:优化完成后,再次运行完整的性能测试,与基线数据对比。确保优化没有引入新的问题(如闪烁、裁剪错误)。将优化后的配置保存为新的资产版本。
6.2 常见性能问题速查与解决方案
下表汇总了实战中最常遇到的一些性能问题及其排查思路:
| 问题现象 | 可能原因 | 排查与解决步骤 |
|---|---|---|
| 游戏运行时整体卡顿,Stat Unit显示Game Thread耗时高 | 1. 粒子总数过多。 2. 存在CPU模拟的复杂发射器(如带物理碰撞)。 3. 事件处理过于频繁。 | 1. 运行stat Niagara,查看粒子总数和CPU耗时最高的发射器。2. 尝试将该发射器的 Simulation Target改为GPU(如果功能允许)。3. 检查并优化 Spawn Rate和Lifetime。4. 审查 Collision模块和Event处理器。 |
| GPU负载很高,画面渲染慢 | 1. 使用了Mesh Renderer或Light Renderer。2. 粒子材质过于复杂。 3. 大量半透明粒子导致严重Overdraw。 4. 使用了纹理图集,破坏了实例化。 | 1. 检查渲染器类型,尝试换用Sprite Renderer。2. 使用Shader复杂度视图( Shader Complexity)查看材质开销,简化材质。3. 将材质混合模式从 Translucent改为Additive。4. 评估是否必须使用子图像动画。 |
| 特效播放时,出现单帧的严重卡顿(Hitch) | 1. 单帧内触发了粒子Burst,数量巨大。2. 粒子系统首次加载时编译Shader或加载资源。 | 1. 检查发射器的Burst设置,将大爆发拆分为多帧小爆发。2. 在关卡流式加载或游戏开始时,预加载(Preload)粒子系统资产。 |
| 粒子在远处仍然有很高开销 | 1. 未启用或未正确设置LOD(细节层次)。 2. 粒子系统的动态边界过大,导致剔除失效。 | 1. 在Niagara系统资产中,设置基于距离或屏幕大小的LOD,在远处降低Spawn Rate、减少粒子大小或禁用复杂模块。2. 为发射器设置紧凑的 Fixed Bounds。 |
| 移动设备上发热严重,耗电快 | 综合了以上所有CPU和GPU的高开销问题,在移动端有限的硬件上被放大。 | 1.强制使用GPU模拟处理大量粒子。 2.大幅削减粒子数量,追求“少而精”的设计。 3.使用极简的材质和纹理(甚至单色)。 4.彻底禁用所有Mesh、Light渲染器。 5.积极使用LOD,确保在中远距离特效极度简化。 |
6.3 高级技巧:Niagara性能分析与调试的深层手段
除了上述通用方法,还有一些更深层的调试手段可以帮助定位疑难杂症。
- 使用“Debug Draw”功能:在发射器属性或模块中,经常可以看到
Debug Draw选项。开启后,可以在视口中看到力的方向、碰撞体、事件触发位置等可视化信息。这对于验证Collision模块的范围是否过大、Force模块的方向是否正确非常有用,避免因参数错误导致的无用计算。 - 剖析HLSL脚本:对于自定义模块或复杂的动态输入,其内部的HLSL代码可能效率不高。虽然Niagara提供了可视化脚本,但底层仍是HLSL。如果你熟悉HLSL,可以尝试优化其中的循环或数学运算。例如,将一些每粒子计算改为每发射器计算一次然后共享。
- 资源管理与池化:频繁创建和销毁Niagara系统组件(
UNiagaraComponent)会产生开销。对于需要频繁播放的通用特效(如击中火花、脚步声尘埃),应该使用对象池(Object Pooling)进行管理。这不是Niagara内部的设置,而是需要在游戏逻辑代码层面实现的优化,但对于整体性能至关重要。
性能优化是一个永无止境的权衡过程,核心思想始终是“将计算资源用在刀刃上”。对于Niagara粒子系统,从Emitter Properties这个源头控制好粒子的“生”与“死”,在模拟阶段选择正确的计算路径(CPU/GPU),在渲染阶段为效果选择最经济的“外衣”(Renderer),最后再通过工具和数据驱动决策,你就能在视觉表现和运行效率之间找到那个完美的平衡点。记住,最好的优化往往是设计阶段的决策,在构思一个华丽特效的同时,就在心里为它的性能成本留好预算。