Threejs笔记文档(二)

450 阅读13分钟

6-1 初识Points与点材质

  • 创建一个球,展示方式为网格,颜色为红色

    const sphereGeometry = new THREE.SphereGeometry(3, 20, 20);
    const material = new THREE.MeshBasicMaterial({
      color: 0xff0000,
      wireframe: true,
    });
    const sphere = new THREE.Mesh(sphereGeometry, material);
    scene.add(sphere);
    

    1721810638991.png

  • 创建点材质

    const sphereGeometry = new THREE.SphereGeometry(3, 20, 20);
    // const material = new THREE.MeshBasicMaterial({
    //   color: 0xff0000,
    //   wireframe: true,
    // });
    // const sphere = new THREE.Mesh(sphereGeometry, material);
    // scene.add(sphere);
    ​
    // 设置点材质(大小)
    const pointsMaterial = new THREE.PointsMaterial();
    pointsMaterial.size = 0.1;
    // 创建点堆物体
    const points = new THREE.Points(sphereGeometry, pointsMaterial);
    scene.add(points);
    

    1721810744427.png

6-2 深度解析点材质属性

  • 设置点材质颜色

    pointsMaterial.color.set(0xfff000);
    

    1721811277734.png

  • 设置是否按相机深度衰减:默认true,一般不进行设置(上图中的方块近大远小,设置为false每个点会一样大)

    pointsMaterial.sizeAttenuation = false;
    

    1721811392948.png

  • 为点材质设置纹理,点材质设置纹理的时候是对整个物体进行纹理设置,而不是物体上面的每一个点设置

    // 注掉相机深度衰减
    // pointsMaterial.sizeAttenuation = false;
    ​
    // 载入纹理
    const textureLoader = new THREE.TextureLoader();
    const texture = textureLoader.load("./textures/particles/1.png");
    pointsMaterial.map = texture;
    pointsMaterial.alphaMap = texture;
    pointsMaterial.transparent = true;
    

    1721813727633.png

    threejs版本:0.152.2 和 0.166.0

    • 设置map时,材质给整个物体区域设置纹理

    threejs版本:0.137.5

    • 设置map时,材质给整个物体区域内的每一个点设置纹理
  • 设置点材质后,材质图案有黑边的时候,前面的点会挡到后面的点

    1721877566212.png

    可以通过设置透明度贴图来解决

    pointsMaterial.alphaMap = texture;
    pointsMaterial.transparent = true;
    

    ![1721877639548.png]

  • depthWrite:Boolean 渲染此材质是否对缓冲区有任何影响。默认为true。

    pointsMaterial.depthWrite = false;
    

    ![1721877937698.png]

  • blending:Blending

    • 使用此材质显示对象时要使用何种混合

    • 必须将其设置为CustomBlending才能使用自定义blendSrc, blendDst 或者 [page:Constant blendEquation]。 混合模式所有可能的取值请参阅constants。默认值为NormalBlending

    • 混合模式

      THREE.NoBlending  // 无混合
      THREE.NormalBlending  // 正常混合
      THREE.AdditiveBlending  // 叠加混合
      THREE.SubtractiveBlending  // 减法混合
      THREE.MultiplyBlending  // 乘法混合
      THREE.CustomBlending  // 自定义混合
      

      1721878478547.png

6-3 应用定点着色器打造绚丽多彩的星空

  • 创建星空效果

    // 不要之前的球体范围了,保留材质等的设置// 创建缓冲物体
    const particlesGeometry = new THREE.BufferGeometry();
    //设置5000个点
    const count = 5000;
    // 设置缓冲区数组
    const positions = new Float32Array(count * 3);
    // 设置顶点
    for (let i = 0; i < count * 3; i++) {
      positions[i] = (Math.random() - 0.5) * 30;
    }
    // 设置各个点的位置
    particlesGeometry.setAttribute(
      "position",
      new THREE.BufferAttribute(positions, 3)
    );
    

    1721879114207.png

  • 创建五彩的星空效果

    // 设置每一个顶点的颜色
    const colors = new Float32Array(count * 3);
    // 设置顶点
    for (let i = 0; i < count * 3; i++) {
      positions[i] = (Math.random() - 0.5) * 30;
      colors[i] = Math.random(); // 设置颜色值 Math.random() 默认0-1,对于颜色来说就是0-255
    }
    particlesGeometry.setAttribute(
      "position",
      new THREE.BufferAttribute(positions, 3)
    );
    // 设置顶点颜色
    particlesGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
    ​
    /**
     * 以上设置不会启效果,需要启用顶点颜色设置
     */
    pointsMaterial.vertexColors = true;
    

    1721879562317.png

6-4 通过封装与相机裁剪实现漫天飞舞的雪花

  • 素材网站:

    1721879765330.png

    • iconfont
    • 爱给网
  • 实现漫天飞舞的雪花

    ![snow.png]

    // 将纹理替换为雪花的纹理
    const texture = textureLoader.load("./textures/particles/snow.png");
    ​
    // 将顶点的颜色值设置为1
    for (let i = 0; i < count * 3; i++) {
      positions[i] = (Math.random() - 0.5) * 30;
      colors[i] = 1;
    }
    ​
    // 将之前设置的黄颜色注释掉
    // pointsMaterial.color.set(0xfff000);
    ​
    // 在渲染函数中设置旋转值
    function render() {
      let time = clock.getElapsedTime();
      points.rotation.x = time * 0.05;
    ​
      controls.update();
      renderer.render(scene, camera);
    ​
      // 渲染下一帧的时候调用render函数
      requestAnimationFrame(render);
    }
    

    1721890390612.png

  • 将创建雪花封装成函数

    // 创建物体
    function createPoints(url, size = 0.5, color) {
      // 创建物体
      const particlesGeometry = new THREE.BufferGeometry();
      const count = 10000;
    ​
      // 设置缓冲区数组
      const positions = new Float32Array(count * 3);
      // 设置每一个顶点的颜色
      const colors = new Float32Array(count * 3);
      // 设置顶点
      for (let i = 0; i < count * 3; i++) {
        positions[i] = (Math.random() - 0.5) * 30;
        if (color) {
          colors[i] = color;
        } else {
          colors[i] = Math.random();
        }
      }
      particlesGeometry.setAttribute(
        "position",
        new THREE.BufferAttribute(positions, 3)
      );
      particlesGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
    ​
      // 设置点材质(大小)
      const pointsMaterial = new THREE.PointsMaterial();
      pointsMaterial.size = size;
    ​
      // 载入纹理
      const textureLoader = new THREE.TextureLoader();
      const texture = textureLoader.load(`./textures/particles/${url}.png`);
      pointsMaterial.map = texture;
      pointsMaterial.alphaMap = texture;
      pointsMaterial.transparent = true;
      pointsMaterial.depthWrite = false;
      pointsMaterial.blending = THREE.AdditiveBlending;
      // 启用顶点颜色设置
      pointsMaterial.vertexColors = true;
    ​
      // 创建点堆物体
      const points = new THREE.Points(particlesGeometry, pointsMaterial);
      scene.add(points);
      return points;
    }
    ​
    // 创建三个不同的粒子
    const pointsSnow = createPoints("snow", 0.05, 1);
    const pointsStart = createPoints("9", 0.1, 1);
    const points08 = createPoints("8", 0.1, 1);
    ​
    // 设置三个不同粒子的运动方向
    function render() {
      let time = clock.getElapsedTime();
      pointsSnow.rotation.x = time * 0.05;
      pointsStart.rotation.x = time * 0.05;
      pointsStart.rotation.y = time * 0.01;
      points08.rotation.x = time * 0.01;
      points08.rotation.y = time * 0.01;
    ​
      controls.update();
      renderer.render(scene, camera);
    ​
      // 渲染下一帧的时候调用render函数
      requestAnimationFrame(render);
    }
    

    上述方式设置完后,可以看到有的粒子往相反的方向(上)运动,这个时候可以设置相机的近端面和远端面

    // 创建相机
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      10 // 将远端面设置的和相机的位置一样,就可以看到粒子都是往下运动的
    );
    ​
    // 设置相机位置
    camera.position.set(0, 0, 10);
    

    ![1721895058035.png]

6-5 运用数学知识打造复杂形状臂旋星系01

  • 目标:给x轴上面设置点

    // 创建纹理加载器并加载纹理
    const textureLoader = new THREE.TextureLoader();
    const texture = textureLoader.load("./textures/particles/1.png");
    ​
    // 设置参数
    const params = {
      count: 100,
      size: 0.1,
      radius: 5,
      branch: 3,
      color: "#ffffff",
    };
    ​
    let geometry = null;
    let material = null;
    let points = null;
    // 生成点的函数
    const generateGalaxy = () => {
      // 生成顶点
      geometry = new THREE.BufferGeometry();
      // 随机生成位置
      const positions = new Float32Array(params.count * 3);
      // 设置顶点颜色
      const colors = new Float32Array(params.count * 3);
      // 循环生成顶点(count个顶点,只给x设置值,y和z应该是0)
      for (let i = 0; i < params.count; i++) {
        const current = i * 3;
        positions[current] = Math.random() * params.radius;
        positions[current + 1] = 0;
        positions[current + 2] = 0;
      }
      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
    ​
      // 设置点参数
      material = new THREE.PointsMaterial({
        color: new THREE.Color(params.color),
        size: params.size,
        sizeAttenuation: true,
        depthWrite: false, 
        blending: THREE.AdditiveBlending,
        map: texture,
        alphaMap: texture,
        transparent: true,
        // vertexColors: true,
      });
      points = new THREE.Points(geometry, material);
      scene.add(points);
    };
    generateGalaxy();
    

    1721896723737.png

6-6 运用数学知识打造复杂形状臂旋星系02

  • 目标:总共设置三个分支,在三个分支都生成点

    // 循环生成顶点
    for (let i = 0; i < params.count; i++) {
        // 当前的点应该在哪一条分支的角度上(分支值 * 两个分支之间的角度)
        const branchAngle = (i % params.branch) * ((2 * Math.PI) / params.branch);
        // 当前点距圆心的位置
        const distance = Math.random() * params.radius;
        const current = i * 3;
        positions[current] = Math.cos(branchAngle) * distance;
        positions[current + 1] = 0;
        positions[current + 2] = Math.sin(branchAngle) * distance;
    }
    

    将设置的count数更改为1000,则有以下效果

    1721897627075.png

6-7 运用数学知识打造复杂形状臂旋星系03

  • 目标1:为三个分支设置一定的轨迹

    const params = {
      count: 1000,
      size: 0.1,
      radius: 5,
      branch: 3,
      color: "#ffffff",
      rotateScale: 0.3, // 设置点的旋转角度
    };
    ​
    // 循环生成顶点
    for (let i = 0; i < params.count; i++) {
        // 当前的点应该在哪一条分支的角度上
        const branchAngle = (i % params.branch) * ((2 * Math.PI) / params.branch);
        // 当前点距圆心的位置
        const distance = Math.random() * params.radius;
        const current = i * 3;
        
        // 为每一个点的x, y, z分别加上一个[0-1]的随机数
        const randomX = Math.random();
        const randomY = Math.random();
        const randomZ = Math.random();
    ​
        // 让近原点的点旋转角度少一点,远原点的点旋转角度多一点,再加上一点偏移
        positions[current] =
            Math.cos(branchAngle + distance * params.rotateScale) * distance +
            randomX;
        positions[current + 1] = 0 + randomY;
        positions[current + 2] =
            Math.sin(branchAngle + distance * params.rotateScale) * distance +
            randomZ;
    }
    

    这个时候的相机远面端要大于相机所在位置,否则看不全整个图像

    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      20 // 值要大于相机所在位置的值
    );
    ​
    camera.position.set(0, 0, 10);
    

    1721899240165.png

  • 目标2:将各个分支上的点集中一下

    // 先将点的数量增加至10000
    ​
    for (let i = 0; i < params.count; i++) {
        // 当前的点应该在哪一条分支的角度上
        const branchAngle = (i % params.branch) * ((2 * Math.PI) / params.branch);
        // 当前点距圆心的位置
        const distance = Math.random() * params.radius;
        const current = i * 3;
    ​
        // 因为x, y, z值有正有负,因此用的是三次方
        const randomX = Math.pow(Math.random() * 2 - 1, 3); // -1~1
        const randomY = Math.pow(Math.random() * 2 - 1, 3);
        const randomZ = Math.pow(Math.random() * 2 - 1, 3);
    ​
        positions[current] =
            Math.cos(branchAngle + distance * params.rotateScale) * distance +
            randomX;
        positions[current + 1] = 0 + randomY;
        positions[current + 2] =
            Math.sin(branchAngle + distance * params.rotateScale) * distance +
            randomZ;
    }
    

    1721900109096.png

  • 目标3:将集中点后的分支上的点设置为——靠近原点位置多,远离原点位置少

    for (let i = 0; i < params.count; i++) {
        const branchAngle = (i % params.branch) * ((2 * Math.PI) / params.branch);
        // 当前点距圆心的位置(将此处乘的半径在进行一次[0-1]的随机)
        const distance = Math.random() * params.radius * Math.pow(Math.random(), 3);
        const current = i * 3;
    ​
        const randomX = Math.pow(Math.random() * 2 - 1, 3); // -1~1
        const randomY = Math.pow(Math.random() * 2 - 1, 3);
        const randomZ = Math.pow(Math.random() * 2 - 1, 3);
    ​
        positions[current] =
            Math.cos(branchAngle + distance * params.rotateScale) * distance +
            randomX;
        positions[current + 1] = 0 + randomY;
        positions[current + 2] =
            Math.sin(branchAngle + distance * params.rotateScale) * distance +
            randomZ;
    }
    

    1721900477189.png

    1721900488736.png

  • 目标4:现在上下是平的,上下也做成靠近中间的点多,其它位置少

    for (let i = 0; i < params.count; i++) {
        const branchAngle = (i % params.branch) * ((2 * Math.PI) / params.branch);
        // 当前点距圆心的位置
        const distance = Math.random() * params.radius * Math.pow(Math.random(), 3);
        const current = i * 3;
        
        // 给每一个点加的随机数乘以(半径减去点距圆心的距离),再除以5——离得越远的点半径减去点距圆心的距离越短,即x, y, z偏移的越少,就形成了集中的效果
        const randomX =
              (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
        const randomY =
              (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
        const randomZ =
              (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
    ​
        positions[current] =
            Math.cos(branchAngle + distance * params.rotateScale) * distance +
            randomX;
        positions[current + 1] = 0 + randomY;
        positions[current + 2] =
            Math.sin(branchAngle + distance * params.rotateScale) * distance +
            randomZ;
    }
    

    1721900968631.png

    1721901000392.png

  • 可以设置分支个数

    // 设置参数
    const params = {
      count: 10000,
      size: 0.1,
      radius: 5,
      branch: 6, // 设置分支为6个
      color: "#ffffff",
      rotateScale: 0.3,
    };
    

1721901766590.png

6-8 运用颜色收敛方法设置星系臂旋渐变

  • 目标:给星系上色,从中间向四周扩散,中间颜色亮,越往外颜色越暗

    • lerp(color: Color, alpha: Float): Color

      color - 用于收敛的颜色

      alpha - 介于0-1的数字

  • 生成目标效果

    // 设置颜色和结束颜色
    const params = {
      count: 10000,
      size: 0.1,
      radius: 5,
      branch: 3,
      color: "#ff6030", // 颜色:红色
      rotateScale: 0.3,
      endColor: "#1b3894", // 结束颜色:蓝色
    };
    ​
    // 创建颜色对象
    const centerColor = new THREE.Color(params.color);
    const endColor = new THREE.Color(params.endColor);
    ​
    const generateGalaxy = () => {
      geometry = new THREE.BufferGeometry();
      const positions = new Float32Array(params.count * 3);
      const colors = new Float32Array(params.count * 3);
      for (let i = 0; i < params.count; i++) {
        const branchAngle = (i % params.branch) * ((2 * Math.PI) / params.branch);
        const distance = Math.random() * params.radius * Math.pow(Math.random(), 3);
        const current = i * 3;
    ​
        const randomX =
          (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
        const randomY =
          (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
        const randomZ =
          (Math.pow(Math.random() * 2 - 1, 3) * (params.radius - distance)) / 5;
    ​
        positions[current] =
          Math.cos(branchAngle + distance * params.rotateScale) * distance +
          randomX;
        positions[current + 1] = 0 + randomY;
        positions[current + 2] =
          Math.sin(branchAngle + distance * params.rotateScale) * distance +
          randomZ;
    ​
        // 生成混合颜色,形成渐变色——先克隆颜色,再和结束颜色进行混合,最后设置点的rgb颜色值
        const mixColor = centerColor.clone();
        mixColor.lerp(endColor, distance / params.radius);
        colors[current] = mixColor.r;
        colors[current + 1] = mixColor.g;
        colors[current + 2] = mixColor.b;
      }
      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      // 将生成的颜色设置给缓冲区
      geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
    ​
      material = new THREE.PointsMaterial({
        // color: new THREE.Color(params.color), // 将材质颜色给注掉,否则会影响混合颜色的显示
        size: params.size,
        sizeAttenuation: true,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
        map: texture,
        alphaMap: texture,
        transparent: true,
        vertexColors: true, // 设置启用顶点颜色
      });
      points = new THREE.Points(geometry, material);
      scene.add(points);
    };
    

    1721902972027.png

7-1 投射光线实现三维物体交互

  • 光线投射(Raycaster)

    • 先获取鼠标点在屏幕上的位置,x/y取值范围为-1~1

    • 根据鼠标位置设置相机投射位置

      • setFromCamera(coords: Vector2, camera: Camera): null

        coords——在标准化设备坐标中鼠标的二维坐标 - X分量与Y分量应当在-1~1之间

        camera——射线所来源的摄像机

        使用一个新的原点和方向来更新射线

    • 相交物体检测(intersectObject 或 intersectObjects)

      • intersectObjects( objects : Array, recursive : Boolean, optionalTarget : Array ) : Array

        objects —— 检测和射线相交的一组物体。 recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为true。 optionalTarget —— (可选)设置结果的目标数组。如果不设置这个值,则一个新的Array会被实例化;如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)

  • 实现点击物体将物体的填充改成红色(点到哪个格子填充哪个格子)

    // 创建立方体
    const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
    // 创建材质,设置网格显示,默认颜色为白色
    const material = new THREE.MeshBasicMaterial({
      wireframe: true,
    });
    // 创建一个红色材质
    const redMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
    ​
    // 创建1000个立方体
    let cubeArr = [];
    for (let i = -5; i < 5; i++) {
      for (let j = -5; j < 5; j++) {
        for (let k = -5; k < 5; k++) {
          // 利用立方体和材质创建物体,存入数组
          const cube = new THREE.Mesh(cubeGeometry, material);
          cube.position.set(i, j, k);
          scene.add(cube);
          cubeArr.push(cube);
        }
      }
    }
    ​
    // 创建投射光线对象
    const raycaster = new THREE.Raycaster();
    ​
    // 设置鼠标的位置对象
    const mouse = new THREE.Vector2();
    ​
    // 监听鼠标位置
    window.addEventListener("click", (event) => {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1; // X轴从左到右是-1~1
      mouse.y = -((event.clientY / window.innerHeight) * 2 - 1); // Y轴从下到上是-1~1,和js屏幕坐标方向相反,因此是负的
      // 设置投射位置位置
      raycaster.setFromCamera(mouse, camera);
      let result = raycaster.intersectObjects(cubeArr);
      if (result[0]) {
        result[0].object.material = redMaterial;
      }
    });
    

    1721963473393.png

9 应用物理引擎设置物体相互作用

9-1 认识物理引擎与cannon安装

之前使用的THREE创建物体以及操作是渲染引擎渲染的,真实世界的效果(比如两个球碰撞然后相互弹开)是由物理引擎渲染的,物理引擎和渲染引擎共同工作才能实现真实的效果。

cannon是一个物理引擎。

  • 安装并导入

    npm i cannon-es --save
    ​
    import * as CANNON from "cannon-es";
    console.log(CANNON);
    

9-2 使用物理引擎关联Threejs物体

(1)创建小球和平面

// 创建球和平面同时设置阴影效果
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshStandardMaterial();
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true; // 投射阴影
scene.add(sphere);
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(10, 10),
  new THREE.MeshStandardMaterial()
);
floor.position.set(0, -5, 0);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true; // 接收阴影
scene.add(floor);
​
​
// 添加环境光和平行光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.castShadow = true; // 投射阴影
scene.add(directionalLight);

1722329138627.png

(2)创建物理环境的球体,并让它进行自由落体运动

// 创建物理世界
// 第一种方式
// const world = new CANNON.World({
//   gravity: new CANNON.Vec3(0, -9.8, 0), // 为物理环境设置重力
// });// 第二种方式
const world = new CANNON.World();
// 为物理环境设置重力
world.gravity.set(0, -9.8, 0);
// 创建物理世界的小球
const sphereShape = new CANNON.Sphere(1);
// 设置物体材质
const sphereWorldMaterial = new CANNON.Material("iron");
// 创建小球物体
const sphereBody = new CANNON.Body({
  mass: 1, // 质量
  position: new CANNON.Vec3(0, 0, 0),
  shape: sphereShape,
  // 物体的材质
  material: sphereWorldMaterial,
});
// 将物体添加至物理世界
world.addBody(sphereBody);
​
// 这个时候只在物理世界创建了小球,它还不能运动,因为没有跟renderer渲染器渲染的小球进行关联function render() {
  let deltaTime = clock.getDelta();
​
  // 更新物理引擎里世界的物体,每秒120帧
  world.step(1 / 120, deltaTime);
  // 设置renderer渲染器中的小球位置和物体世界中的小球同步
  sphere.position.copy(sphereBody.position);
​
  controls.update();
  renderer.render(scene, camera);
  // 渲染下一帧的时候调用render函数
  requestAnimationFrame(render);
}
​
// 这下就能看见小球在做自由落体运动

1722329519169.png

9-3 设置固定不动的地面与小球碰撞

  • 创建物理世界的地板

    const floorShape = new CANNON.Plane();
    const floorWorldMaterial = new CANNON.Material("floor");
    const floorBody = new CANNON.Body();
    floorBody.mass = 0; // 固定不动
    floorBody.addShape(floorShape);
    // 设置地面位置
    floorBody.position.set(0, -5, 0);
    // 旋转地面的位置(按照第一个参数的向量方向,旋转第二个参数的角度)
    floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
    floorBody.material = floorWorldMaterial;
    world.addBody(floorBody);
    

    1722331401819.png

9-4 监听碰撞事件和控制碰撞音效

  • 准备素材,可自行下载

    1722332387742.png

  • 创建碰撞事件,使其碰撞时有音效

    // 创建击打声音
    const hitSound = new Audio("assets/metalHit.mp3");
    // 添加监听碰撞事件
    function HitEvent(e) {
      console.log(e);
      // 获取碰撞强度
      const impactStrength = e.contact.getImpactVelocityAlongNormal();
      console.log(impactStrength);
      // 强度大于5的时候,播放音效,播放音效的前提是需要交互,谷歌浏览器不支持自动出现音效
      if (impactStrength > 5) {
        hitSound.play();
      }
    }
    sphereBody.addEventListener("collide", HitEvent);
    

9-5 关联材质设置摩擦与弹性系数

(1)设置物理世界物体的材料名称

// 设置球体物体材质
const sphereWorldMaterial = new CANNON.Material("sphere");
​
// 设置平面物体材质
const floorWorldMaterial = new CANNON.Material("floor");

(2)设置两种材质碰撞的参数

// 设置两种材质碰撞的参数
const defaultContactMaterial = new CANNON.ContactMaterial(
  sphereWorldMaterial, // 第一种材质
  floorWorldMaterial, // 第二种材质
  {
    friction: 0.1, // 摩擦系数
    restitution: 0.7, // 恢复系数(弹性)
  }
);
// 将材料的关联设置添加到物理世界中
world.defaultContactMaterial = defaultContactMaterial;

(3)监听碰撞事件中重新设置音效参数

// 添加监听碰撞事件
function HitEvent(e) {
  console.log(e);
  // 获取碰撞强度
  const impactStrength = e.contact.getImpactVelocityAlongNormal();
  console.log(impactStrength);
  if (impactStrength > 1) {
    // 每一次碰撞都重新播放音效
    hitSound.currentTime = 0;
    // 设置音效的音量,范围[0-1]
    hitSound.volume =
      hitSound.volume - 0.2 < 0 ? hitSound.volume : hitSound.volume - 0.2;
    // 播放音效
    hitSound.play();
  }
}
sphereBody.addEventListener("collide", HitEvent);

9-6 立方体相互碰撞后旋转效果

(1)平面和其余不变,将立方体的操作封装为一个函数

// 设置多个立方体,使用数组
const cubeArr = [];
// 设置物体材质(写在外面,默认材料使用)
const cubeWorldMaterial = new CANNON.Material("cube");
​
function createCube() {
  const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
  const cubeMaterial = new THREE.MeshStandardMaterial();
  const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
  cube.castShadow = true;
  scene.add(cube);
​
  // 创建物理世界的立方体
  const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); // 这里要写宽度的一般
​
  const cubeBody = new CANNON.Body({
    mass: 1, // 质量
    position: new CANNON.Vec3(0, 0, 0),
    shape: cubeShape,
    // 物体的材质
    material: cubeWorldMaterial,
  });
  // 将物体添加至物理世界
  world.addBody(cubeBody);
​
  // 添加监听碰撞事件
  function HitEvent(e) {
    console.log(e);
    // 获取碰撞强度
    const impactStrength = e.contact.getImpactVelocityAlongNormal();
    console.log(impactStrength);
    if (impactStrength > 1) {
      hitSound.currentTime = 0;
      hitSound.volume = impactStrength / 12 > 1 ? 1 : impactStrength / 12;
      console.log(hitSound.volume);
      hitSound.play();
    }
  }
  cubeBody.addEventListener("collide", HitEvent);
  cubeArr.push({
    mesh: cube,
    body: cubeBody,
  });
}

(2)渲染函数中将渲染器物体和物理物体相关联

function render() {
  let deltaTime = clock.getDelta();
​
  // 更新物理引擎里世界的物体
  world.step(1 / 120, deltaTime);
​
  cubeArr.forEach((item) => {
    item.mesh.position.copy(item.body.position);
    // 设置渲染的物体跟随物理的旋转
    item.mesh.quaternion.copy(item.body.quaternion);
  });
​
  controls.update();
  renderer.render(scene, camera);
  // 渲染下一帧的时候调用render函数
  requestAnimationFrame(render);
}

(3)设置点击创建立方体

window.addEventListener("click", () => {
  createCube();
});

9-7 给物体施加作用力

cubeBody.applyLocalForce(
    // 添加的力的大小和方向
    new CANNON.Vec3(180, 0, 0),
    // 施加的力所在的位置
    new CANNON.Vec3(0, 0, 0)
);

能观察到立方体是偏移的