Unity Scene 优化
我做的编辑器拓展开发,卡顿是出现在通过配置生成上万个带指向的3d球体和linerenderer,根据每个点的配置信息来设置球体的旋转偏量,3d空间位置,两个点之间通过linerenderer连接,创建过程耗时很长,卡顿是出现在编辑器静态界面,而非运行状态。当前面临的 编辑器卡顿问题,本质上是因为:
✅ 在 非运行时的编辑器模式(EditMode)中批量生成上万个带有 Mesh 和 LineRenderer 的 GameObject,Unity 会:
- 每次
GameObject.Instantiate()
都触发内部注册与序列化- 每个 LineRenderer 或 Transform 修改都会立即记录在场景状态中
- 在主线程阻塞执行,触发 大量 Hierarchy 刷新、Inspector 重绘、Undo 缓存注册、序列化数据写入
→ 导致 Unity 编辑器卡死或极度卡顿
✅ 针对性优化建议(编辑器扩展模式专属)
🧱 一、核心优化原则
问题来源 | 优化手段 |
---|---|
每个 GameObject 实时创建、注册 Undo、序列化 |
✅ 使用 EditorApplication.delayCall 分批异步创建 / 禁用 Undo |
每次 Transform 设置都会触发刷新 | ✅ 批量设置坐标,禁用场景刷新(SceneView.RepaintControl) |
每个对象生成都立即刷新 Hierarchy | ✅ 临时关闭自动刷新界面,批量结束后一次性刷新 |
每次 LineRenderer.SetPositions() 修改也会导致 GC |
✅ 合并线段/简化结构/使用 GPU 绘制优化线渲染 |
🔧 二、操作级优化方式(编辑器端)
✅ 1. 禁用 Undo.RecordObject
如果不是必要的编辑器撤销操作,禁用 Undo.RegisterCreatedObjectUndo()
可以显著减少性能开销:
1 | GameObject go = GameObject.Instantiate(prefab); |
✅ 2. 批量暂停 Scene 刷新 + Hierarchy 重绘
1 | EditorApplication.delayCall += () => |
✅ 3. 使用 Coroutine 风格的异步分帧生成(模拟帧分批)
1 | IEnumerator GeneratePointsStepByStep() |
用 EditorCoroutineUtility.StartCoroutine(GeneratePointsStepByStep(), this)
来调用
✅ 来自 Unity.EditorCoroutines 包,推荐引入用于编辑器分帧逻辑
✅ 4. 创建时不要激活 GameObject,最后一起启用
1 | GameObject go = Instantiate(prefab); |
✅ 5. 使用 Mesh 或 DrawMesh 替代 LineRenderer(合并优化)
- 将多个线段数据汇总,手动用
GL.LINES
或Graphics.DrawMeshNow()
在 SceneView 渲染而不是生成上万个 LineRenderer - 或者使用一个
Mesh
合并所有线段的渲染顶点,自定义材质渲染
📌 Bonus:GPU 绘制线连接方案(不生成 GameObject)
- 使用一个
ScriptableObject
维护线段列表 - 在 Scene GUI 中绘制:
1 | Handles.color = Color.green; |
➡️ 不创建 GameObject,不占用层级,无 GC/卡顿 ✅
✅ 推荐组合优化方案(低开销)
目标 | 实现方式 |
---|---|
支持查看上万个点 + 连接线 | Handles.DrawLine() 在 SceneView 中实时渲染 |
支持选中点位 | 注册 HandleUtility.AddDefaultControl() + HandleUtility.PickGameObject 实现点击识别 |
仅在运行前批量生成真实物体 | 提供按钮:“生成 GameObject” → 批量生成,不自动创建 |
🚀 实用工具推荐
- ✅ Editor Coroutines:编辑器异步分帧执行逻辑
- ✅ SceneView.onSceneGUIDelegate:用于绘制线、不创建对象
- ✅ Odin Inspector:可视化控制面板和配置生成控制器
🧠 总结
类型 | 建议 |
---|---|
❌ 不要 | 一次性创建上万个 GameObject + LineRenderer(会卡) |
✅ 推荐 | 分批延迟创建、禁用 Undo、绘制到 Scene GUI 替代真实对象 |
🛠️ 补充 | 可以提供一个“点位配置→可视化→生成真实节点”的分离工具 |
你在 OnSceneGUI
中绘制上万条线,导致移动视角卡顿,是 合理且常见的问题。原因如下:
🧠 原因:Handles.DrawLine
在 OnSceneGUI
中是 CPU 密集型 + 每帧运行
- 每次移动场景视角时,
SceneView.OnSceneGUI
都会触发重新绘制 Handles.DrawLine
属于 Immediate Mode GUI(IMGUI),无法批量合批优化- 当你绘制上万个点或线段,编辑器主线程完全吃满(全在 CPU 上渲染、无缓存)
✅ 解决方案:降低绘制密度 + 使用 GPU 批量绘制
✅ 方案一:仅在静止或需要时绘制(减少频率)
你可以 只在窗口打开或点击按钮时才绘制一次,而不是一直监听 SceneView.duringSceneGui
:
1 | private bool forceDraw = false; |
✅ 方案二:GL + CommandBuffer 批量绘制(推荐)
可以使用 GL.LINES
+ 自定义材质,在 GPU 中绘制所有线条,一次性渲染,不产生 GC 和 CPU 压力。
步骤:
- 创建一个材质(使用
Unlit/Color
或自定义 Shader) - 在 Scene GUI 中注册一个
Camera.onPostRender
或SceneView.onSceneGUIDelegate
- 使用
GL.Begin(GL.LINES)
批量绘制所有连接线
示例代码:
1 | [ ] |
✅ 优势:
- 使用 GPU 绘制,不卡顿
- 不生成 GameObject,也不会触发 Scene 刷新
- 可实时预览 + 快速刷新
✅ 方案三:对远离视野的线段/点位做视锥剔除
1 | if (SceneView.lastActiveSceneView != null) |
📌 降低绘制线段数量,对可见部分仅绘制,性能可大幅提升
✅ 总结推荐
场景需求 | 推荐方案 |
---|---|
上万条线,实时预览 | ✅ 使用 GL.LINES 批量绘制,避免 Handles.DrawLine |
临时静态可视化 | 只绘制一次 OnSceneGUI ,绘完关闭 |
编辑器工具面板控制 | 提供“开启预览”、“关闭预览”按钮控制绘制状态 |
远距离线段过滤 | 添加视锥剔除只绘可见连接线 |
❗你遇到的问题
使用
GL.LINES
绘制的线段,确实不会自动跟随 Scene 中对象的移动,因为GL.Vertex
是在SceneView.camera
的一次性投影矩阵下静态绘制的,除非你主动刷新数据。你在编辑器 Scene 操作顶点(或对象)时卡顿,是因为:
- 你每帧都进行 大量线段计算 + 绘制(如上万个)
- 并且你不断访问/同步变化的坐标 → 每帧都 CPU 扫一次全量顶点 → 毫无缓存
✅ 解决方案:动态、流式、可跟随的高效绘制策略
✅ 方案一:仅展示当前视野范围的少量线段
结合视锥体剔除 + 距离裁剪,大幅降低绘制压力:
1 | Plane[] planes = GeometryUtility.CalculateFrustumPlanes(sceneCam); |
✅ 方案二:仅绘制选中或焦点区域的连接线
1 | Vector3 camPos = sceneCam.transform.position; |
✅ 方案三:数据缓存 + 变化时更新
你需要避免每帧都全量重计算所有坐标。可以这样优化:
1 | private List<Vector3> cachedPoints = new(); |
然后在对象位置变化时设置 needUpdate = true
。你可以:
- 用
EditorApplication.hierarchyChanged
监听结构变化 - 或在
Selection
改变时做刷新
✅ 方案四:使用 Graphics.DrawMeshNow
合批绘制动态线条
自己生成一整张 Mesh
(比如线段的 cylinder 或 quad),然后一次性 Graphics.DrawMeshNow()
绘制所有线段,这种方式:
- 支持跟随坐标变动(每帧更新 mesh)
- GPU 绘制,不卡
- 可打包成
GizmosRenderer
或SceneView
工具
这是很多大型编辑器工具(如 DOTS Inspector、AI Debugger)常用方案。
🔧 如果你要动态显示/更新线段,并跟随场景中球体移动
✅ 最佳方案组合建议:
项目 | 使用方式 |
---|---|
点位 | 实体使用 GameObject + Sphere (可交互、选中) |
连线 | 用单个 Mesh 合批绘制所有线段,动态更新(非 LineRenderer ,避免生成上万个) |
性能保障 | 添加视锥体剔除 + 距离剔除逻辑 |
数据管理 | 使用数组缓存点位坐标,仅在坐标改变时更新一次线段 Mesh |
触发更新 | 使用自定义按钮 / Unity EditorApplication.update / SceneView.duringSceneGui 判断是否需要更新 |
🚀 是否需要我帮你写一个:
- 自动从所有球体位置构建一张合批线段
Mesh
- 支持动态更新(如某球体移动后线段自动更新)
- 不依赖 LineRenderer 或 GL,每次仅绘制 1 个合批对象