Three.js新手实战二

106 阅读4分钟

加载外部三维模型

1、在新手实战一的代码基础上修改,以下是基础代码:

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls";

//1、场景
const scene = new THREE.Scene();
//2、摄像机
//四个参数,摄像机锥体角度,摄像机锥体看到的宽高比(设置成宽口的宽高比就可以了),摄像机锥体看到物体的最近和最远的距离
const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  1,
  1000
);
//3、渲染器
const render = new THREE.WebGLRenderer({
  //抗锯齿
  antialias: true,
});
//设置渲染的画面宽高
render.setSize(window.innerWidth, window.innerHeight);

document.getElementById("app").appendChild(render.domElement);


//设置camera的距离,否则摄像机在原点(0,0,0)的位置。
camera.position.z = 10;
camera.position.x = 4;
camera.position.y = 10;

//添加轨道控制器,控制摄像机的位置,不影响在页面上的位置。
const controls = new OrbitControls(camera, render.domElement);
//是否开起控制器阻尼系数(理解为对旋转的阻力,系数越小阻力越小)
//请注意,如果该值被启用,必须在动画循环中调用.update()
controls.enableDamping = true;

//添加辅助坐标系和辅助网格
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);

//辅助网格,一个参数是格子的大小,第二个参数是一行有几个格子
const gridHelper = new THREE.GridHelper(20, 10);

gridHelper.material.opacity = 0.2;
gridHelper.material.transparent = true;

scene.add(gridHelper);

function init(time) {
  controls.update();
  //在页面渲染场景和相机
  render.render(scene, camera);
  //requestAnimationFrame浏览器的方法
  requestAnimationFrame(init);
}

init();

//设置画面自适应
window.addEventListener("resize", () => {
  //更新摄像头
  camera.aspect = window.innerWidth / window.innerHeight;
  //更新摄像头的投影矩阵
  camera.updateProjectionMatrix();

  //更新渲染器
  render.setSize(window.innerWidth, window.innerHeight);
  //设置渲染器的像素比
  render.setPixelRatio(window.devicePixelRatio);
});

2、引入gltf模型加载库GLTFLoader.js

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

3、GLTF加载器

const loader = new GLTFLoader();

4、加载

模型库:www.shvr.work/

注:glb文件放到public文件夹中

loader.load('/car.glb', function ( gltf ) {
  const model = gltf.scene;
  console.log('gltf对象场景属性',model);
  //将模型在y轴方向旋转45度
  model.rotation.y = 45;
  // 返回的场景对象gltf.scene插入到threejs场景中
  scene.add(model);
})

5、添加地板

const geometry = new THREE.PlaneGeometry(60, 60); //默认在XOY平面上
  const material = new THREE.MeshPhysicalMaterial({
    color: "yellow",
    metalness: 0.9, //车外壳金属度
    roughness: 0.5, //车外壳粗糙度
    envMapIntensity: 2.5, //环境贴图对Mesh表面影响程度
  });
  const mesh = new THREE.Mesh(geometry, material);
  //旋转地板角度
  mesh.rotation.x = 30;
  
  scene.add(mesh)

6、添加聚光灯

const spotLight = new THREE.SpotLight("#fff", 2000.0);
  spotLight.angle = Math.PI / 6; //散射角度
  spotLight.penumbra = 0.2;
  spotLight.decay = 2;
  spotLight.distance = 30;
  spotLight.shadow.radius = 30;
  spotLight.shadow.mapSize.set(4096, 4096);

  //灯的位置
  spotLight.position.set(0, 20, -10);
  //目标的位置
  spotLight.target.position.set(0, 0, 0);
  // 设置产生投影的网格模型
  spotLight.castShadow = true;

  scene.add(spotLight); //光源添加到场景中

7、设置圆柱型的展厅

// CylinderGeometry:圆柱
  const geometry = new THREE.CylinderGeometry(50, 50, 100);
  const material = new THREE.MeshBasicMaterial({
    color: 0xffff00,
    side: THREE.DoubleSide,
  });
  const cylinder = new THREE.Mesh(geometry, material);
  scene.add(cylinder);

8、旋转会看到车底,缩小会看到外面的圆柱体。我们通过设置限制来解决这个问题

//最大缩放,最小缩放
controls.maxDistance = 6;
controls.minDistance = 1;

//最小旋转角度
controls.minPolarAngle = 0;
//最小旋转角度
controls.maxPolarAngle = (80 / 360) * 2 * Math.PI;

9、修改车身

three.js编辑器:three.js editor (threejs.org)

9.1 通过编辑器导入.glb模型文件,增加灯光

image.png

9.2 点击车身,在右侧可查看到此材质的名称

image.png

9.3 递归模型节点

  // 递归遍历所有模型节点批量修改材质
  model.traverse(function(obj) {
    if(obj.isMesh)
    console.log(obj, 'obj');
  })

9.4 修改车身的材质

//车身和两个门的材质
let bodyMesh = new THREE.MeshPhysicalMaterial({
      color: "green",
      metalness: 1, //车外壳金属度
      roughness: 0.5, //车外壳粗糙度
      clearcoat: 1.0, //物体表面清漆层或者说透明涂层的厚度
      clearcoatRoughness: 0.03, //透明涂层表面的粗糙度
    });
    obj.material = material;
  };
  
// 递归遍历所有模型节点批量修改材质
  model.traverse(function (obj) {
    if (obj.isMesh) {
      if (obj.name == "Object_103" || obj.name == "Object_77" || obj.name == "Object_64") {
        obj.material = bodyMesh;
      }
    }
  });
  

10、添加图形界面

lil-gui:lil-gui 0.18.2 (georgealways.com)

image.png 10.1 定义面板,面板中的属性和方法

  import GUI from "lil-gui";

  const gui = new GUI();
  const myObject = {
    color: "#ffffff",
    carOpen,
    carIn,
    carClose
  };

  gui
    .addColor(myObject, "color")
    .name("颜色")
    .onChange((value) => {
      //设置车身的颜色
      material.color.set(value);
    });

  

10.2 添加车的相关操作

//获取车门
let doors = [];

//在递归遍历所有模型节点的时候获取车门------详情见9.4
if (obj.name === 'Empty001_16' || obj.name === 'Empty002_20') {
    // 门
    doors.push(obj)
}

gui.add(myObject, "carOpen").name('打开车门');
gui.add(myObject, "carClose").name('关门车门')
gui.add(myObject, "carIn").name('车内视角');
gui.add(myObject, "carClose").name('车外视角')
  
function carOpen() {
    for (let i = 0; i < doors.length; i++) {
        setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[i])
    }
}

function carClose() {
    for (let i = 0; i < doors.length; i++) {
        setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[i])
    }
}

function carIn() {
    setAnimationCamera({ cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 }, { cx: -0.27, cy: 0.83, cz: 0.60, ox: 0, oy: 0.5, oz: -3 });
}

function carOut() {
    setAnimationCamera({ cx: -0.27, cy: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3 }, { cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 });
}

function setAnimationDoor(start, end, mesh) {
    const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
    tween.onUpdate((that) => {
        mesh.rotation.x = that.x
    })
    tween.start()
}

function setAnimationCamera(start, end) {
    const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
    tween.onUpdate((that) => {
        //  camera.postition  和 controls.target 一起使用
        camera.position.set(that.cx, that.cy, that.cz)
        controls.target.set(that.ox, that.oy, that.oz)
    })
    tween.start()
}

11、Raycaster(射线拾取模型)

注:通过点击材质,实现与10.2一样的功能

const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
window.addEventListener("click", onPointerMove);

function onPointerMove(event) {
  // calculate pointer position in normalized device coordinates
  // (-1 to +1) for both components

  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;

  // update the picking ray with the camera and pointer position
  raycaster.setFromCamera(pointer, camera);

  // calculate objects intersecting the picking ray
  const intersects = raycaster.intersectObjects(scene.children);
  console.log(intersects, "intersects");
  for (let i = 0; i < intersects.length; i++) {
    if (
      intersects[i].object.name == "Object_77" ||
      intersects[i].object.name == "Object_64"
    ) {
      carOpen();
    }else
    {
      carClose();
    }
  }
}

12、添加车的阴影

//渲染器支持阴影
render.shadowMap.enabled = true

// 递归遍历所有模型节点批量修改材质中添加以下代码:
//车模型产生阴影
obj.castShadow = true;

//地板接收阴影
mesh.receiveShadow = true;