🔥Three.js轮廓线高亮神器来啦!自定义高亮选中效果只需一个类搞定!

889 阅读5分钟

🔥Three.js轮廓线高亮神器来啦!自定义高亮选中效果只需一个类搞定!

在开发 Three.js 项目的过程中,很多人都会遇到这样一个需求——如何高亮选中某个物体?

我们希望用户点击一个模型,它能“亮起来”,视觉上突出显示,这样不仅提升交互体验,也让整体项目看起来更“高级”。

🎯 本文将手把手带你封装一个支持多物体、可配置、易管理的轮廓线系统,使用 Three.js 中的 OutlinePass 实现,只需一句话即可启用高亮选中效果!


✅ 轮廓线效果到底是啥?

轮廓线(Outline)是一种通过为选中的物体描边来增强视觉效果的技术。在 Three.js 中,我们可以借助 EffectComposerOutlinePass 来实现。

如下图所示(官方示例) 👇,添加了轮廓线的物体会被边缘描边勾勒出来,非常醒目:

Snipaste_2025-06-24_11-26-57.png

✨ 功能亮点

我们将封装成一个 CustomOutline 类,具有以下特点:

  • 多物体高亮支持,可批量添加或删除选中对象
  • 动态启用/禁用轮廓线效果(比如切换编辑/预览模式)
  • 支持可配置参数(颜色、粗细、透明度等)
  • 释放资源、自动清理引用,适配复杂项目

📦 核心依赖

你需要先安装/引入以下 Three.js 扩展:

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'

🧠 类的核心结构一览

我们将封装一个 CustomOutline 类,支持以下方法:

方法名功能说明
addSelectedObjects(obj)添加高亮对象
removeSelectedObjects(obj)移除高亮对象
clearSelectedObjects()清除所有高亮
setOutlineParameters(options)设置描边参数
enable() / disable()启用/关闭描边
render()每帧渲染时调用
dispose()释放资源
setSize(width, height)自适应画布大小

📄 使用示例

// 初始化
const outline = new CustomOutline(scene, camera, renderer)

// 设置轮廓线参数
outline.setOutlineParameters({
  edgeStrength: 5,
  edgeThickness: 2,
  visibleEdgeColor: '#00ffff',
})

// 添加选中高亮的物体
outline.addSelectedObjects(mesh)

// 渲染循环中调用
function animate() {
  requestAnimationFrame(animate)
  outline.render()
}

✅ 小提示:你还可以动态设置 visibleEdgeColorhiddenEdgeColor 来实现不同状态下的风格变化!


💡 适用场景

  • 模型编辑器中的物体选中反馈
  • 三维配置器中高亮显示部件
  • 智慧矿山、数字孪生等系统中的对象标识
  • 教学可视化、模型拆解引导

📁 完整源码

/**
 * CustomOutline 类 - Three.js 的轮廓线效果管理器(优化版本)
 * 提供了一个性能优化的轮廓线效果系统,支持:
 * - 懒加载初始化
 * - 高效的对象管理
 * - 优化的渲染性能
 * - 自适应分辨率
 * - 内存优化
 *
 * @class
 * @author [ADui]
 * @version 2.0.0
 */

import * as THREE from 'three'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'

/**
 * 默认的轮廓线效果参数配置
 * 使用 Object.freeze 防止配置被意外修改
 * @constant
 * @type {Object}
 */
const DEFAULT_OUTLINE_OPTIONS = Object.freeze({
  edgeStrength: 3.0, // 轮廓线强度
  edgeGlow: 0.0, // 发光强度
  edgeThickness: 1.0, // 线条粗细
  pulsePeriod: 0, // 脉冲周期(0表示禁用)
  usePatternTexture: false, // 是否使用纹理图案
  visibleEdgeColor: new THREE.Color(0xffffff), // 可见边缘颜色
  hiddenEdgeColor: new THREE.Color(0x190a05), // 被遮挡边缘颜色
  opacity: 1.0, // 透明度
})

export default class CustomOutline {
  /**
   * 创建一个新的轮廓线效果实例
   * @constructor
   * @param {THREE.Scene} scene - Three.js 场景
   * @param {THREE.Camera} camera - Three.js 相机
   * @param {THREE.WebGLRenderer} renderer - Three.js 渲染器
   */
  constructor(scene, camera, renderer) {
    /** @private */ this._scene = scene
    /** @private */ this._camera = camera
    /** @private */ this._renderer = renderer

    // 使用 Set 存储选中的对象,提供 O(1) 的查找和删除效率
    /** @private */ this._selectedObjects = new Set()
    /** @private */ this._enabled = true

    // 缓存频繁使用的值,避免重复创建
    /** @private */ this._renderSize = new THREE.Vector2()
    /** @private */ this._currentOptions = { ...DEFAULT_OUTLINE_OPTIONS }

    // 延迟初始化标志
    /** @private */ this._initialized = false
    /** @private */ this._lastPixelRatio = null
  }

  /**
   * 懒加载初始化 - 仅在首次需要时初始化所有组件
   * 这样可以节省内存并提高初始化性能
   * @private
   */
  /**
   * 懒加载初始化 - 仅在首次需要时初始化所有组件
   * @private
   */
  _lazyInit() {
    if (this._initialized) return
    this._renderer.getSize(this._renderSize)

    // 创建优化的渲染目标
    const renderTarget = new THREE.WebGLRenderTarget(this._renderSize.x, this._renderSize.y, {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
      format: THREE.RGBAFormat,
      colorSpace: THREE.SRGBColorSpace,
      type: THREE.HalfFloatType,
      samples: 4,
    })

    this._composer = new EffectComposer(this._renderer, renderTarget)

    const renderPass = new RenderPass(this._scene, this._camera)
    renderPass.clearAlpha = 0
    this._composer.addPass(renderPass)

    this._outlinePass = new OutlinePass(this._renderSize, this._scene, this._camera)
    this._outlinePass.renderToScreen = false
    this._composer.addPass(this._outlinePass)

    const outputPass = new OutputPass(THREE.ACESFilmicToneMapping)
    outputPass.renderToScreen = true
    this._composer.addPass(outputPass)

    this.setOutlineParameters(this._currentOptions)

    this._initialized = true
    this._lastPixelRatio = this._renderer.getPixelRatio()
  }

  /**
   * 更新轮廓线效果的参数配置
   * 只更新发生变化的参数,提高性能
   * @param {Object} options - 轮廓线参数配置对象
   */
  setOutlineParameters(options = {}) {
    if (!this._initialized) {
      // 如果尚未初始化,只更新存储的配置
      Object.assign(this._currentOptions, options)
      return
    }

    // 只更新发生变化的参数
    Object.entries(options).forEach(([key, value]) => {
      if (this._currentOptions[key] !== value && key in this._outlinePass) {
        this._currentOptions[key] = value
        this._outlinePass[key] = value
      }
    })
  }

  /**
   * 添加要显示轮廓线的物体
   * @param {THREE.Object3D|Array<THREE.Object3D>} objects - 要添加轮廓线的物体或物体数组
   */
  addSelectedObjects(objects) {
    if (!this._initialized) this._lazyInit()

    const objectsArray = Array.isArray(objects) ? objects : [objects]
    let changed = false

    objectsArray.forEach((object) => {
      if (!this._selectedObjects.has(object)) {
        this._selectedObjects.add(object)
        changed = true
      }
    })

    // 只在发生变化时更新
    if (changed) {
      this._outlinePass.selectedObjects = Array.from(this._selectedObjects)
    }
  }

  /**
   * 移除物体的轮廓线效果
   * @param {THREE.Object3D|Array<THREE.Object3D>} objects - 要移除轮廓线的物体或物体数组
   */
  removeSelectedObjects(objects) {
    if (!this._initialized) return

    const objectsArray = Array.isArray(objects) ? objects : [objects]
    let changed = false

    objectsArray.forEach((object) => {
      if (this._selectedObjects.delete(object)) {
        changed = true
      }
    })

    // 只在发生变化时更新
    if (changed) {
      this._outlinePass.selectedObjects = Array.from(this._selectedObjects)
    }
  }

  /**
   * 清除所有轮廓线效果
   */
  clearSelectedObjects() {
    if (!this._initialized) return
    this._selectedObjects.clear()
    this._outlinePass.selectedObjects = []
  }

  /**
   * 获取当前所有具有轮廓线效果的物体
   * @returns {Array<THREE.Object3D>} 具有轮廓线效果的物体数组
   */
  getSelectedObjects() {
    return Array.from(this._selectedObjects)
  }

  /**
   * 更新轮廓线效果的渲染尺寸
   * @param {number} width - 新的渲染宽度
   * @param {number} height - 新的渲染高度
   */
  setSize(width, height) {
    if (!this._initialized) {
      this._renderSize.set(width, height)
      return
    }

    this._renderSize.set(width, height)
    this._composer.setSize(width, height)
  }

  /**
   * 设置轮廓线的可见边缘颜色
   * @param {THREE.Color|string|number} color - 新的颜色值
   */
  setVisibleEdgeColor(color) {
    if (!this._initialized) this._lazyInit()
    this._outlinePass.visibleEdgeColor.set(color)
  }

  /**
   * 设置轮廓线的隐藏边缘颜色
   * @param {THREE.Color|string|number} color - 新的颜色值
   */
  setHiddenEdgeColor(color) {
    if (!this._initialized) this._lazyInit()
    this._outlinePass.hiddenEdgeColor.set(color)
  }

  /**
   * 销毁轮廓线效果,释放资源
   * 完整的资源清理对于防止内存泄漏很重要
   */
  dispose() {
    if (!this._initialized) return

    this.clearSelectedObjects()
    this._outlinePass.dispose()
    this._composer.dispose()

    this._outlinePass = null
    this._composer = null
    this._selectedObjects = null
    this._initialized = false
  }

  /**
   * 临时禁用轮廓线效果,但保留配置
   */
  disable() {
    this._enabled = false
  }

  /**
   * 重新启用之前禁用的轮廓线效果
   */
  enable() {
    if (!this._initialized) this._lazyInit()
    this._enabled = true
  }

  /**
   * 执行轮廓线效果的渲染
   * 包含性能优化和自适应处理
   */
  render() {
    if (!this._enabled || !this._initialized) {
      this._renderer.render(this._scene, this._camera)
      return
    }

    // 检查并适应设备像素比的变化
    const currentPixelRatio = this._renderer.getPixelRatio()
    if (this._lastPixelRatio !== currentPixelRatio) {
      this._composer.setPixelRatio(currentPixelRatio)
      this._lastPixelRatio = currentPixelRatio
    }

    this._composer.render()
  }
}

🔚 结语

Three.js 是一个强大的 3D 渲染引擎,而 OutlinePass 的轮廓线效果则能为你的项目增添视觉亮点与交互体验。

希望这个封装类能帮你节省时间、提升项目质量。

📌 喜欢就点个赞 👍,关注我!