
1. 项目概述为什么你的Unity项目需要一个“描边”插件在Unity开发中尤其是涉及到交互、解谜、战斗或者任何需要玩家聚焦于特定物体的场景你有没有遇到过这样的困惑玩家总是找不到那个关键的可交互物品或者战斗时技能目标不够醒目我接手过不少项目早期版本上线后用户反馈最多的就是“那个门把手在哪”、“我该打哪个怪”。这背后其实是一个关于视觉引导和交互反馈的核心体验问题。一个简单、高效且性能友好的解决方案就是为关键物体添加一个醒目的外发光或描边效果。这听起来像是需要Shader大神才能搞定的高级特效对吧但今天要聊的QuickOutline插件彻底改变了这个局面。它是一款完全免费、开源的Unity插件专为物体高亮而设计。你不需要理解复杂的屏幕后处理Post-Processing管线也不用自己从头编写Shader只需拖拽一个脚本组件几行代码调用就能为你的游戏物体赋予清晰、可定制的高亮轮廓。为什么我特别推荐它因为在多年的项目实战中我试过不少方案自己写Shader、用Unity官方的Renderer组件变颜色、甚至尝试一些昂贵的Asset Store资源。要么是效果生硬要么是性能开销大要么是集成复杂。QuickOutline的出现完美地平衡了易用性、效果和性能。它基于物体本身的网格Mesh进行轮廓扩张渲染原理清晰消耗可控特别适合移动端或性能敏感的项目。无论是用于标记任务目标、高亮可拾取物品还是指示玩家当前选中的单位它都能让你的项目交互体验立刻提升一个档次。2. QuickOutline核心原理与优势拆解2.1 它是如何“画”出轮廓的理解一个工具的原理能帮助你在使用时做出更明智的决策尤其是在遇到性能瓶颈或效果异常时。QuickOutline的核心原理并不复杂但非常巧妙。它不是通过后处理全屏扫描来识别边缘而是采用了一种更直接、更高效的方式基于模板缓冲Stencil Buffer和正面剔除Front Face Culling的多Pass渲染。简单来说它的工作流程可以拆解为三步第一次渲染绘制模板插件会先正常渲染一次你的物体但这次渲染的主要目的不是显示而是向一个叫做“模板缓冲区”的隐藏区域写入一个特定的标记比如标记为1。你可以把这个缓冲区想象成一张和屏幕一样大小的透明玻璃纸这次渲染就是在玻璃纸上对应物体形状的位置用特殊的笔画了一个“1”。第二次渲染放大并绘制轮廓接着插件会再次渲染同一个物体但这次会做两个关键操作。第一将物体的网格沿着法线方向轻微“膨胀”一圈这样渲染出来的形状会比原物体大一点这就是轮廓的粗细来源。第二启用“正面剔除”意思是只渲染物体的背面因为膨胀后背面露出来了。同时它设置了一个规则“只在上一步模板标记不是1的地方进行绘制”。因为物体本身占据的区域已经被标记为1了所以这次渲染只会在物体周围那一圈“膨胀出来的、且未被标记”的区域绘制上你设定的轮廓颜色。第三次渲染正常显示物体最后再正常渲染一次物体本身覆盖在轮廓之上这样我们就得到了一个内部是正常材质、外部有一圈颜色轮廓的最终效果。这种方法的优势非常明显性能优异它只影响你挂载了脚本的特定物体而不是对整个屏幕进行后处理计算。物体不在视野内时轮廓根本不会渲染。这对于场景中只有少数几个需要高亮的物体如BOSS、宝箱时开销几乎可以忽略不计。效果稳定轮廓是基于物体网格生成的因此无论物体如何旋转、变形轮廓都能紧紧地贴合其形状不会出现断裂或闪烁的问题后处理边缘检测在复杂运动下容易出问题。兼容性强它不依赖特定的渲染管线Built-in, URP, HDRP经过适配均可使用也不与项目中的其他Shader或渲染效果产生剧烈冲突。2.2 对比其他高亮方案它强在哪在引入QuickOutline之前我们通常有几种备选方案方案实现方式优点缺点适用场景材质颜色/发光动态更换物体的材质或增加自发光Emission强度。实现简单无需额外插件。效果生硬可能破坏原有美术风格发光在光照复杂场景下不明显性能开销取决于新材质复杂度。对效果要求不高的临时调试或风格化极强的项目。屏幕后处理边缘检测使用Unity Post-Processing Stack等通过深度/法线图检测场景边缘并高亮。全局生效能高亮所有符合条件的物体。性能开销大每帧全屏处理难以精准控制单个物体容易高亮到不需要的物体如背景需要调整大量参数。需要整体风格化渲染如卡通渲染、侦查视角的项目而非精准交互反馈。自定义几何体着色器编写Shader在物体外围渲染一个放大的Pass。效果可控性最强可集成到项目主Shader中。开发门槛高需要图形学知识维护成本高每个需要高亮的材质球都要修改。有专业图形程序团队且高亮为核心玩法的大型项目。QuickOutline插件基于模板缓冲与网格膨胀的多Pass渲染。免费、开源、易集成效果专业且稳定性能开销低且可预测不干扰原有材质。轮廓颜色为纯色难以实现渐变等复杂效果物体网格过于复杂时轮廓可能不完美。绝大多数需要精准、高性能物体高亮的交互场景如RPG、解谜、RTS、VR/AR应用。从对比中可以看出QuickOutline在易用性、性能和维护成本上取得了极佳的平衡。对于中小型团队或独立开发者而言它几乎是解决“物体高亮”需求的不二之选。3. 从零开始QuickOutline的集成与基础使用3.1 获取与导入插件QuickOutline是一个开源项目你可以在GitHub上找到它的仓库。最方便的获取方式是通过Unity的Package Manager。打开Package Manager在Unity编辑器中点击顶部菜单Window Package Manager。添加Git URL点击窗口左上角的“”号选择“Add package from git URL...”。输入仓库地址在弹出的输入框中粘贴QuickOutline的Git仓库地址。通常格式类似于https://github.com/作者名/QuickOutline.git。你需要确认当前可用的正确地址由于地址可能变更建议通过Asset Store或信誉良好的第三方资源站获取稳定版本。点击“Add”。等待导入Unity会自动从Git仓库下载并导入插件包。导入完成后你会在Project窗口的Packages目录下看到QuickOutline。注意从Git导入的方式依赖于网络且无法保证版本绝对稳定。对于生产项目我更推荐将下载好的插件包通常是一个.unitypackage文件直接导入项目或者将其源码复制到项目的Assets文件夹下的某个目录如Assets/Plugins/QuickOutline。这样能更好地进行版本控制避免因上游仓库变更导致项目意外出错。3.2 为物体添加高亮效果导入成功后使用起来非常简单。选择需要高亮的物体在Hierarchy窗口中选中你的目标GameObject。这个物体必须包含MeshRenderer或SkinnedMeshRenderer组件。添加Outline组件在Inspector窗口中点击底部的“Add Component”按钮搜索“Outline”并添加。你会立刻看到物体被添加了一个默认的红色轮廓。调整轮廓参数Outline组件提供了几个直观的参数供你调整Outline Mode: 轮廓模式。OutlineAll是整体轮廓OutlineVisible只显示可见面的轮廓OutlineHidden只显示被遮挡面的轮廓可用于透视效果。通常使用OutlineAll。Outline Color: 轮廓颜色。点击色块选择你需要的颜色。Outline Width: 轮廓宽度。值越大轮廓越粗。注意这个值是基于物体本身大小的相对值对于大小差异巨大的物体可能需要分别调整。Outline Material: 内部使用的轮廓材质一般无需修改除非你有高级定制需求。实操心得对于由多个子物体组成的复杂模型比如一个角色由身体、武器、头发等多个部分组成你不需要为每个子物体都添加Outline组件。只需要在父级空物体上添加一个Outline组件然后勾选组件上的Use Renderers Children选项如果插件版本有此选项或者手动将子物体的Renderer组件拖拽到Outline组件的Renderers列表里。这样插件会自动收集所有子渲染器并为整个复合物体绘制一个统一的轮廓管理起来方便得多。3.3 通过代码动态控制高亮静态的高亮显然无法满足游戏需求。我们通常需要根据游戏逻辑如鼠标悬停、被选中、任务目标来动态开启或关闭轮廓。using UnityEngine; // 假设Outline组件的命名空间是 QuickOutline // using QuickOutline; public class HighlightController : MonoBehaviour { private Outline outlineComponent; void Start() { // 获取物体上的Outline组件 outlineComponent GetComponentOutline(); if (outlineComponent null) { // 如果物体上没有可以动态添加 outlineComponent gameObject.AddComponentOutline(); outlineComponent.OutlineColor Color.yellow; outlineComponent.OutlineWidth 5.0f; } // 默认先禁用轮廓 outlineComponent.enabled false; } // 当鼠标进入物体碰撞体时调用 void OnMouseEnter() { if (outlineComponent ! null) { outlineComponent.enabled true; } } // 当鼠标离开物体碰撞体时调用 void OnMouseExit() { if (outlineComponent ! null) { outlineComponent.enabled false; } } // 一个公共方法供其他系统如任务系统、战斗系统调用 public void EnableHighlight(Color color, float duration 0f) { if (outlineComponent ! null) { outlineComponent.OutlineColor color; outlineComponent.enabled true; if (duration 0) { // 如果设置了持续时间则在一段时间后自动关闭 Invoke(nameof(DisableHighlight), duration); } } } public void DisableHighlight() { if (outlineComponent ! null) { outlineComponent.enabled false; } } }这段代码展示了最基本的动态控制逻辑。在实际项目中你可能会将其集成到更复杂的交互管理器或状态机中。4. 高级配置与性能优化实战4.1 应对复杂模型与特殊材质的挑战虽然QuickOutline很强大但并非万能。在某些特定情况下你可能会遇到轮廓显示异常的问题。问题一轮廓闪烁或断裂原因通常发生在模型面数极高或网格结构异常复杂时。轮廓的“膨胀”计算是基于顶点法线的如果模型法线信息混乱或不连续就会导致膨胀方向不一致从而产生断裂。排查与解决检查模型导入设置。在Project窗口选中模型文件在Inspector中查看Model分页确保“Normals”选项设置为“Calculate”或“Import”而不是“None”。可以尝试勾选“Smooth Normals”平滑法线。在3D建模软件中检查并修复模型的法线。确保所有面的法线方向一致统一朝外。如果问题仅出现在特定复杂部件上可以考虑为该部件使用一个简化版的低模LOD0专门用于轮廓渲染。虽然麻烦但能根治。问题二轮廓与透明材质物体叠加异常原因透明材质如粒子特效、玻璃的渲染顺序和深度写入与不透明物体不同可能会干扰模板缓冲区的操作。解决为Outline组件指定一个独立的、用于渲染轮廓的材质球如果插件支持自定义。确保这个材质球的渲染队列Render Queue设置正确通常需要在不透明物体之后、透明物体之前渲染。如果插件不支持可能需要修改其源码中的Shader调整其Queue标签。问题三UI物体或Sprite无法高亮原因QuickOutline依赖于MeshRenderer而UICanvasRenderer和2D SpriteSpriteRenderer使用的是不同的渲染器。解决对于UI高亮通常有更专门的方案如修改UI材质或使用遮罩。如果非要用Outline一个取巧的办法是为UI元素创建一个与之同大小的3D Quad平面挂载Outline组件并让这个Quad始终跟随UI元素的位置通过脚本同步。但这会额外增加Draw Call需谨慎使用。4.2 性能监控与批量管理策略“它性能很好”不代表可以无节制地使用。在移动平台上同时激活几十个高亮物体依然可能带来压力。性能开销在哪里额外的Draw Call每个被激活的Outline组件至少会为物体增加1-2个额外的渲染Pass绘制轮廓的Pass。这意味着一个物体的Draw Call从1次变成了2-3次。Overdraw轮廓渲染是在原物体基础上“放大”绘制会覆盖更多的像素增加片元着色器的计算量即Overdraw。优化策略严格控制同时高亮的物体数量这是最有效的优化。在设计游戏逻辑时避免让大量物体同时处于可高亮状态。例如在RTS游戏中可以只高亮玩家当前鼠标悬停的单位和已选中的单位组而不是地图上所有友方单位。使用对象池管理Outline组件对于频繁出现和消失的高亮物体如大量可拾取物品可以考虑使用对象池来复用Outline组件而不是频繁地AddComponent和Destroy后者会引起GC垃圾回收开销。分帧更新如果一帧内需要更新大量Outline的状态如颜色、宽度可以考虑将这些更新分散到多帧中进行避免单帧CPU峰值。利用LOD Group对于有LOD多层次细节的模型确保Outline组件只在必要的LOD层级上生效。或者为低模也配置一个更简单的轮廓效果。简单的性能测试脚本 你可以写一个简单的脚本在运行时输出Draw Call和三角形数量的变化来直观感受Outline的影响。using UnityEngine; using UnityEngine.Profiling; public class OutlinePerformanceMonitor : MonoBehaviour { public Outline targetOutline; private int baseDrawCalls; private int baseTriangles; void Start() { // 记录未开启轮廓时的基础数据需要在渲染一帧后获取可在Update中延迟一帧记录 Invoke(nameof(RecordBaseStats), 0.1f); } void RecordBaseStats() { baseDrawCalls UnityEngine.Rendering.RenderStats.drawCalls; baseTriangles UnityEngine.Rendering.RenderStats.triangles; Debug.Log($Base - DrawCalls: {baseDrawCalls}, Triangles: {baseTriangles}); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { ToggleOutlineAndLog(); } } void ToggleOutlineAndLog() { if (targetOutline ! null) { targetOutline.enabled !targetOutline.enabled; // 等待一帧让渲染统计更新 Invoke(nameof(LogCurrentStats), 0.1f); } } void LogCurrentStats() { int currentDrawCalls UnityEngine.Rendering.RenderStats.drawCalls; int currentTriangles UnityEngine.Rendering.RenderStats.triangles; Debug.Log($Outline [{targetOutline.enabled}] - DrawCalls: {currentDrawCalls}({currentDrawCalls - baseDrawCalls}), Triangles: {currentTriangles}({currentTriangles - baseTriangles})); } }5. 实战案例构建一个完整的交互高亮系统让我们结合一个具体的游戏场景——一个第一人称解谜游戏中的“可调查物品”系统——来将QuickOutline的应用串起来。5.1 系统设计思路需求玩家在场景中移动视线中心有一个准星。当准星对准一个可调查的物品如一张纸条、一个机关时该物品应高亮如蓝色轮廓并显示提示文字。玩家按下交互键如E键后触发调查事件显示纸条内容、播放机关动画同时高亮消失。系统组件InteractionManager (单例)全局管理器负责处理玩家输入管理当前焦点物体。InteractableObject可交互物体的基类脚本挂载在所有可交互物体上。包含高亮控制、交互触发逻辑。UIPromptUI组件用于在屏幕下方显示“按E调查 [物品名]”的提示。5.2 核心代码实现InteractableObject.csusing UnityEngine; using UnityEngine.Events; public class InteractableObject : MonoBehaviour { public string objectName 可调查物品; public Color highlightColor Color.cyan; public float outlineWidth 7.0f; [Space] public UnityEvent onInteract; // 用于在Inspector中配置交互事件如播放动画、触发对话 private Outline outline; private bool isPlayerFocus false; void Start() { // 确保物体有Collider用于射线检测 if (GetComponentCollider() null) { gameObject.AddComponentBoxCollider(); Debug.LogWarning(${gameObject.name} 缺少Collider已自动添加BoxCollider。); } // 添加并配置Outline组件 outline gameObject.AddComponentOutline(); outline.OutlineColor highlightColor; outline.OutlineWidth outlineWidth; outline.enabled false; // 默认关闭 } // 被InteractionManager调用当玩家视线聚焦于此物体时 public void OnGainFocus() { if (!isPlayerFocus) { isPlayerFocus true; outline.enabled true; // 通知UI显示提示 InteractionManager.Instance?.ShowPrompt(this); } } // 被InteractionManager调用当玩家视线移开此物体时 public void OnLoseFocus() { if (isPlayerFocus) { isPlayerFocus false; outline.enabled false; // 通知UI隐藏提示 InteractionManager.Instance?.HidePrompt(); } } // 被InteractionManager调用当玩家按下交互键时 public void TriggerInteraction() { Debug.Log($与 {objectName} 交互); onInteract?.Invoke(); // 触发配置好的事件 OnLoseFocus(); // 交互后失去焦点 } }InteractionManager.csusing UnityEngine; public class InteractionManager : MonoBehaviour { public static InteractionManager Instance; public Camera playerCamera; public float interactionRange 3f; public LayerMask interactableLayer; // 设置一个专门用于可交互物体的Layer [Header(UI Reference)] public UIPrompt uiPrompt; private InteractableObject currentFocusObject; void Awake() { if (Instance null) { Instance this; } else { Destroy(gameObject); } } void Update() { CheckForInteractable(); HandleInteractionInput(); } void CheckForInteractable() { Ray ray playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); // 从屏幕中心发射射线 RaycastHit hit; if (Physics.Raycast(ray, out hit, interactionRange, interactableLayer)) { InteractableObject obj hit.collider.GetComponentInteractableObject(); if (obj ! null) { // 如果射线击中了新的可交互物体 if (currentFocusObject ! obj) { // 让旧物体失去焦点 if (currentFocusObject ! null) { currentFocusObject.OnLoseFocus(); } // 让新物体获得焦点 currentFocusObject obj; currentFocusObject.OnGainFocus(); } return; // 找到物体直接返回 } } // 如果射线什么都没击中或者击中的不是InteractableObject if (currentFocusObject ! null) { currentFocusObject.OnLoseFocus(); currentFocusObject null; } } void HandleInteractionInput() { if (Input.GetKeyDown(KeyCode.E) currentFocusObject ! null) { currentFocusObject.TriggerInteraction(); } } public void ShowPrompt(InteractableObject obj) { if (uiPrompt ! null) { uiPrompt.Show($按 E 调查 [{obj.objectName}]); } } public void HidePrompt() { if (uiPrompt ! null) { uiPrompt.Hide(); } } }5.3 在编辑器中配置与测试创建一个新的Layer例如命名为“Interactable”。将场景中所有可交互的物体如桌子上的书、墙上的画都放入“Interactable”层。为这些物体挂载InteractableObject脚本并设置好objectName和highlightColor。在InteractableObject的Inspector面板中点击onInteract事件下方的“”号可以关联具体操作例如激活一个包含纸条内容的UI面板、播放一个抽屉打开的动画、或者触发一个声音。在场景中创建一个空物体命名为“GameManager”挂载InteractionManager脚本。将Main Camera拖拽到InteractionManager的playerCamera字段。设置InteractionManager的interactableLayer为刚刚创建的“Interactable”层。创建并配置好UIPrompt的UI元素并将其引用赋值给InteractionManager的uiPrompt字段。运行游戏当你看向可交互物体时它会立刻显示出你设定的轮廓色屏幕下方出现提示。按下E键触发你预设的事件。一个完整、响应迅速、视觉效果清晰的交互系统就搭建完成了。这个系统具有很强的扩展性你可以轻松地修改InteractableObject基类派生出“可拾取物品”、“可对话NPC”、“可开启的门”等不同的交互类型并复用这套高亮和焦点管理逻辑。6. 常见问题排查与进阶技巧即使按照步骤操作在实际开发中仍可能遇到一些“坑”。这里记录了一些我踩过的坑和对应的解决方案。6.1 轮廓不显示或显示异常排查清单当轮廓没有按预期显示时可以按照以下清单逐步排查问题现象可能原因解决方案轮廓完全看不见1. Outline组件未启用。2. 物体没有MeshRenderer/SkinnedMeshRenderer。3. 物体的Renderer组件被禁用。4. 轮廓颜色与背景色太接近或Alpha值为0。5. 使用了不兼容的渲染管线如URP/HDRP未正确配置。1. 检查Inspector中enabled复选框。2. 确保物体有渲染器。3. 检查Renderer组件的启用状态。4. 尝试一个鲜艳的颜色如亮红检查Color的A通道是否为255。5. 确认你使用的QuickOutline版本支持当前项目的渲染管线URP/HDRP可能需要特定的Shader变体或设置。轮廓闪烁Z-fighting轮廓的渲染深度与物体本身或背景物体非常接近导致深度测试不稳定。1. 尝试轻微增加Outline组件中材质的ZTest偏移如果插件暴露此参数。2. 修改插件Shader在轮廓的Pass中加入Offset 1, 1指令需一定Shader知识。3. 确保场景中物体的缩放Scale不是极端值如0.001或1000。轮廓在特定角度消失可能开启了OutlineVisible或OutlineHidden模式且当前视角下不符合渲染条件。切换为OutlineAll模式进行测试。轮廓穿透墙壁显示轮廓渲染可能没有正确处理深度测试或者其渲染队列设置在了不透明物体之后。检查并确保轮廓Shader的渲染队列Queue设置正确通常应在Geometry不透明物体之后AlphaTest透明测试之前。例如QueueGeometry1。移动端上轮廓性能差同时激活的轮廓物体过多或轮廓宽度Outline Width值设置过大。1. 严格限制同屏高亮物体数量。2. 降低轮廓宽度。3. 考虑为移动端使用一个更简化的轮廓Shader减少计算。6.2 进阶技巧自定义轮廓效果与Shader微调QuickOutline默认提供的轮廓是纯色、均匀宽度的。如果你需要更风格化的效果比如渐变轮廓、虚线轮廓或发光轮廓就需要对它的Shader进行修改。这需要一定的Shader编程基础。步骤简述在项目中找到QuickOutline使用的Shader文件通常是一个.shader文件。复制一份重命名如CustomOutline.shader以免更新插件时被覆盖。用代码编辑器如VSCode打开这个Shader文件进行修改。示例实现一个简单的颜色渐变轮廓关键思路是在片元着色器fragment shader中根据轮廓的“厚度”来插值颜色。我们可以在顶点着色器中将轮廓的“膨胀程度”传递给片元着色器。以下是概念性代码需根据实际Shader结构调整// 在顶点着色器输出结构体中增加一个变量 struct v2f { float4 vertex : SV_POSITION; half rim : TEXCOORD0; // 用于表示边缘强度 }; v2f vert (appdata_base v) { v2f o; // ... 原有的顶点膨胀计算代码 ... float3 viewDir normalize(ObjSpaceViewDir(v.vertex)); float dotProduct 1 - dot(v.normal, viewDir); // 简单的边缘检测计算 o.rim smoothstep(0.5, 1.0, dotProduct); // 将点积结果映射到0-1范围 return o; } fixed4 frag (v2f i) : SV_Target { // 使用rim值在两种颜色之间插值 fixed4 gradientColor lerp(_OutlineColor, _OutlineColor2, i.rim); return gradientColor; }同时你需要在Properties块中定义_OutlineColor2属性并在C#脚本中提供接口来设置它。重要提醒修改Shader是高级操作务必在测试场景中充分验证确保在不同平台尤其是移动端上的兼容性和性能。对于大多数项目而言默认的纯色轮廓已经足够出色和实用。7. 总结与项目集成建议经过从原理剖析、快速上手、性能优化到实战搭建系统的完整流程相信你已经对QuickOutline这款插件有了深入的了解。它绝不仅仅是一个“让物体变亮”的小工具而是一个能系统性提升你项目交互品质的解决方案。在我自己的项目集成过程中有几点深刻的体会 第一规划先行。在项目早期就确定好哪些类型的物体会需要高亮如可交互物、敌人、任务目标并为之设计一套统一的管理规则和视觉规范比如友方用蓝色敌方用红色任务物品用金色。这能避免后期到处打补丁维护起来一团糟。 第二建立抽象层。不要让你的游戏逻辑代码直接去GetComponentOutline()和设置enabled。就像我们上面实战案例中做的通过一个InteractableObject这样的抽象类或接口来封装高亮行为。这样未来即使你想换掉QuickOutline改用其他高亮方案也只需要修改这个抽象层的内部实现而游戏逻辑代码完全不用动。 第三重视性能预算。尤其是在移动端或VR项目中将“同时高亮物体数量”作为一个明确的性能指标来监控。在性能分析器Profiler中观察开启高亮前后的Draw Call和GPU耗时变化做到心中有数。 第四善用插件的扩展性。如果你有特殊需求不要害怕去阅读和修改它的源码。它的代码结构相对清晰是学习Unity渲染和组件设计的一个很好案例。当然修改前记得备份。最后QuickOutline的简洁和高效很好地诠释了“工具为设计服务”的理念。它不会喧宾夺主而是安静、可靠地完成它的本职工作——让玩家看得更清楚玩得更顺畅。当你看到玩家在测试中不再迷茫地四处乱点而是能准确找到你精心设计的每一个互动点时你就会觉得花在集成和调试上的每一分钟都是值得的。