vue+threejs写物体效果:勾边

537 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情


写在前面

本文研究使用vue+threejs对物体进行勾边操作。

我们在选中物体时,需要给物体勾边,表示其被选中的状态。

最终演示gif如下:

20220928_110658.gif

完整代码说明

  1. 创建一个id容器,用于展示threejs
<template>
  <div class="item">
    <div id="THREE34"></div>
  </div>
</template>
  1. 引入需要用到的threejs模块
  • OrbitControls: 轨道控制器,用于界面随鼠标操作而移动、缩放、转换视角
  • ThreeMFLoader: .3mf后缀模型导入模块,用它加载.3mf文件模型
  • GUI: threejs的一个UI工具,用来控制例如:颜色的变化
  • EffectComposer:后期处理模块
  • RenderPass:必须,用于渲染
  • OutlinePass:勾边功能模块
import * as THREE from "three";

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { ThreeMFLoader } from "three/examples/jsm/loaders/3MFLoader.js";

import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.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";
  1. 初始化threejs的方法写在mounted()中,因为需要根据id获取元素
mounted() {
  this.initThreejs();
},
  1. initThreejs方法

首先创建threejs必须的:相机、场景、灯光和渲染器,并设置场景背景颜色、相机的位置、灯光的类型颜色及位置。

再用PlaneGeometry创建一个平面,进行90度旋转后ground.rotation.x = -Math.PI / 2;作为地面。

接着通过ThreeMFLoader加载.3mf文件的模型,并将模型添加到场景中scene.add(object);

然后就是本文的重点,勾边效果的实现:

(1) 创建一个后期处理composer = new EffectComposer(renderer);

(2) 将RenderPass和OutlinePass都添加到后期处理链中composer.addPass(renderPass);``composer.addPass(outlinePass);

(3) 对添加的两个处理链的参数进行修改,参数的说明在代码中也有注释。

后期处理是threejs用来提供进行后期处理的一个模块,其中一个后期处理可以用来完成勾边功能:OutlinePass

打印outlinePass,有很多属性,有兴趣的可以一个个研究,下面是一些常用的

  • visibleEdgeColor:勾边的颜色
  • edgeStrength:勾边锐度 Number
  • edgeGlow:边缘辉光 Number
  • pulsePeriod:边缘闪烁 Number
  • edgeThickness:边缘厚度 Number

到此时整个勾边效果已经写完了,我这里用GUI加了个改变勾边颜色的功能,具体代码都在createPanel方法中,且都有注释。

最后就是整个场景的渲染 composer.render();

initThreejs() {
  /**
   * 相机,场景,渲染器
   */
  let camera, scene, renderer;
  /**
   * 模型,gui设置
   */
  let model, settings;
  /**
   * 后期处理,勾选功能模块
   */
  let composer, outlinePass;

  init();

  function init() {
    // 场景
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x8cc7de); // 场景背景颜色
    scene.fog = new THREE.Fog(0xa0a0a0, 10, 500); // 边缘雾化

    // 相机,这里创建了一个透视相机
    camera = new THREE.PerspectiveCamera(
      35,
      (window.innerWidth - 201) / window.innerHeight,
      1,
      500
    );
    camera.position.set(-100, 80, 100); // 相机位置
    scene.add(camera);

    // 半球光
    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444); // 天空的光的颜色是0xffffff,地面的光的颜色是0x444444
    hemiLight.position.set(0, 100, 0); // 灯光位置
    scene.add(hemiLight);

    // 平行光
    const dirLight = new THREE.DirectionalLight(0xffffff);
    dirLight.position.set(-0, 40, 50);
    dirLight.castShadow = true; // 是否产生阴影
    dirLight.shadow.camera.top = 50;
    dirLight.shadow.camera.bottom = -25;
    dirLight.shadow.camera.left = -25;
    dirLight.shadow.camera.right = 25;
    dirLight.shadow.camera.near = 0.1;
    dirLight.shadow.camera.far = 200;
    dirLight.shadow.mapSize.set(1024, 1024);
    scene.add(dirLight);

    // 渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth - 201, window.innerHeight);
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.shadowMap.enabled = true; // 是否渲染阴影
    renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 阴影类型
    document.getElementById("THREE34").appendChild(renderer.domElement);

    // 地面
    const ground = new THREE.Mesh(
      new THREE.PlaneGeometry(1000, 1000),
      new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false })
    );
    ground.rotation.x = -Math.PI / 2;
    ground.position.y = 11;
    ground.receiveShadow = true;
    scene.add(ground);

    /**
     * 后期处理
     * 这是threejs用来提供进行后期处理的一个模块,其中一个后期处理可以用来完成勾边功能
     * RenderPass是必须的
     * OutlinePass就是勾边功能的主要模块
     * 这里先将OutlinePass加入后期处理链中,后面在createPanel() 中会用到
     * 打印outlinePass,有很多属性,有兴趣的可以一个个研究,下面是一些常用的
     * - visibleEdgeColor:勾边的颜色
     * - edgeStrength:勾边锐度 Number
     * - edgeGlow:边缘辉光 Number
     * - pulsePeriod:边缘闪烁 Number
     * - edgeThickness:边缘厚度 Number
     */
    composer = new EffectComposer(renderer);

    const renderPass = new RenderPass(scene, camera);
    composer.addPass(renderPass);

    outlinePass = new OutlinePass(
      new THREE.Vector2(window.innerWidth - 201, window.innerHeight),
      scene,
      camera
    );
    outlinePass.visibleEdgeColor.set(new THREE.Color(0xffffff));
    outlinePass.edgeStrength = 3;
    outlinePass.edgeThickness = 1;
    composer.addPass(outlinePass);

    // 模型加载
    const loader = new ThreeMFLoader();
    loader.load("models/models/3mf/truck.3mf", function (object) {
      object.quaternion.setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0)); // z-up conversion
      object.traverse(function (child) {
        child.castShadow = true; // 设置模型有阴影
      });

      model = object;

      scene.add(object);

      createPanel();
      animate();
    });

    // 控制器
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.addEventListener("change", render);
    controls.minDistance = 50; // 能够将相机向内移动多少
    controls.maxDistance = 200; // 能够将相机向外移动多少
    controls.minPolarAngle = 0; // 能够旋转角度的下限
    controls.maxPolarAngle = Math.PI / 2; // 能够旋转角度的上限
    controls.enablePan = false; // 启用或禁用摄像机平移
    controls.target.set(0, 20, 0); // 控制器焦点
    controls.update();
  }

  function createPanel() {
    const panel = new GUI({ width: 310 });

    const folder1 = panel.addFolder("物体效果");

    settings = {
      勾边: false,
      勾边颜色: "0xffffff",
    };

    // outlinePass的selectedObjects属性存储的模型都会通过该方法进行勾边处理
    folder1.add(settings, "勾边").onChange((val) => {
      if (val) {
        outlinePass.selectedObjects = [model];
      } else {
        outlinePass.selectedObjects = [];
      }
    });

    folder1.addColor(settings, "勾边颜色").onChange(function (value) {
      outlinePass.visibleEdgeColor.set(value);
    });

    folder1.open();
  }

  function animate() {
    requestAnimationFrame(animate);

    render();
  }

  function render() {
    // 注意,这里就是composer.render();而不是renderer.render(scene, camera);了
    composer.render();
  }
},

写在最后

以上就是勾边功能的实现,但是有个问题需要解决,勾边的颜色会带点透明度,显得颜色很淡,不知道在哪里可以修改。