自我学习 Vue3使用three.js

1,536 阅读7分钟

画线

// 创建一个渲染器 设置背景的地方
const renderer = new THREE.WebGLRenderer();
// 设置渲染的大小区域  如果分辨率太低可以 /2 以一半来渲染
renderer.setSize( window.innerWidth, window.innerHeight ); 
// domElement是一个canvas,渲染器在其上面绘制输出,需加入页面之中
document.body.appendChild( renderer.domElement ); 

// 创建一个相机  PerspectiveCamera是透视摄像机
// 参数 视野角度 / 长宽比 / 近截面 / 远截面
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); 
// 下面2个属性是一起的
// 设置相机位置 x轴 y轴 z轴 对应焦点方向 
// 就像把相机举起来拍摄一样,y相当抬起来的高度,抬的高才能看得清
camera.position.set( 0, 0, 100 ); 
// up:相机朝向,默认(0,1,0),可以理解为那一根坐标轴向上,必须写在kookat前面
// camera.up.x = 0;
// camera.up.y = 1;
// camera.up.z = 0;
// 设置相机焦点方向 x轴 y轴 z轴 相机往哪看
camera.lookAt( 0, 0, 0 ); 


// 创建一个场景
// 放置物品,灯光和摄像机的地方
const scene = new THREE.Scene();

// 绘制线框样式几何体的材质,就是线的颜色
const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );

const points = []; 
// Vector3表示一个三维向量(x,y,z)
points.push( new THREE.Vector3( -10, 0, 0 ) ); 
points.push( new THREE.Vector3( 0, 10, 0 ) ); 
points.push( new THREE.Vector3( 10, 0, 0 ) ); 
// 注意,线是画在每一对连续的顶点之间的,而不是在第一个顶点和最后一个顶点之间绘制线条(线条并未闭合)。

// setFromPoints ( points : Array ) : this
// 存储点的信息,更高效的向GPU传递数据
const geometry = new THREE.BufferGeometry().setFromPoints( points );

// 组成一条线
// BufferGeometry —— 表示线段的顶点,默认值是一个新的BufferGeometry
// material —— 线的材质,默认值是一个新的具有随机颜色的
const line = new THREE.Line( geometry, material );

// 添加到场景
scene.add( line ); 
// 调用渲染函数
renderer.render( scene, camera );

在Vu3中,如果想在指定的DOM上面显示:

<template>
  <div class="three" ref="three" id="three">zdw</div>
</template>

<script lang="ts" setup>
    // 引入对应模块
    import { onMounted, ref } from "vue";
    import * as THREE from "three";
    
    // 获取DOM数据
    const three = ref(null);
    
    // 重点,因为setup在DOM渲染之前,获取不到,一直是null所以在生命周期执行绑定操作
    onMounted(() => {
      const content: any = three.value;
      content.appendChild(renderer.domElement);
    })

</script>

画一个立方体

// 创建一个纹理贴图,将其应用到一个表面,或者作为反射/折射贴图。
const texture = new THREE.TextureLoader();
// 加载一个资源
texture.load('xxxxxx', 
// onLoad回调
function (texture: any) {
// 立即使用纹理进行材质创建
    const material = new THREE.MeshBasicMaterial({
      map: texture,
      // 材质的颜色([Color](<> "Color")),默认值为白色 (0xffffff)。
      color: 0xffffff,
      // 不写,透明的图片会变灰色
      transparent: true, 
    });

    // 设置立体
    // 设置x,y,z
    const geometry = new THREE.BoxGeometry(20, 20, 20);
    // 创建这个mesh对象
    const cube = new THREE.Mesh(geometry, material);  
    // 设置场景x的位置
    cube.position.x = -50;
    // 设置场景x的位置 物体的远近
    cube.position.z = 2;
    // 设置场景x的位置
    cube.position.y = 20;
    // 将新创建的带贴图的几何体添加到场景内,我们就可以看到了
    scene.add(cube); 
    // 调用渲染函数
    renderer.render(scene, camera);
  }
);

实现3个球体的滚动

// 老规矩,先引入
import * as THREE from "three"

// 获取ref绑定的DOM信息,详解见上面
const three = ref(null);

// 渲染器
const renderer = new THREE.WebGLRenderer();
const fov = 75;
const aspect = 2;  // 相机默认值
const near = 0.1;
const far = 5;
// 创建一个相机  PerspectiveCamera是透视摄像机
// 参数 视野角度 / 长宽比 / 近截面 / 远截面
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// 改变相机z轴位置
camera.position.z = 2;

// 场景
const scene = new THREE.Scene();
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
// 创建一个包含盒子信息的立方几何体
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);

const makeInstance = (geometry, color, x) => {
  // 创建受灯光影响的MeshPhongMaterial
  const material = new THREE.MeshPhongMaterial({color});
  // 创建一个网格Mesh
  const cube = new THREE.Mesh(geometry, material);
  // 添加到场景
  scene.add(cube);
  // 在x轴上排成一条直线
  cube.position.x = x;
  return cube;
}

// 创建3个立方体
const cubes = [
  makeInstance(geometry, 0x44aa88,  0),
  makeInstance(geometry, 0x8844aa, -2),
  makeInstance(geometry, 0xaa8844,  2),
];

// 封装滚动方法
const render = (time: number) => {
  time *= 0.001;  // 将时间单位变为秒
  cubes.forEach((cube, ndx) => {
    const speed = 1 + ndx * .1;
    const rot = time * speed;
    cube.rotation.x = rot;
    cube.rotation.y = rot;
  });
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

// 执行滚动方法
requestAnimationFrame(render);

const color = 0xFFFFFF;
const intensity = 1;
// 创建一盏平行光(颜色 / 光照强度)
const light = new THREE.DirectionalLight(color, intensity);
// 位于摄像机前面稍微左上方一点
light.position.set(-1, 2, 4);

// 放入场景
scene.add(light);
// 渲染
renderer.render(scene, camera);

绘制一个心型


import * as THREE from "three";

const three = ref(null);
const renderer = new THREE.WebGLRenderer();

const fov = 45;
const aspect = 2;  // the canvas default
const near = 1;
const far = 1000;
// 参数 视野角度 / 长宽比 / 近截面 / 远截面
// fov表示摄像机视锥体垂直视野角度,最小值为0,最大值为180,默认值为50,实际项目中一般都定义45,因为45最接近人正常睁眼角度
// aspect表示摄像机视锥体长宽比,默认长宽比为1,即表示看到的是正方形,实际项目中使用的是屏幕的宽高比
// near表示摄像机视锥体近端面,这个值默认为0.1,实际项目中都会设置为1
// far表示摄像机视锥体远端面,默认为2000,这个值可以是无限的,说的简单点就是我们视觉所能看到的最远距离。
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// camera.position.z = 80;
camera.position.set(-29, 12, 80);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);

// 搞两个光源
{
  const color = 0xFFFFFF;
  const intensity = 1;
  const light = new THREE.DirectionalLight(color, intensity);
  light.position.set(-1, 2, 4);
  scene.add(light);
}
{
  const color = 0xFFFFFF;
  const intensity = 1;
  const light = new THREE.DirectionalLight(color, intensity);
  light.position.set(1, -2, -4);
  scene.add(light);
}

const objects = [];
const spread = 15;

// 设置图形的位置x,y轴
function addObject(x, y, obj) {
  obj.position.x = x * spread;
  obj.position.y = y * spread;

  scene.add(obj);
  objects.push(obj);
}

// 可以反射的材质
function createMaterial() {
  const material = new THREE.MeshPhongMaterial({
    side: THREE.DoubleSide,
  });

  const hue = Math.random();
  const saturation = 1;
  const luminance = .5;
  material.color.setHSL(hue, saturation, luminance);

  return material;
}

function addSolidGeometry(x, y, geometry) {
  // 网格
  const mesh = new THREE.Mesh(geometry, createMaterial());
  addObject(x, y, mesh);
}

const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
// 绘制贝塞尔曲线
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);

const extrudeSettings = {
  // 用于沿着挤出样条的深度细分的点的数量,默认值为1。
  steps: 2,
  // 挤出的形状的深度,默认值为1。
  depth: 2,
  // 对挤出的形状应用是否斜角,默认值为true。
  bevelEnabled: true,
  // 设置原始形状上斜角的厚度。默认值为0.2。
  bevelThickness: 1,
  // 斜角与原始形状轮廓之间的延伸距离,默认值为bevelThickness-0.1。
  bevelSize: 1,
  // 斜角的分段层数,默认值为3。
  bevelSegments: 2,
};

addSolidGeometry(-2, 1, new THREE.ExtrudeGeometry(shape, extrudeSettings));

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

function render(time: number) {
  time *= 0.01;

  if (resizeRendererToDisplaySize(renderer)) {
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }

  objects.forEach((obj, ndx) => {
    const speed = .1 + ndx * .05;
    const rot = time * speed;
    obj.rotation.x = rot;
    obj.rotation.y = rot;
  });

  renderer.render(scene, camera);

  requestAnimationFrame(render);
}

// 动画
requestAnimationFrame(render);

绘制一个球体围着一个球体旋转

import * as THREE from "three";

const three = ref(null);

const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer();

const fov = 40;
const aspect = 2;  // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 50, 0);
// 因为设置了set(5,5,5)导致sunMeshs局部空间5倍,导致earthMesh.position.x = 10;也是5倍
// camera.position.set(0, 150, 0);
camera.up.set(0, 0, 1);
camera.lookAt(0, 0, 0);

// 要更新旋转角度的对象数组,放入要旋转的物体
const objects = [];

// 一球多用
// 球体半径
const radius = 1;
// 水平分段数 半径+6 差不多就是球体
const widthSegments = 60;
// 垂直分段数
const heightSegments = 16;
// 生成球体
const sphereGeometry = new THREE.SphereGeometry(
    radius,
    widthSegments,
    heightSegments
);

// 设置光源
{
  const color = 0xffffff;
  const intensity = 3;
  const light = new THREE.PointLight(color, intensity);
  scene.add(light);
}

// 画轨道
const geometry = new THREE.TorusGeometry( 10, 0.1, 10, 100 );
const material = new THREE.MeshBasicMaterial( { color: 0xdddddd } );
const torus = new THREE.Mesh( geometry, material );
torus.rotateZ(1.5)
torus.rotateY(1.5)
scene.add( torus );

// 新建一个包裹太阳和地球的盒子
const solarSystem = new THREE.Object3D();
scene.add(solarSystem);
objects.push(solarSystem);

// 设置有光泽的物体
const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xffff00});
const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
// 设置太阳的大小
sunMesh.scale.set(5, 5, 5);
solarSystem.add(sunMesh);
objects.push(sunMesh);

// 包裹地球和月亮的盒子
const earthOrbit = new THREE.Object3D();
earthOrbit.position.x = 10;
solarSystem.add(earthOrbit);
objects.push(earthOrbit);

// 设置地球
const earthMaterial = new THREE.MeshPhongMaterial({
  color: 0x2233ff,
  emissive: 0x112244,
});
const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
// earthMesh.position.x = 10;
// scene.add(earthMesh);
earthOrbit.add(earthMesh);
objects.push(earthMesh);

// 画轨道
const geometry2 = new THREE.TorusGeometry( 2, 0.1, 10, 100 );
const material2 = new THREE.MeshBasicMaterial( { color: 0xdddddd } );
const torus2 = new THREE.Mesh( geometry2, material2 );
torus2.rotateZ(1.5)
torus2.rotateY(1.5)
earthOrbit.add( torus2 );

// 月亮
const moonOrbit = new THREE.Object3D();
moonOrbit.position.x = 2;
earthOrbit.add(moonOrbit);

const moonMaterial = new THREE.MeshPhongMaterial({color: 0x888888, emissive: 0x222222});
const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
moonMesh.scale.set(.5, .5, .5);
moonOrbit.add(moonMesh);
objects.push(moonMesh);

function render(time) {
  time *= 0.001;

  // 旋转
  objects.forEach((obj) => {
    obj.rotation.y = time;
  });

  renderer.render(scene, camera);

  requestAnimationFrame(render);
}

requestAnimationFrame(render);

VR全景看房

import {onMounted, ref} from "vue";
import * as THREE from "three";
// 引入轨道操作器 轨道控制使摄像机可以围绕目标旋转。
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";


const three = ref(null);

// 场景
const scene = new THREE.Scene();
// 初始化相机
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
// 设置相机位置
camera.position.z = 0.1;
// 渲染器
const renderer = new THREE.WebGLRenderer();
// 加载图片
let loader = new THREE.TextureLoader();
// 统一处理图片数据,方便
const setImgs = (event) => {
  let arr = []
  event.forEach((item) => {
    let texture = loader.load(`src/assets/VR/${item}.jpg`)
    arr.push(new THREE.MeshBasicMaterial( {map: texture} ))
  })
  return arr;
}
// 房间图片
let homeObj = {
  home1: {
    materials: setImgs(["home_l", "home_r", "home_u", "home_d", "home_b", "home_f"]),
    btnPosition: [-16, -8, -9]
  },
  home2: {
    materials: setImgs(["home2_l", "home2_r", "home2_u", "home2_d", "home2_b", "home2_f"]),
    btnPosition: [18, -8, -7]
  },
}

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

// 定义当前房间名称的参数
let currentHome = 'home1';
// 鼠标点击事件
function onMouseDown(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  //将平面坐标系转为世界坐标系
  raycaster.setFromCamera(mouse, camera);

  //得到点击的几何体
  var raycasters = raycaster.intersectObjects(scene.children);

  if (raycasters[0].object.name === 'sanshazi') {
    currentHome = currentHome === "home1" ? "home2" : "home1";
    // 重新加载房间信息
    initHome();
  }
}
// 轨道控制器,说白了就是控制鼠标操作的
const controls = new OrbitControls(camera, renderer.domElement);
// 设置阻尼感,就是你的动作有一个过程
controls.enableDamping = true;
// 监听控制器的鼠标事件,执行渲染内容
controls.addEventListener('change', () => {
  renderer.render(scene, camera)
})
//监视鼠标左键按下事件
window.addEventListener("mousedown", onMouseDown, false);
// 切换场景
const initHome = () => {
  // 切换场景前把之前的物体清除掉
  let homeMesh1 = scene.getObjectByName('ershazi')
  let locationBtn = scene.getObjectByName('sanshazi')
  scene.remove(homeMesh1, locationBtn)

  // 创建一个矩形,贴上六张材质图片,模拟室内效果
  let homeGeoMetry = new THREE.BoxGeometry(40, 40, 40);
  let homeMesh = new THREE.Mesh(homeGeoMetry, homeObj[currentHome].materials);
  homeMesh.castShadow = true
  homeMesh.position.set(0, 0, 0);
  // 设置-1.就相当于把相机反过来,从里面看
  homeMesh.geometry.scale(1, 1, -1);
  homeMesh.name = "ershazi";
  // 你写好要放进去
  scene.add(homeMesh);

  // 添加一个圆形按钮,点击后跳转到其他房间场景
  let planeGemetry = new THREE.CircleGeometry(1.2, 20);
  let planeMaterial = new THREE.LineBasicMaterial({color: 0xffffff, side: THREE.DoubleSide});

  let planeMesh = new THREE.Mesh(planeGemetry, planeMaterial);
  planeMesh.position.set(...homeObj[currentHome].btnPosition)
  planeMesh.rotateX(0.5 * Math.PI)

  planeMesh.name = "sanshazi"
  scene.add(planeMesh);
}
// 渲染效果
const render = () => {
  renderer.render(scene, camera);
  // 一帧一帧的执行动画
  requestAnimationFrame(render);
};

onMounted(() => {
  const content: any = three.value;
  // 只渲染指定的地方
  renderer.setSize(content.clientWidth, content.clientHeight)
  content.appendChild(renderer.domElement);
  initHome()
  render();
})