vue写three.js例子-相机-鼠标拾取物品(光线投射)

318 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

相信想要做3D可视化的同学都了解过three.js并且学习过,而学习的第一途径,就是官方文档。不得不说,官方文档真的很简单粗暴,属于看完就跟没看一样(当然大佬除外

但是又不能就不学了吧,只能对官方例子下手了,以下是我用vue写官方例子的一些代码和理解,请各位大佬路过指教一下(实在不知道怎么学啊!

vue引入three.js

创建vue项目的步骤我就省略了(这方面教程遍地都是

  1. 安装three.js
npm install three
  1. 容器id先写好
<template>
  <div class="item">
    <div id="THREE7"></div>
  </div>
</template>
  1. 引入three.js、GUI、CinematicCamera

GUI是three.js社区的一个UI库,用来对模型进行交互控制,使用非常的方便和简单

CinematicCamera是一个相机插件,可以用来设置焦点

import * as THREE from "three";

import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";

import { CinematicCamera } from "three/examples/jsm/cameras/CinematicCamera.js";
  1. mounted方法写一个,initThreejs()定义的变量下面会一一解释

Vector2():创建一个二维向量

mounted() {
  this.initThreejs();
},
methods: {
  initThreejs() {
    let camera, scene, raycaster, renderer;

    const mouse = new THREE.Vector2();
    let INTERSECTED;

    init();
    animate();
  }
}

init():

  1. 相机设置

CinematicCamera(角度,摄像机视锥体的长宽比)

setLens 设置相机离物体的远近程度,数字越小,离得越远;初始化相机焦距相关

position.set 设置相机位置

camera = new CinematicCamera(
  60,
  (window.innerWidth - 210) / window.innerHeight,
  1,
  1000
);
camera.setLens(5);
camera.position.set(2, 1, 500);
  1. 场景设置

scene.background:设置场景背景颜色

scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
  1. 光源设置

AmbientLight:环境光(白色,光照强度为0.3)

DirectionalLight:平行光(白色,光照强度0.35),模拟太阳光

scene.add(new THREE.AmbientLight(0xffffff, 0.3));

const light = new THREE.DirectionalLight(0xffffff, 0.35);
light.position.set(1, 1, 1).normalize(); // 理解为从(0,0,0)->(1,1,1)的方向的光
scene.add(light);
  1. 创建网格模型

BoxGeometry(长,宽,高):创建立方体

Mesh(模型,材质):创建网格模型

这里是创建了1500个颜色随机位置随机的立方体的网格模型

const geometry = new THREE.BoxGeometry(20, 20, 20);

for (let i = 0; i < 1500; i++) {
  const object = new THREE.Mesh(
    geometry,
    new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff })
  );

  object.position.x = Math.random() * 800 - 400;
  object.position.y = Math.random() * 800 - 400;
  object.position.z = Math.random() * 800 - 400;

  scene.add(object);
}
  1. 光线投射(用于进行鼠标拾取)
raycaster = new THREE.Raycaster();
  1. 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth - 201, window.innerHeight);
document.getElementById("THREE7").appendChild(renderer.domElement);
  1. 鼠标移动事件监听

在原本的例子上改了:clientX,clientY => offsetX,offsetY

document.getElementById("THREE7").addEventListener("mousemove", onDocumentMouseMove);

function onDocumentMouseMove(event) {
    event.preventDefault();
    mouse.x = (event.offsetX / (window.innerWidth - 201)) * 2 - 1;
    mouse.y = -(event.offsetY / window.innerHeight) * 2 + 1;
}
  1. GUI的使用

camera.setLens:初始化相机焦距相关

const effectController = {
  focalLength: 15,
  fstop: 2.8,
  showFocus: false,
  focalDepth: 3,
};

const matChanger = function () {
  for (const e in effectController) {
    if (e in camera.postprocessing.bokeh_uniforms) {
      camera.postprocessing.bokeh_uniforms[e].value =
        effectController[e];
    }
  }

  camera.postprocessing.bokeh_uniforms["znear"].value = camera.near;
  camera.postprocessing.bokeh_uniforms["zfar"].value = camera.far;
  camera.setLens(
    effectController.focalLength,
    camera.frameHeight,
    effectController.fstop,
    camera.coc
  );
  effectController["focalDepth"] =
    camera.postprocessing.bokeh_uniforms["focalDepth"].value;
};

const gui = new GUI();

gui.add(effectController, "focalLength", 1, 135, 0.01).onChange(matChanger);
gui.add(effectController, "fstop", 1.8, 22, 0.01).onChange(matChanger);
gui.add(effectController, "focalDepth", 0.1, 100, 0.001).onChange(matChanger);
gui.add(effectController, "showFocus", true).onChange(matChanger);

matChanger();

animate():

function animate() {
    requestAnimationFrame(animate, renderer.domElement);
    render();
}

render():

setFromCamera(在标准化设备坐标中鼠标的二维坐标,射线来源的相机):通过摄像机和鼠标位置更新射线

intersectObjects:检查所有模型(上面增加的1500个)和射线的相交部分。该方法返回一个包含有交叉部分的数组,返回结果时,相交部分将按距离进行排序,最近的位于第一个。

  • distance:射线投射原点和相交部分之间的距离

  • object:相交的物体

camera.focusAt(焦距):修改相机的焦距

INTERSECTED.material.emissive.getHex():object本身的颜色

INTERSECTED.material.emissive.setHex(0xff0000):设置对象为红色

camera.renderCinematic(scene, renderer):使用这个方法渲染场景, 代替renderer.render(scene, camera)

整体的逻辑是通过鼠标和摄像机的位置得到一根射线,距离最近的对象显示为红色,其他对象显示其本身的颜色。

function render() {
    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(scene.children, false);

    if (intersects.length > 0) {
      const targetDistance = intersects[0].distance;

      camera.focusAt(targetDistance);

      if (INTERSECTED != intersects[0].object) {
        if (INTERSECTED)
          INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);

        INTERSECTED = intersects[0].object;
        INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
        INTERSECTED.material.emissive.setHex(0xff0000);
      }
    } else {
      if (INTERSECTED)
        INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);

      INTERSECTED = null;
    }

    if (camera.postprocessing.enabled) {
      camera.renderCinematic(scene, renderer);
    } else {
      scene.overrideMaterial = null;

      renderer.clear();
      renderer.render(scene, camera);
    }
}