Three.js 实现模型边缘高亮

4,420 阅读1分钟

效果:

image.png

完整源码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Three.js 模型边缘高亮</title>
  <style>
    body {
      margin: 0;
      overflow: hidden
    }
  </style>
  <script src="./js/three.js"></script>
  <script src="./js/OrbitControls.js"></script>
  <script src="./js/CSS2DRenderer.js"></script>
  <script src="./js/loaders/DDSLoader.js"></script>
  <script src="./js/loaders/MTLLoader.js"></script>
  <script src="./js/loaders/OBJLoader.js"></script>
  <!-- 发光特效 -->
  <script src="./js/shaders/CopyShader.js"></script>
  <script src="./js/postprocessing/EffectComposer.js"></script>
  <script src="./js/postprocessing/RenderPass.js"></script>
  <script src="./js/shaders/FXAAShader.js"></script>
  <script src="./js/postprocessing/OutlinePass.js"></script>
  <script src="./js/postprocessing/ShaderPass.js"></script>

</head>

<body>
  <script>
    let camera, scene, renderer, controls
    let composer, effectFXAA, outlinePass, renderPass
    let selectedObjects = []

    const raycaster = new THREE.Raycaster()
    const mouse = new THREE.Vector2()

    Init()

    // 初始化方法
    function Init() {
      // 初始化渲染器
      InitRenderer()
      // 初始化场景
      InitScene()
      // 初始化相机
      InitCamera()
      // 初始化光源
      InitLights()
      // 初始化辅助器
      InitHelper()
      // 设置控制器
      setControl()
      // 初始化数据
      InitData()
      // 选择器
      initSelection()
      // 执行渲染
      render()

      renderer.domElement.addEventListener('pointermove', onPointerMove.bind(this))

    }

    // 初始化数据,加载3D对象、事件等
    function InitData() {
      // 加载OBJ模型与MTL材质
      const onProgress = function (xhr) {
        if (xhr.lengthComputable) {
          const percentComplete = xhr.loaded / xhr.total * 100
          console.log(Math.round(percentComplete, 2) + '% downloaded')
        }
      }
      const onError = function () {}

      const manager = new THREE.LoadingManager()
      manager.addHandler(/\.dds$/i, new THREE.DDSLoader())

      new THREE.MTLLoader(manager)
        .setPath('models/server_v2_console/')
        .load('ServerV2+console.mtl', function (materials) {

          materials.preload()

          new THREE.OBJLoader(manager)
            .setMaterials(materials)
            .setPath('models/server_v2_console/')
            .load('ServerV2+console.obj', function (object) {
              object.position.y = 0
              scene.add(object)
            }, onProgress, onError)
        })

    }

    // 渲染器
    function InitRenderer() {
      // 创建渲染器
      renderer = new THREE.WebGLRenderer()
      // 设置渲染区域尺寸
      renderer.setSize(window.innerWidth, window.innerHeight)
      // 设置背景颜色
      // renderer.setClearColor(0xb9d3ff, 1)
      // 插入canvas 
      document.body.appendChild(renderer.domElement)
    }

    // 相机
    function InitCamera() {
      // 相机设置
      camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000)
      camera.position.set(10, 10, 10)
      scene.add(camera)
      // scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000)
    }

    // 场景
    function InitScene() {
      // 创建场景Scene
      scene = new THREE.Scene()

      // 设置背景色
      scene.background = new THREE.Color(0xf0f0f0)
    }

    // 辅助器
    function InitHelper() {
      // 显示坐标系
      let axes = new THREE.AxisHelper(300)
      scene.add(axes)

      // 辅助网格
      const helper = new THREE.GridHelper(2000, 100)
      helper.position.y = -199
      helper.material.opacity = 0.25
      helper.material.transparent = true
      scene.add(helper)
    }

    // 光源
    function InitLights() {
      // 光源设置
      const ambientLight = new THREE.AmbientLight(0xcccccc, 0.4)
      scene.add(ambientLight)

      const pointLight = new THREE.PointLight(0x333333)
      camera.add(pointLight)
    }

    // 控制器
    function setControl() {
      controls = new THREE.OrbitControls(camera, renderer.domElement) //创建控件对象
      // 已经通过requestAnimationFrame(render);周期性执行render函数,没必要再通过监听鼠标事件执行render函数
      // controls.addEventListener('change', render)
    }

    // 执行渲染操作,指定场景、相机作为参数
    function render() {
      Animate()
      
      if (composer) {
        composer.render()
      }
    }
    
    // 逐帧调用
    function Animate() {
      renderer.render(scene, camera) //执行渲染操作
      controls.update()
      requestAnimationFrame(render)
    }

    // 光标移动
    function onPointerMove(event) {
      if (event.isPrimary === false) return
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
      checkIntersection()
    }

    // 对象选择
    function checkIntersection() {
      raycaster.setFromCamera(mouse, camera)
      const intersects = raycaster.intersectObject(scene, true)
      if (intersects.length > 0) {
        const selectedObject = intersects[0].object
        addSelectedObject(selectedObject)
        outlinePass.selectedObjects = selectedObjects
      } else {
        // outlinePass.selectedObjects = []
      }
    }
    // 高亮描边
    function initSelection() {
      // 选中特效
      composer = new THREE.EffectComposer(renderer)
      renderPass = new THREE.RenderPass(scene, camera)
      composer.addPass(renderPass)
      outlinePass = new THREE.OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera)
      outlinePass.visibleEdgeColor.set('orange') //包围线颜色
      composer.addPass(outlinePass)
      effectFXAA = new THREE.ShaderPass(THREE.FXAAShader)
      effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight)
      composer.addPass(effectFXAA)
    }

    // 选中
    function addSelectedObject(object) {
      selectedObjects = []
      selectedObjects.push(object)
    }
  </script>
</body>

</html>