Unity大场景性能优化:InstantOC动态遮挡剔除实战指南

📅 2026/7/9 22:35:12 👁️ 阅读次数 📝 编程学习
Unity大场景性能优化:InstantOC动态遮挡剔除实战指南

1. 项目概述:为什么你的Unity大场景总是卡顿?

如果你做过Unity的中大型项目,尤其是开放世界、MMO或者高精度室内场景,一定遇到过这个头疼的问题:编辑器里跑得好好的,一打包出来,帧率就直线下降,手机发烫,PC风扇狂转。你检查了Draw Call,优化了贴图,合并了网格,但性能瓶颈似乎无处不在。很多时候,问题的根源不在于你画了什么,而在于你画了太多“看不见”的东西。

这就是遮挡剔除(Occlusion Culling)要解决的核心问题。传统的Unity静态遮挡剔除需要在编辑器里烘焙,对于动态变化的场景束手无策。而InstantOC(Instant Occlusion Culling)这款插件,就是为了解决“即时”、“动态”的遮挡计算而生的。它能在运行时实时判断摄像机视野内哪些物体被其他物体挡住,从而阻止GPU去渲染这些根本不会出现在屏幕上的像素,直接从渲染管线源头“掐掉”不必要的开销。

我经历过一个项目,一个复杂的城市街道场景,未优化前,即使用高端显卡,在街道拐角处帧率也会骤降到40帧以下。引入并正确配置InstantOC后,同场景同视角帧率稳定在80帧以上,GPU负载下降了近40%。这不仅仅是数字游戏,它直接决定了你的项目能否在目标设备上流畅运行,特别是面向移动端或VR设备时,每一帧的渲染时间都弥足珍贵。

本指南将不仅仅教你如何安装和使用InstantOC,更会深入其工作原理,分享我在多个项目中实战总结出的配置技巧、避坑经验和性能调优策略。无论你是正在被场景性能困扰的开发者,还是希望提前为项目架构做好性能储备的技术负责人,这篇文章都能提供从理论到实践的完整参考。

2. InstantOC核心原理与方案选型

在决定使用任何优化方案前,理解其背后的原理至关重要。这能帮助你在遇到问题时快速定位,也能让你更明智地进行配置和取舍。

2.1 遮挡剔除的本质:渲染管线的“守门人”

现代GPU的渲染管线非常强大,但它的工作模式是“老实听话”:你提交多少绘制指令(Draw Call),告诉它画哪些三角形(网格),它就会尽力去画,而不会主动思考“这个三角形画出来会不会被挡住”。从CPU提交数据到GPU光栅化、像素着色,每一步都需要消耗时间和资源。

遮挡剔除的核心思想,就是在CPU端提前进行一次粗粒度的可见性判断。它通过一种算法,快速计算出在当前的摄像机位置和视角下,场景中哪些物体的包围体(通常是AABB,即轴对齐包围盒)完全位于其他物体的后方,从而在提交渲染命令之前就将它们从渲染列表中剔除。

Unity内置了两种遮挡剔除:

  1. 静态遮挡剔除(Static Occlusion Culling):需烘焙。将场景和静态物体体素化,预计算可见性数据。优点是一劳永逸,运行时开销极低。缺点是只对标记为Occluder StaticOccludee Static的静态物体有效,无法处理动态物体。
  2. 硬件遮挡查询(Hardware Occlusion Query):GPU反馈机制。让GPU绘制物体的简化版(如包围盒),然后查询其是否通过深度测试。精度高,但查询带来的GPU-CPU同步会造成性能波动,不适合大量物体。

InstantOC属于软件遮挡剔除的范畴。它在CPU上,通过维护一个场景的空间数据结构(通常是动态八叉树),并利用摄像机的视锥体和对场景的射线投射(Raycast)或体积查询,来实时计算遮挡关系。

2.2 InstantOC的工作流解析

InstantOC的“即时”体现在它的动态更新上。其工作流程可以简化为以下几个步骤:

  1. 场景组织:运行时,InstantOC会将所有需要参与遮挡计算的游戏对象(挂载了特定组件)的动态包围盒信息,组织进一个高效的空间数据结构中。这个结构使得它能快速回答“在某个区域附近有哪些物体”这类空间查询。
  2. 潜在可见集(PVS)计算:每一帧(或每N帧,可配置),插件会以摄像机为原点,向视锥体近平面上的多个采样点(或使用保守的视锥体扩展)发射射线,或进行视锥体层次遍历。这个过程旨在快速找到一个初步的、可能可见的物体集合。
  3. 层次深度剔除:对于PVS中的物体,InstantOC会进行层次化的深度测试。它并非逐个像素精确计算,而是利用物体包围盒的深度范围信息。如果一个物体A的最近点(包围盒距离摄像机最近的面)都比另一个物体B的最远点(包围盒距离摄像机最远的面)还要远,并且A在B的投影范围内,那么A很可能被B遮挡。这是一种保守但快速的判断。
  4. 渲染指令截断:经过上述计算,被判定为完全不可见的物体,其对应的Renderer组件会被InstantOC动态地禁用(renderer.enabled = false)。注意,物体本身(GameObject)依然是激活的,只是不被渲染。当摄像机移动,该物体变为可能可见时,Renderer又会被重新启用。

这种方法的优势在于完全在CPU端进行,避免了GPU查询的同步开销,并且能很好地处理动态移动的物体(只要它们的包围盒更新到空间数据结构中)。其性能开销主要取决于场景的复杂度(物体数量)、空间数据结构的更新频率以及遮挡计算的精度设置。

2.3 为何选择InstantOC?与其他方案的对比

当你的场景存在大量中远距离物体、复杂室内结构或密集植被时,就需要考虑动态遮挡方案。以下是常见方案的横向对比:

方案原理优点缺点适用场景
Unity静态烘焙编辑器预计算体素化可见性。运行时零开销,精度高。仅限静态物体,烘焙耗时,场景不能动态改变。静态建筑、固定地形。
硬件遮挡查询通过GPU反馈像素可见性。精度极高,能处理复杂形状。引入渲染延迟和波动,管理复杂,对透明物体不友好。PC/主机端,对遮挡精度要求极高的静态/少量动态场景。
软件门户系统手动设置“门户”区域定义可见性。确定性高,性能可控。需要大量手动设计和维护,不适用于开放自然场景。迷宫类游戏、设计规整的室内场景。
InstantOCCPU端动态空间查询与层次深度测试。支持动态物体,无需烘焙,配置相对简单,开销可控。CPU有一定开销,遮挡判断是保守估计(可能剔除不足)。开放世界、动态城市、VR场景、移动端复杂场景(需谨慎调参)。
LOD Group根据距离切换不同细节层次的模型。减少远处模型的渲染面数。不解决遮挡问题,只是减少单个物体的负担。与遮挡剔除互补使用,用于中远距离物体。
烘焙光照探针+遮挡结合光照和可见性优化。优化整体渲染管线。复杂度高,是综合方案而非单纯遮挡方案。追求极致画面与性能平衡的项目。

实操心得:不要指望一个方案解决所有性能问题。InstantOC通常与LOD Group组合使用,效果最佳。对于远处山脉,用LOD减少面数;对于被建筑遮挡的街区,用InstantOC直接不渲染。对于完全静态的大型背景(如远山天空盒),直接用静态烘焙可能更省心。

选择InstantOC的核心场景是:你的场景中有大量动态或半动态物体,且它们之间存在明显的、可能变化的遮挡关系。例如,一个第三人称游戏在树林中穿梭,角色身后的树木和石块应该被剔除;一个RTS游戏中,被建筑挡住的单位不应被渲染。

3. 插件集成与核心组件详解

理解了原理,我们开始动手。InstantOC的集成非常 straightforward,但正确使用其各个组件是发挥效力的关键。

3.1 安装与初始配置

假设你已经通过Asset Store或包文件将InstantOC导入项目。首先,你需要创建一个全局的管理器。

  1. 创建InstantOC管理器:在场景中创建一个空游戏对象,命名为“InstantOcclusionManager”或类似。然后为其添加InstantOC脚本组件。这个组件是插件运行的总控中心。
  2. 关键参数解析(以常见版本为例,具体参数名可能随版本更新):
    • Camera:拖入主摄像机。插件将基于此摄像机的视角进行计算。
    • View Cell Size:视图单元大小。这是空间划分的粒度。值越小,遮挡精度越高,但CPU开销越大。对于大型开放世界,可以从50.0开始尝试;对于室内场景,可以设为5.0或更小。这是一个需要反复测试权衡的核心参数。
    • Backface Threshold:背面剔除阈值。当物体背面(法线背离摄像机)的面积百分比超过此值时,该物体会被优先考虑剔除。对于墙壁、地面等大面积单面物体,提高此值(如设为0.99)可以提前剔除它们,当摄像机进入其内部时(比如穿墙BUG),它们才会被渲染。合理设置能显著提升室内场景性能。
    • Precise Calculation:精确计算。启用后,会使用更精确但更耗时的射线投射来进行遮挡测试。通常只在最终质量优化时,对少数仍有问题的物体开启。
    • Frames Per Calculation:每N帧计算一次。设置为1表示每帧都更新,最及时但开销最大。对于移动缓慢的摄像机(如步行模拟),可以设置为23,用轻微延迟换取显著的CPU耗时降低。

3.2 让物体参与遮挡:Occluder与Occludee

不是所有物体都需要或应该参与遮挡计算。InstantOC通过两个核心组件来区分物体的角色:

  1. InstantOccluder:挂载在遮挡其他物体的物体上。例如:厚实的墙壁、巨大的岩石、建筑主体。它们是遮挡关系的“提供者”。

    • 作用:告诉系统,这个物体的形状可以用来判断是否挡住了后面的东西。
    • 配置建议:尽量将其挂在形状规整、体积较大、不透明的静态或动态物体上。对于非常复杂网格的物体,插件可能会使用其简化包围盒(如AABB)来进行计算,此时其遮挡效果是“保守”的(即实际遮挡范围可能小于物体本身)。
  2. InstantOccludee:挂载在可能被遮挡的物体上。例如:房间内的家具、街上的NPC、树木后面的小物件。它们是遮挡关系的“接受者”。

    • 作用:告诉系统,这个物体需要被检查是否被其他Occluder挡住。
    • 重要属性Min Occludee Area。这是一个非常实用的优化参数。它定义了物体屏幕空间面积的最小阈值。如果一个Occludee在屏幕上的投影面积小于这个值(比如远处一个只有几个像素的小石头),即使它在理论上可见,InstantOC也可能选择忽略它,不将其纳入精细的遮挡计算,因为渲染它对帧时间影响微乎其微,但计算它是否被遮挡却需要成本。将这个值设置为一个合理的正数(如0.10.05),可以大幅减少需要计算的物体数量,提升效率。

注意事项:一个物体可以同时是OccluderOccludee吗?答案是肯定的,而且这很常见。比如一棵树,它既能遮挡住树后面的其他树木(作为Occluder),同时它自己也可能被更近的山坡或建筑遮挡(作为Occludee)。同时挂载两个组件即可。

3.3 动态物体的处理策略

对于会移动、旋转、缩放(非均匀缩放需注意)的物体,InstantOC需要知道其包围盒的变化。

  • 自动更新InstantOccluderInstantOccludee组件通常有选项(如Update Dynamic)来控制是否每帧自动更新其包围盒。对于持续运动的物体(如移动的平台),需要开启此选项。
  • 手动更新:对于位置变化不频繁的物体(如被玩家推开后静止的门),可以在物体变换完成后,调用组件上的UpdateBounds()或类似方法,以更高效的方式通知InstantOC更新空间数据结构。
  • 性能权衡:动态物体会导致InstantOC内部的空间索引结构需要局部更新或重构,带来额外开销。如果一个物体移动频率很低,将其设为静态(在Unity中标记为Static,并在InstantOC中不开启动态更新)是更好的选择。插件通常能智能处理静态物体,将其纳入更高效的管理流程。

4. 实战配置:从零构建一个优化场景

让我们通过一个具体的案例,将上述知识串联起来。假设我们正在优化一个“中世纪小镇”的场景,包含中心广场、周边房屋、街道和移动的NPC。

4.1 场景分析与组件分配

首先,关闭所有优化,在目标平台(如PC Standalone)上运行,使用Unity Profiler的Rendering区域观察BatchesSetPass Calls,并用帧调试器(Frame Debugger)查看每一帧实际绘制了什么。记录下性能瓶颈视角(比如从广场看向密集房屋的街道)。

然后,开始分配组件:

  1. 大型静态遮挡物(Occluder)

    • 所有房屋的外墙、厚实的屋顶:挂载InstantOccluder。这些是主要的遮挡提供者。
    • 广场中央的大型雕像、喷泉基座:挂载InstantOccluder
    • 环绕小镇的城墙、山体:挂载InstantOccluder
    • 配置:对于这些完全静态的物体,确保它们在Unity的Static Flags中至少勾选了Occluder Static(如果也使用静态光照,则勾选Contribute GI)。在InstantOC组件上,关闭动态更新。
  2. 中小型可遮挡物(Occludee)

    • 房屋内的桌椅、橱柜、床:挂载InstantOccludee。设置合理的Min Occludee Area(例如0.03)。
    • 街道上的摊位、路牌、灯笼:挂载InstantOccludee
    • 树木同时挂载InstantOccluderInstantOccludee。因为树木之间会相互遮挡。
    • NPC角色:挂载InstantOccludee,并开启动态更新。因为NPC会移动。
  3. 特殊处理对象

    • 透明物体(窗户、粒子效果):谨慎处理。标准的深度剔除对半透明物体排序可能产生错误。通常,不给透明物体添加InstantOC组件,或者仅作为Occludee并仔细测试。因为透明渲染需要混合,强行剔除可能导致视觉错误。
    • 天空盒、远山背景:这些是永远不被遮挡的。不应添加任何InstantOC组件。对它们使用传统的Layer Culling Distance(按层设置裁剪距离)或者LOD(Level of Detail)来优化。
    • 小到可以忽略的物体(如地上的小石子、草叶):与其用InstantOC,不如使用Unity的Draw Call Batching(合批)或GPU Instancing进行优化。给大量微小物体添加Occludee组件可能得不偿失。

4.2 管理器参数调优

创建InstantOcclusionManager并挂载脚本。

  • Camera:赋值主摄像机。
  • View Cell Size:这是一个小镇,建筑规模中等。可以从10.0开始测试。在游戏运行时,观察从街道一头看向另一头的性能。如果帧率提升不明显但CPU开销(InstantOC.Calculate或类似函数在Profiler中的耗时)很高,可以尝试增大到15.0以降低精度和开销。如果发现明显的剔除错误(该消失的物体闪烁出现),则减小到7.05.0
  • Backface Threshold:由于有很多房屋(内部不可见),可以设置为0.95。这样当摄像机在屋外时,房屋的背面(内部)会被高效剔除。
  • Frames Per Calculation:设置为2。因为这是一个步行模拟游戏,摄像机移动速度不快,每两帧更新一次遮挡关系足以提供平滑的体验,并能节省近一半的遮挡计算开销。
  • Precise Calculation:初始关闭。在后续微调阶段,如果发现某个重要的物体(如任务关键道具)在不该出现的时候被错误剔除,可以尝试仅对该物体的InstantOccludee组件开启本地精确计算选项,而不是全局开启。

4.3 分层管理与裁剪距离结合

Unity的Layer系统可以和InstantOC协同工作,实现更精细的控制。

  1. 创建不同的Layer,如StaticOccluder,DynamicOccludee,Foliage,SmallDetails
  2. 将对应的物体分配到这些Layer。
  3. InstantOcclusionManager中,可以设置针对不同Layer的裁剪距离。例如,将SmallDetails层的最大裁剪距离设置得近一些,超过这个距离的物体无论是否被遮挡都不渲染。这相当于在InstantOC之前加了一道粗筛。
  4. 你甚至可以配置InstantOC只对特定的Layer进行计算,忽略那些你确定不需要参与遮挡的层(如UI,Effects)。

5. 性能分析与调试技巧

集成和配置只是第一步,真正的功夫在于调试和优化。盲目使用插件有时甚至会降低性能。

5.1 使用Profiler精准定位开销

Unity Profiler是你的最佳伙伴。在Profiler窗口中,重点关注:

  • CPU Usage:寻找名为InstantOCOcclusion或插件开发商自定义的更新函数(如InstantOcclusionManager.Update)。观察其每帧耗时。一个优化良好的场景中,InstantOC的CPU耗时应稳定在每帧1-3毫秒以内(针对中等复杂度场景)。如果超过5ms,就需要审视你的配置(View Cell Size是否太小?动态物体是否太多?)。
  • Rendering:观察BatchesSetPass Calls在启用InstantOC前后的变化。成功的优化应该能看到在遮挡严重的视角下,这两个数值显著下降。如果下降不明显,说明遮挡物(Occluder)设置不足或遮挡计算未生效。
  • GPU:观察GPU耗时是否降低。由于减少了渲染负担,GPU耗时应有可测量的下降。

5.2 视觉调试与问题排查

InstantOC通常提供调试视图(Debug Visualization),在编辑器和运行时都可以开启。

  • 线框/着色模式:开启后,屏幕上会以不同颜色显示物体状态。例如:
    • 绿色:完全可见。
    • 红色:被完全遮挡。
    • 蓝色/黄色:可能可见或正在计算中。
  • 使用调试视图,你可以直观地发现
    1. 剔除不足:一个明明被墙挡住的物体,在调试视图里还是绿色的。这说明要么那面墙没有设置为Occluder,要么View Cell Size过大导致精度不够,插件“看”不到那面墙的遮挡能力。
    2. 过度剔除:一个应该看到的物体(比如墙角的宝藏箱)显示为红色,在游戏中闪烁或消失。这通常是因为Backface Threshold设置过高,或者该物体的包围盒(Bounds)计算不准确(对于非规则网格,可能需要手动调整包围盒)。
    3. 动态物体更新问题:一个移动的NPC在调试视图中颜色更新滞后,出现“拖影”。这可能是Frames Per Calculation设置过高,或者动态物体的包围盒更新没有正确触发。

5.3 常见问题与解决方案实录

以下是我在项目中实际遇到过的坑和解决办法:

问题1:启用InstantOC后,帧率不升反降。

  • 排查:Profiler显示InstantOC.Calculate耗时极高(>10ms)。
  • 原因:场景中挂载了InstantOccludee的物体数量过多(例如,给每一片草、每一颗石子都挂了)。或者View Cell Size设置得过小,导致空间划分过细,查询计算量爆炸。
  • 解决
    1. 严格按“3.1”和“4.1”的原则分配组件,只给中型及以上、可能被遮挡的物体挂Occludee
    2. 调大View Cell Size,牺牲一点精度换取性能。
    3. 利用Min Occludee Area,过滤掉屏幕上的微小物体。
    4. 考虑使用LOD,在物体很远时直接替换为更简化的代理体(Proxy),这个代理体再参与遮挡计算。

问题2:物体在摄像机移动时频繁闪烁(Flickering)。

  • 排查:调试视图中发现物体在红绿之间快速切换。
  • 原因:这是“Z-fighting”在遮挡剔除上的表现。当两个物体的深度(Z值)非常接近,或者遮挡计算的精度不足以稳定判断前后关系时,就会每帧得出不同的可见性结果。
  • 解决
    1. 检查并消除模型中真正的Z-fighting(重叠的面)。
    2. 适当减小View Cell Size,提高计算精度。
    3. 对于有问题的特定物体,在其InstantOccludee组件上增加一个“延迟切换”的容差值,或者启用Precise Calculation(仅对该物体)。
    4. 如果物体是薄片(如公告板Billboard),考虑手动稍微放大其碰撞体或包围盒,给计算留出容错空间。

问题3:摄像机进入物体内部(如穿墙)时,外部世界变黑或消失。

  • 排查:摄像机进入一个设置为Occluder的物体(如房屋)内部。
  • 原因Backface Threshold设置过高(如1.0),导致当摄像机位于物体“内部”时,系统认为该物体的所有面都是背面(因为法线朝外),从而将其自身也剔除了,而它原本是遮挡外部世界的主要物体。
  • 解决:将Backface Threshold设置为一个小于1的值,例如0.950.9。这样即使大部分面是背面,只要有一小部分不是,物体就不会被剔除。或者,对于这类“容器”型物体,可以将其设置为只对特定层(Occludee Layer Mask)生效,而不剔除自身所在层。

问题4:移动平台(Android/iOS)上效果不佳或崩溃。

  • 排查:PC上运行良好,打包到手机后卡顿或闪退。
  • 原因:移动设备CPU性能弱,且InstantOC的计算是单线程的(取决于版本),可能成为瓶颈。或者内存使用过高。
  • 解决
    1. 大幅降低精度:将View Cell Size增加到20.0甚至30.0。将Frames Per Calculation增加到34
    2. 减少计算对象:重新评估,只对最大的建筑和最重要的动态物体使用遮挡。大量的小物件通过合批优化。
    3. 分帧计算:检查插件是否有“异步计算”或“分帧更新”选项。将遮挡计算分摊到多帧完成。
    4. 内存优化:确保动态物体的包围盒更新不会每帧都分配新内存。使用对象池管理频繁创建销毁的物体及其InstantOC组件。

6. 进阶技巧与最佳实践

当你掌握了基础用法并解决了常见问题后,这些进阶技巧能帮你榨取最后一滴性能。

6.1 与Unity其他优化系统协同

  • 与LOD Group结合:这是黄金组合。为中远距离物体设置LOD。InstantOC的Occludee应该挂在LOD Group的根物体上,或者每个LOD层级的渲染器上。插件会基于当前激活的LOD层级的包围盒进行计算。这样,远处简化的模型不仅面数少,其用于遮挡计算的包围盒也更简洁。
  • 与遮挡裁剪(Camera Occlusion Culling)配合:Unity摄像机本身有一个Occlusion Culling选项,它主要依赖于静态烘焙数据。如果你同时使用了Unity的静态烘焙和InstantOC,需要理清优先级。通常,对于静态物体,让静态烘焙处理;对于动态部分,交给InstantOC。确保两者不会冲突(例如,同一个物体既被静态烘焙剔除又被InstantOC启用)。
  • 与批处理(Batching)的关系:InstantOC禁用的是Renderer。如果一些渲染器原本是通过静态批处理或动态批处理合并的,禁用其中一部分可能会破坏批处理,反而增加Draw Call。这是一个需要仔细权衡的点。对于大量相同的小物体(如场景草),使用GPU Instancing可能比依赖InstantOC更好。或者,将这些小物体放在一个特定的层,让InstantOC忽略它们。

6.2 针对特定场景的优化策略

  • 茂密森林:树木既是Occluder也是Occludee。使用相对较大的View Cell Size(如15.0)来降低计算复杂度。为树木设置2-3级LOD,最近一级才使用完整网格参与遮挡计算。可以考虑使用植被系统(如Unity的Terrain Trees或GPU Instancing的植被方案)替代大量独立GameObject,这些系统通常有内置的裁剪优化。
  • 多层室内结构Backface Threshold是你的利器,设置为0.98以上可以高效剔除隔壁房间。将每一层楼板或大型家具设置为强Occluder。注意处理楼梯、中庭等开放空间,这些地方的遮挡关系复杂,可能需要手动设置一些额外的遮挡体积(Box Occluder)来辅助计算。
  • 大量同屏单位(RTS/MMO):单位通常是动态的。为每个单位都挂InstantOccludee开销巨大。更好的策略是:使用简化的代理体进行遮挡计算。例如,为每个士兵单位使用一个胶囊体或简单的方块作为其“遮挡代理”,这个代理体挂载InstantOccludee并跟随单位移动。而实际渲染的精细模型则根据代理体的可见性来决定是否渲染。这大大减少了需要计算的网格复杂度。

6.3 自定义扩展与脚本控制

大多数成熟的InstantOC插件会提供API供脚本调用,以实现更灵活的控制。

  • 动态启用/禁用:在过场动画、UI界面全屏显示时,可以通过代码FindObjectOfType<InstantOC>().enabled = false;来完全关闭InstantOC计算,节省资源。
  • 强制更新:当你通过代码瞬间移动了大量物体(如加载一个新区域),可以调用管理器的ForceUpdateRecalculateImmediately方法,立即刷新遮挡状态,避免视觉延迟。
  • 自定义包围盒:对于形状特异的物体(如一个长条形的桥梁),其自动生成的AABB包围盒可能很大且不精确,导致遮挡效果差。查看插件是否允许通过脚本设置自定义的Bounds,你可以提供一个更贴合物体形状的包围盒。

最后,性能优化没有银弹。InstantOC是一个强大的工具,但它需要你根据自己项目的具体情况进行细致的调整和测试。从最重要的性能瓶颈视角开始,逐步添加和调整配置,并持续使用Profiler和调试视图进行验证。记住,优化的目标是提升最终用户的体验,而不是追求某个数字上的极致。在性能、视觉质量和开发成本之间找到属于你项目的最佳平衡点,这才是资深技术人的价值所在。