three.js(三)

文章目录

  • 前言
  • 坐标辅助器(就是三条x y z轴的线段)
  • 轨道控制器
  • 物体位移与父子元素
  • 欧拉角的旋转
  • 设置画布自适应大小
  • 监听全屏
  • lil-gui工具的使用
  • 纹理贴图(搞懂了!!!)
    • 关于找图片的位置
    • 打包后找图片的位置
  • 纹理贴图(pro)
  • 纹理贴图(pro max)[等我有了新的理解就在这里补充]
  • 环境贴图(没搞懂)
  • 光线投影+模型的位置+点击交互
  • 不采用const geometry = new THREE.SphereGeometry(50, 25, 25);创建模型,而是去读取一个stl文件
  • 补间动画
  • 问题集中处理
    • 1. 如果我不想使用three.js轨道控制器的默认配置,我想让:中间移动,右键缩放,5应该怎么办?如果我不想使用three.js轨道控制器的默认配置,我想让:中间移动,右键缩放,应该怎么办?
    • 2.坐标轴控制器只有三个线段,他能不能显示xyz三个字母呢?
    • 3.顶点的转换
    • 4.破防了,家人们,遇到一个巨离谱的BUG:
  • 着色器
  • 本地坐标和世界坐标
    • 本地坐标(随便看一下)
    • 局部坐标系(重点)
    • 世界坐标(重点)

前言

  • 问题
    在开启今天的学习之前,我们先解决一个问题:当我们使用右击让物体远离中心时,会看起来扭曲或拉伸

  • 解决办法:

    //透视投影(透视投影会使得远离中心的物体看起来扭曲或拉伸,这是一个不可避免的数学事实)
    //const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    //解决办法是使用正交投影:
    const camera = new THREE.OrthographicCamera(window.innerWidth / -2,
      window.innerWidth / 2,
      window.innerHeight / 2,
      window.innerHeight / -2,
      1, 1000);

    //设置相机的缩放比例(正交投影会使得物体变得非常小,所以你需要设置一下缩放比例,比如放大一百倍)
    camera.zoom = 100;
    // 更新相机的投影矩阵(这句话好像不用加)
    camera.updateProjectionMatrix();

坐标辅助器(就是三条x y z轴的线段)

官方:
https://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene

在官方搜索:AxesHelper

// 添加坐标轴辅助器(相当于一个线段,看成一个物件)
    const axesHelper = new THREE.AxesHelper(5);//线段的长度为5
    scene.add(axesHelper);//添加到场景中
// 补充 
// 设置相机位置
    //camera.position.set(0, 0, 10);
    //camera.position.set(0, 15, 70);
    camera.position.z = 6;
    camera.position.y = 6;
    camera.position.x = 6;

    scene.add(camera);

轨道控制器

有了这个,就可以用右键进行拖动了
测试了一下,不仅仅是右键,总结起来:

  • 右键移动
  • 中键缩放
  • 左键旋转
    那么问题来了:

如果我不想使用three.js轨道控制器的默认配置,我想让:中间移动,右键缩放,应该怎么办?(请看问题集中处理部分)

// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 创建轨道控制器(此时你可以使用右键让模型动起来)
    const controls = new OrbitControls(camera, renderer.domElement);
    // 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
    controls.enableDamping = true;
    //设置阻尼的系数
    controls.dampingFactor = 0.05;
    //让其主动的旋转
    controls.autoRotate = true;


// 动画循环渲染
    function animate() {
      // 如果没有鼠标交互,立方体会自动旋转
      if (!isDragging) {
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
      }
      controls.update();//更新轨道控制器/
      renderer.render(scene, camera);
      requestAnimationFrame(animate);
    }
    animate(); // 开始动画循环

物体位移与父子元素

//cube.position.x = 2;
    //等价于
    cube.position.set(2, 0, 0);

父组件的坐标是多少就是多少,
子组件的真实坐标=父组件坐标+子组件坐标

欧拉角的旋转

默认是"XYZ",
先旋转X轴,然后是Y轴,最后是Z轴

设置画布自适应大小

// 监听窗口大小变化事件
    window.addEventListener('resize', function () {
      var width = window.innerWidth;
      var height = window.innerHeight;

      //1. 更新渲染器大小
      renderer.setSize(width, height);
      //2.更新相机的宽高比
      camera.aspect = width / height;
      //   设置渲染器的像素比(这个好像可有可无,不过加上确实更好一点,拉伸更自然了)
      renderer.setPixelRatio(window.devicePixelRatio);
      // 3.更新相机的投影矩阵
      camera.updateProjectionMatrix();

    });

监听全屏

window.addEventListener("dblclick", () => {
      const fullScreenElement = document.fullscreenElement;
      if (!fullScreenElement) {
        //   双击控制屏幕进入全屏,退出全屏
        // 让画布对象全屏
        renderer.domElement.requestFullscreen();///真实功能实现
      } else {
        //   退出全屏,使用document对象
        document.exitFullscreen();
      }
      //   console.log(fullScreenElement);
    });

lil-gui工具的使用

//导入lil.gui
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
//lil-gui
    let eventObj = {
      Fullscreen: function () {
        renderer.domElement.requestFullscreen();
        gui.show();
      },
      ExitFullscreen: function () {
        document.exitFullscreen();
        gui.show();
      }
    };
    //创建GUI
    const gui = new GUI();
    //改变交互界面style属性
    gui.domElement.style.right = '0px';
    gui.domElement.style.width = '300px';
    gui.close();
    // 创建一个折叠的文件夹
    const folder = gui.addFolder('全屏设置');
    // 添加按钮到折叠文件夹
    folder.add(eventObj, 'Fullscreen').name('全屏');
    folder.add(eventObj, 'ExitFullscreen').name('退出全屏');
    // 关闭折叠面板
    folder.close();
    const folder1 = gui.addFolder('立方体位置设置');
    folder1.add(cube.position, "x", -5, 5).name("立方体x轴位置").onChange((val) => {
      console.log("cube's x position:", val);
    });
    folder1.add(cube.position, "y", -5, 5).name("立方体y轴位置");
    folder1.add(cube.position, "z", -5, 5).name("立方体z轴位置");
    //.min(-10).max(10).step(1)
    folder1.close();

    const folder2 = gui.addFolder('线框模式设置');
    folder2.add(material, "wireframe").name("线框模式");
    folder2.close();

    const folder3 = gui.addFolder('颜色设置');
    let colorParams = {
      cubeColor: "#ff0000",
    };
    folder3.addColor(colorParams, "cubeColor")
      .name("颜色变化")
      .onChange((val) => {
        cube.material.color.set(val);
      });
    folder3.close();

纹理贴图(搞懂了!!!)

有没有大神知道下面这个报错是什么原因?因为我是半路搞前端,对前端知之甚少,只是暂时学习一下three.js,后续可能更加倾向于cpp+opengl
three.js(三)_第1张图片

//一共需要三步:1.创建加载器2.加载图片3.将纹理放入材质
    //1.创建纹理加载器
    let textureLoader = new THREE.TextureLoader();
    //2.加载纹理
    let texture = textureLoader.load(
      './src/picture/2.png',
      // onLoad回调
      function (texture) {
        // in this example we create the material when the texture is loaded
        console.log('Texture loaded successfully.');
      },
      // 目前暂不支持 onProgress 的回调
      undefined,
      // onError回调
      function (err) {
        console.log('An error happened while loading the texture.');
        console.log('Error details:{{{[', err, ']}}}');
      }
    );
    /********************************************/

    // 创建一个立方体
    const geometry = new THREE.BoxGeometry();
    //材质
    const material = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      map: texture,///3.将贴图添加到立方体的材质中
    });

关于找图片的位置

'./src/picture/2.png'
我困在这里一下午,后来被大神同事一下子就搞定了!!!太牛了!!!
three.js(三)_第2张图片
package.json的位置才是根目录!!!
three.js(三)_第3张图片

打包后找图片的位置

你需要在index-r5u9b8gU.js中,重新修改png图片的位置three.js(三)_第4张图片

纹理贴图(pro)

three自带的几何体模型是自带顶点数据的,所以当你使用纹理贴图时,会自动匹配这个顶点去渲染,
你自己渲染的stl文件不会有自带的uv顶点数据,所以就会导致贴图不会按照正确的模型位置去渲染纹理的效果.
so,你需要手动去计算他大的uv顶点坐标!!!

  • 在使用three.js自带的模型时,
//你在使用three.js内置的模型时,直接向下面这样写就行
stlLoader.load('./src/stl/Down/m_CutGumPd0_12.stl', geometry => {
      material12 = new THREE.MeshPhongMaterial({
        color: 0xDDDADA,
        map: texture
      });
      mesh12 = new THREE.Mesh(geometry, material12);
      scene.add(mesh12);
    });
  • 计算uv坐标的代码(代码可以复用)
const positions = geometry.getAttribute('position').array;
      // 找到模型顶点坐标的最小值和最大值
      let minX = Number.POSITIVE_INFINITY;
      let maxX = Number.NEGATIVE_INFINITY;
      let minY = Number.POSITIVE_INFINITY;
      let maxY = Number.NEGATIVE_INFINITY;
      for (let i = 0; i < positions.length; i += 3) {
        const x = positions[i];
        const y = positions[i + 1];

        minX = Math.min(minX, x);
        maxX = Math.max(maxX, x);
        minY = Math.min(minY, y);
        maxY = Math.max(maxY, y);
      }
      // 计算顶点坐标范围
      const rangeX = maxX - minX;
      const rangeY = maxY - minY;
      // 计算 UV 坐标并归一化到 0 到 1 之间
      const uvs = [];
      for (let i = 0; i < positions.length; i += 3) {
        const x = positions[i];
        const y = positions[i + 1];

        const u = (x - minX) / rangeX;
        const v = (y - minY) / rangeY;

        uvs.push(u, v);
      }
      geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
  • all ,假如你使用的自己搞的stl模型,你可以照着下面的这样写:
//这个是牙龈
    stlLoader.load('./src/stl/Down/m_CutGumPd0_14.stl', (geometry) => {
    /***************计算uv坐标*******************/
      const positions = geometry.getAttribute('position').array;
      // 找到模型顶点坐标的最小值和最大值
      let minX = Number.POSITIVE_INFINITY;
      let maxX = Number.NEGATIVE_INFINITY;
      let minY = Number.POSITIVE_INFINITY;
      let maxY = Number.NEGATIVE_INFINITY;
      for (let i = 0; i < positions.length; i += 3) {
        const x = positions[i];
        const y = positions[i + 1];

        minX = Math.min(minX, x);
        maxX = Math.max(maxX, x);
        minY = Math.min(minY, y);
        maxY = Math.max(maxY, y);
      }
      // 计算顶点坐标范围
      const rangeX = maxX - minX;
      const rangeY = maxY - minY;
      // 计算 UV 坐标并归一化到 0 到 1 之间
      const uvs = [];
      for (let i = 0; i < positions.length; i += 3) {
        const x = positions[i];
        const y = positions[i + 1];

        const u = (x - minX) / rangeX;
        const v = (y - minY) / rangeY;

        uvs.push(u, v);
      }
      geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
/**********************************/

      material14 = new THREE.MeshPhongMaterial({
        color: 0xDDDADA,
        map: texture2
      });
      mesh14 = new THREE.Mesh(geometry, material14);
      scene.add(mesh14);
    });

纹理贴图(pro max)[等我有了新的理解就在这里补充]

环境贴图(没搞懂)

我这里有一张环境贴图,他是一个hdr文件,如果你没有的话,可以去下面的网站中下载一下:
环境贴图示例文件

//导入环境贴图的加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader"

光线投影+模型的位置+点击交互

  • App.jsx:
import { useState, useEffect, Component } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import * as THREE from 'three';
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 引入指针锁定控制器扩展库PointerLockControls.js
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'
// 导入动画库
import gsap from "gsap";
//导入lil.gui
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";




class App extends Component {
  render() {
    return <div></div>
  }
  componentDidMount() {


    // 设置场景、相机
    const scene = new THREE.Scene();
    // 设置场景颜色为蓝色
    scene.background = new THREE.Color(0xC0C0C0);
    //透视投影(透视投影会使得远离中心的物体看起来扭曲或拉伸,这是一个不可避免的数学事实)
    //const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    //解决办法是使用正交投影:
    const camera = new THREE.OrthographicCamera(window.innerWidth / -2,
      window.innerWidth / 2,
      window.innerHeight / 2,
      window.innerHeight / -2,
      1, 1000);

    //设置相机的缩放比例(正交投影会使得物体变得非常小,所以你需要设置一下缩放比例,比如放大一百倍)
    camera.zoom = 100;
    // 更新相机的投影矩阵(这句话好像不用加)
    camera.updateProjectionMatrix();

    // 设置相机位置
    //camera.position.set(0, 0, 10);
    camera.position.set(0, 15, 70);
    //camera.position.z = -6;
    // camera.position.y = 6;
    // camera.position.x = -6;

    scene.add(camera);

    //渲染器
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 监听窗口大小变化事件
    window.addEventListener('resize', function () {
      var width = window.innerWidth;
      var height = window.innerHeight;

      //1. 更新渲染器大小
      renderer.setSize(width, height);
      //2.更新相机的宽高比
      camera.aspect = width / height;
      //   设置渲染器的像素比(这个好像可有可无,不过加上确实更好一点,拉伸更自然了)
      renderer.setPixelRatio(window.devicePixelRatio);
      // 3.更新相机的投影矩阵
      camera.updateProjectionMatrix();

    });


    // 创建一个立方体
    const geometry = new THREE.BoxGeometry();
    //材质
    const material = new THREE.MeshBasicMaterial({
      color: 0x00ff00,

    });
    //设置材质为线框模式
    material.wireframe = false;
    // const cube = new THREE.Mesh(geometry, material);
    // //cube.position.x = 2;
    // //等价于
    // cube.position.set(0, 0, 0);
    // scene.add(cube);


    /**************/

    // 创建轨道控制器(此时你可以使用右键让模型动起来)
    const controls = new OrbitControls(camera, renderer.domElement);
    // 设置控制器阻尼,让控制器更有真实效果,必须在动画循环里调用.update()。
    controls.mouseButtons = {
      LEFT: THREE.MOUSE.ROTATE, // 左键旋转
      MIDDLE: THREE.MOUSE.PAN, // 中键平移
      RIGHT: THREE.MOUSE.DOLLY // 右键缩放
    };
    controls.enableDamping = true;
    //设置阻尼的系数
    controls.dampingFactor = 0.05;
    //让其主动的旋转
    //controls.autoRotate = true;

    // 添加坐标轴辅助器(相当于一个线段,看成一个物件)
    const axesHelper = new THREE.AxesHelper(5);//线段的长度为5
    scene.add(axesHelper);//添加到场景中
    // 设置时钟
    const clock = new THREE.Clock();

    // window.addEventListener("dblclick", () => {
    //   const fullScreenElement = document.fullscreenElement;
    //   if (!fullScreenElement) {
    //     //   双击控制屏幕进入全屏,退出全屏
    //     // 让画布对象全屏
    //     renderer.domElement.requestFullscreen();
    //   } else {
    //     //   退出全屏,使用document对象
    //     document.exitFullscreen();
    //   }
    //   //   console.log(fullScreenElement);
    // });
    /**************/
    // 动画循环渲染
    function animate() {
      controls.update();
      renderer.render(scene, camera);
      requestAnimationFrame(animate);
    }
    animate(); // 开始动画循环
    // 辅助函数:将角度转换为弧度
    function toRadians(angle) {
      return angle * (Math.PI / 180);
    }


    let textureLoader = new THREE.TextureLoader();
    //2.加载纹理
    let texture1 = textureLoader.load('./src/picture/test.png');
    let texture2 = textureLoader.load('./src/picture/test1.png');
    let texture3 = textureLoader.load('./src/picture/test2.png');
    let texture4 = textureLoader.load('./src/picture/test3.png');
    let texture5 = textureLoader.load('./src/picture/2.png');


     
    /********************bengin*****************
    *光线投影+模型的位置+点击交互
    **************/
    //创建三个球
    //1
    const sphere1 = new THREE.Mesh(
      new THREE.SphereGeometry(1, 32, 32),
      new THREE.MeshBasicMaterial({
        color: 0x00FF00,
        map: texture1,
      })
    );
    sphere1.position.x = -4;
    scene.add(sphere1);
    //2
    const sphere2 = new THREE.Mesh(
      new THREE.SphereGeometry(1, 32, 32),
      new THREE.MeshBasicMaterial({
        color: 0x0000FF,
        map: texture5,
      })
    );
    sphere2.position.x = 0;
    scene.add(sphere2);
    //3
    const sphere3 = new THREE.Mesh(
      new THREE.SphereGeometry(1, 32, 32),
      new THREE.MeshBasicMaterial({
        color: 0xFF00FF,
        map: texture4,
      })
    );
    sphere3.position.x = 4;
    scene.add(sphere3);
    console.log(scene.children);

    //创建射线
    const raycaster = new THREE.Raycaster();
    //创建鼠标向量
    const mouse = new THREE.Vector2();

    window.addEventListener("click", (event) => {
      //console.log(event.clientX, event.clientY);
      //设置鼠标向量的x,y值
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      //console.log(mouse.x, mouse.y);

      //通过摄像机和鼠标为位置,更新射线
      raycaster.setFromCamera(mouse, camera);
      //计算物体和射线的焦点
      const intersects = raycaster.intersectObjects([sphere1, sphere2, sphere3]);
      if (intersects.length > 0) {
        if (intersects[0].object._isSelect) {
          //恢复原色
          intersects[0].object.material.color.set(
            intersects[0].object._originColor
          );
          intersects[0].object._isSelect = false;
          // console.log("直接跳出来");
          return;
        }
        intersects[0].object._isSelect = true;
        intersects[0].object._originColor =
          intersects[0].object.material.color.getHex();
        intersects[0].object.material.color.set(0xff0000);

      }
    });
	/********************end*******************************/
  }
}

export default App

不采用const geometry = new THREE.SphereGeometry(50, 25, 25);创建模型,而是去读取一个stl文件

这个大佬写的非常好,大家可以去看一下:【Three.js使用STLLoader加载stl模型 - CSDN App】

  • 第一步:引入头文件
//STL
import { Mesh } from "three";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
  • 第二步:加载stl模型(这里其实有两种办法加载一个stl文件,并将他塞到geometry中,具体请看大佬的文章:【Three.js使用STLLoader加载stl模型 - CSDN App】)
// 创建STL加载器
    var loader = new STLLoader();

    var material;
    var mesh;
    // 创建STL加载器
    var stlLoader = new STLLoader();
    stlLoader.load('./src/stl/lower.stl', geometry => {
      material = new THREE.MeshPhongMaterial({ color: 0xDDDADA });
      mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);
    })
  • App.jsx:
import { useState, useEffect, Component } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import * as THREE from 'three';
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 引入指针锁定控制器扩展库PointerLockControls.js
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'
// 导入动画库
import gsap from "gsap";
//导入lil.gui
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";

//STL
import { Mesh } from "three";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";


class App extends Component {
  render() {
    return <div></div>
  }
  componentDidMount() {
    //场景
    const scene = new THREE.Scene();


    // // 一个网格模型
    // const geometry = new THREE.SphereGeometry(50, 25, 25);
    // const material = new THREE.MeshPhongMaterial({
    //   color: 0x00ffff,
    //   specular: 0x111111,
    // });
    // const mesh = new THREE.Mesh(geometry, material);
    // scene.add(mesh); //模型对象添加到场景中
    // 创建STL加载器
    var loader = new STLLoader();

    var material;
    var mesh;
    // 创建STL加载器
    var stlLoader = new STLLoader();
    stlLoader.load('./src/stl/lower.stl', geometry => {
      material = new THREE.MeshPhongMaterial({ color: 0xDDDADA });
      mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);
    })


    //光源设置
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(400, 200, 300);
    scene.add(directionalLight);
    const ambient = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambient);

    //辅助观察的坐标系
    const axesHelper = new THREE.AxesHelper(100);
    scene.add(axesHelper);

    const gui = new GUI(); //创建GUI对象 
    //创建一个对象,对象属性的值可以被GUI库创建的交互界面改变
    const obj = {
      color: 0x00ffff,// 材质颜色
      specular: 0x111111,// 材质高光颜色
    };
    // 创建材质子菜单
    const matFolder = gui.addFolder('材质');
    // 材质颜色color
    matFolder.addColor(obj, 'color').onChange(function (value) {
      material.color.set(value);
    });
    // 材质高光颜色specular
    matFolder.addColor(obj, 'specular').onChange(function (value) {
      material.specular.set(value);
    });
    // 环境光子菜单
    const ambientFolder = gui.addFolder('环境光');
    // 环境光强度
    ambientFolder.add(ambient, 'intensity', 0, 2);
    // 平行光子菜单
    const dirFolder = gui.addFolder('平行光');
    // 平行光强度
    dirFolder.add(directionalLight, 'intensity', 0, 2);
    // 平行光位置
    dirFolder.add(directionalLight.position, 'x', -400, 400);
    dirFolder.add(directionalLight.position, 'y', -400, 400);
    dirFolder.add(directionalLight.position, 'z', -400, 400);

    //渲染器和相机
    const width = window.innerWidth;
    const height = window.innerHeight;
    const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
    camera.position.set(292, 223, 185);
    camera.lookAt(0, 0, 0);

    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);
    document.body.appendChild(renderer.domElement);



    // 渲染循环
    function render() {
      // 当gui界面设置obj.bool为true,mesh执行旋转动画
      if (obj.bool) mesh.rotateY(0.01);
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    render();


    const controls = new OrbitControls(camera, renderer.domElement);
    controls.mouseButtons = {
      LEFT: THREE.MOUSE.ROTATE, // 左键旋转
      MIDDLE: THREE.MOUSE.PAN, // 中键平移
      RIGHT: THREE.MOUSE.DOLLY // 右键缩放
    };

    // 画布跟随窗口变化
    window.onresize = function () {
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    };


  }
}

export default App

补间动画

tween.js
three.js(三)_第5张图片

  • 头文件
//打入补间动画tween
import * as  TWEEN from "three/examples/jsm/libs/tween.module.js"
/********************补间动画begin**********************/
	const tween = new TWEEN.Tween(sphere1.position);
	tween.to({ x: 7 }, 3000);//3秒
	//这个动画重复无数次
	tween.repeat(Infinity);
	tween.start();
	//记得在渲染函数中对每一帧进行更新  
/********************end*******************************/
 // 动画循环渲染
    function animate() {
      controls.update();
      renderer.render(scene, camera);
      requestAnimationFrame(animate);
      //更新tween///补间动画///
      TWEEN.update();/
    }
    animate(); // 开始动画循环

问题集中处理

1. 如果我不想使用three.js轨道控制器的默认配置,我想让:中间移动,右键缩放,5应该怎么办?如果我不想使用three.js轨道控制器的默认配置,我想让:中间移动,右键缩放,应该怎么办?

你可以通过修改轨道控制器的mouseButtons属性来实现这个功能。以下是回答:

轨道控制器(OrbitControls)是一种用于控制相机在场景中旋转、缩放和平移的控制器。它有一个mouseButtons属性,用于定义不同的鼠标按钮对应的不同的操作。默认情况下,mouseButtons属性的值是:

controls.mouseButtons = {
  LEFT: THREE.MOUSE.ROTATE, // 左键旋转
  MIDDLE: THREE.MOUSE.DOLLY, // 中键缩放
  RIGHT: THREE.MOUSE.PAN // 右键平移
};

如果你想设置中键移动,你可以将mouseButtons属性的值修改为:

controls.mouseButtons = {
  LEFT: THREE.MOUSE.ROTATE, // 左键旋转
  MIDDLE: THREE.MOUSE.PAN, // 中键平移
  RIGHT: THREE.MOUSE.DOLLY // 右键缩放
};

这样,你就可以使用中键来移动场景,而不是右键。你也可以根据你的喜好,自由地调整其他鼠标按钮的功能。

2.坐标轴控制器只有三个线段,他能不能显示xyz三个字母呢?

答:当然可以.

//导入字体
import { FontLoader } from "three/examples/jsm/loaders/FontLoader.js"
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
// 添加坐标轴辅助器(相当于一个线段,看成一个物件)
    const axesHelper = new THREE.AxesHelper(5);//线段的长度为5
    scene.add(axesHelper);//添加到场景中

    // 创建字体加载器
    var fontLoader = new FontLoader();

    // 加载字体文件
    fontLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/fonts/helvetiker_regular.typeface.json', function (font) {

      // 创建文本几何体
      var textGeoX = new TextGeometry('X', {
        font: font,
        size: 0.5,
        height: 0.1,
        curveSegments: 6
      });
      var textGeoY = new TextGeometry('Y', {
        font: font,
        size: 0.5,
        height: 0.1,
        curveSegments: 6
      });
      var textGeoZ = new TextGeometry('Z', {
        font: font,
        size: 0.5,
        height: 0.1,
        curveSegments: 6
      });

      // 创建文本材质
      var textMaterialX = new THREE.MeshBasicMaterial({
        color: 0xff0000
      });
      var textMaterialY = new THREE.MeshBasicMaterial({
        color: 0x00ff00
      });
      var textMaterialZ = new THREE.MeshBasicMaterial({
        color: 0x0000ff
      });

      // 创建文本网格对象
      var textX = new THREE.Mesh(textGeoX, textMaterialX);
      var textY = new THREE.Mesh(textGeoY, textMaterialY);
      var textZ = new THREE.Mesh(textGeoZ, textMaterialZ);

      // 将文本网格对象添加到坐标轴辅助对象中
      axesHelper.add(textX);
      axesHelper.add(textY);
      axesHelper.add(textZ);

      // 调整文本网格对象的位置和旋转
      textX.position.x = 5.2;
      textX.rotation.y = -Math.PI / 2;
      textY.position.y = 5.2;
      textZ.position.z = 5.2;
      textZ.rotation.x = Math.PI / 2;

    });

假如说,没有报错,你也没看到坐标轴,那你可以试一试改改参数,
eg:

/*******创建字体begin*********/
    // 添加坐标轴辅助器(相当于一个线段,看成一个物件)
    const axesHelper = new THREE.AxesHelper(90);//线段的长度为5
    scene.add(axesHelper);//添加到场景中
    // 创建字体加载器
    var fontLoader = new FontLoader();

    // 加载字体文件
    fontLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/fonts/helvetiker_regular.typeface.json', function (font) {

      // 创建文本几何体
      var textGeoX = new TextGeometry('X', {
        font: font,
        size: 5,
        height: 1,
        curveSegments: 6
      });
      var textGeoY = new TextGeometry('Y', {
        font: font,
        size: 5,
        height: 1,
        curveSegments: 6
      });
      var textGeoZ = new TextGeometry('Z', {
        font: font,
        size: 5,
        height: 1,
        curveSegments: 6
      });

      // 创建文本材质
      var textMaterialX = new THREE.MeshBasicMaterial({
        color: 0xff0000
      });
      var textMaterialY = new THREE.MeshBasicMaterial({
        color: 0x00ff00
      });
      var textMaterialZ = new THREE.MeshBasicMaterial({
        color: 0x0000ff
      });

      // 创建文本网格对象
      var textX = new THREE.Mesh(textGeoX, textMaterialX);
      var textY = new THREE.Mesh(textGeoY, textMaterialY);
      var textZ = new THREE.Mesh(textGeoZ, textMaterialZ);

      // 将文本网格对象添加到坐标轴辅助对象中
      axesHelper.add(textX);
      axesHelper.add(textY);
      axesHelper.add(textZ);

      // 调整文本网格对象的位置和旋转
      textX.position.x = 95.2;
      textX.rotation.y = -Math.PI / 2;
      textY.position.y = 95.2;
      textZ.position.z = 95.2;
      textZ.rotation.x = Math.PI / 2;

    });
    /*******创建字体end*********/
  • 注:https://raw.githubusercontent.com/mrdoob/three.js/master/examples/fonts/helvetiker_regular.typeface.json这个网站需要科学上网

如果你不想每次都要科学上网,才能找到这个json文件,
你可以将该网站的内容进行copy,放入到json文件中,如下:

	//fontLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/fonts/helvetiker_regular.typeface.json', function (font) {
	//改成
    fontLoader.load('./src/json/font.json', function (font) {

three.js(三)_第6张图片

3.顶点的转换

03-几何体顶点转化_顶点位移_旋转_缩放

4.破防了,家人们,遇到一个巨离谱的BUG:

在使用three.js编写代码是,使用它的lil.gui,在本地使用npm run dev运行后,出现bug:折叠后仍然显示,然后你去修改折叠后上面的选项是无法生效的,那就说明,折叠成功了,但是页面没有刷新
但是:当你使用npm run build将相关代码部署到cpp服务器上时,就没有了这个bug!!!(这个就是:解决的办法,部署上就好了)

着色器

(这个你需要知道两个材质:物理材质->MeshBasicMaterial 和 着色器材质->ShaderMaterial)
1.CPU的运行速率比gpu的运行速率快,但是cpU 1次只能执行一次计算,而CPU可以一次执行多次计算
2. 着色器依赖与glsl语言

  • 你既可以js文件中使用glsl语言,直接用单引号引用就行
//eg:
const click_fragmentShader = `
    uniform sampler2D texturetooth;
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vViewPosition;

    void main(){
      vec4 textureColor = texture2D(texturetooth, vUv + 0.1);

      // 光照参数
      vec3 lightColor = vec3(1.0, 1.0, 1.0); // 光源颜色
      vec3 lightDirection = normalize(vec3(0.0, 0.0, 1.0)); // 光线方向

      float ambientIntensity = 0.3;
      float diffuseIntensity = max(dot(vNormal, lightDirection), 0.3);
      vec3 diffuseReflection = lightColor * diffuseIntensity;

      vec3 viewDirection = normalize(vViewPosition);
      vec3 reflectDirection = reflect(-lightDirection, vNormal);
      float specularIntensity = pow(max(dot(viewDirection, reflectDirection), 0.0), 32.0);
      vec3 specularReflection = lightColor * specularIntensity;

      vec3 finalColor = (ambientIntensity * textureColor.rgb) + (diffuseReflection * textureColor.rgb) + (specularReflection * textureColor.rgb);

      gl_FragColor = vec4(finalColor, textureColor.a);
    }`;
  • 也可以向引入CSS文件一样,引入glsl文件,glsl文件是OpenGL着色器的一种文件,当然也是一种语言
  1. 使用着色器材质的流程
    创建顶点着色器对象,创建片段着色器对象,然后直接给这个对象进行赋值,赋值的内容就是着色器demo(当然,你也可以使用文件,然后用import导入也行,用的时候也是一样,import什么,什么就是着色器对象,可以直接用)
    ----->
    在材质中,绑定顶点着色器片段着色器对象以及贴图与法线(贴图和法线也是两个对象,直接加载文件[贴图是必须的,法线就看你自己的需要了])(到这里没着色器的事了)
    ----->
    然后将stl模型进行加载,设置uv坐标,创建网格对象(参数是两个,一个是刚加载的stl模型,另一个是设置了着色器贴图法线的材质);
    然后再将网格加载到scene场景中。
    -------->然后循环渲染
  2. 一个问题
    如何更改用着色器渲染的图像呢?
    你不能直接改当前对象着色器,你要新建一个材质来承载这个着色器,然后再把新材质给到这个对象
    这个我举一个例子:
//你在点击了一颗牙齿或者牙龈后,我就换成这个这个片段着色器,哈哈哈//标记2
    //eg:material_tooth_0.fragmentShader = click_fragmentShader;
    const click_fragmentShader = `
    uniform sampler2D texturetooth;
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vViewPosition;

    void main(){
      vec4 textureColor = texture2D(texturetooth, vUv + 0.1);

      // 光照参数
      vec3 lightColor = vec3(1.0, 1.0, 1.0); // 光源颜色
      vec3 lightDirection = normalize(vec3(0.0, 0.0, 1.0)); // 光线方向

      float ambientIntensity = 0.3;
      float diffuseIntensity = max(dot(vNormal, lightDirection), 0.3);
      vec3 diffuseReflection = lightColor * diffuseIntensity;

      vec3 viewDirection = normalize(vViewPosition);
      vec3 reflectDirection = reflect(-lightDirection, vNormal);
      float specularIntensity = pow(max(dot(viewDirection, reflectDirection), 0.0), 32.0);
      vec3 specularReflection = lightColor * specularIntensity;

      vec3 finalColor = (ambientIntensity * textureColor.rgb) + (diffuseReflection * textureColor.rgb) + (specularReflection * textureColor.rgb);

      gl_FragColor = vec4(finalColor, textureColor.a);
    }`;
    //改完之后,你不能直接修改着色器demo,你需要在重新加载一个material,然后再把这个新的复制给stl网格对象才可以!!!终于解决了!@王珂珑理论正确!!!
    var change_material = new THREE.ShaderMaterial({
      uniforms: {
        texturetooth: { value: texture },
      },
      //下面两个属性是专门属于牙齿的着色器
      vertexShader: vertexShader,
      fragmentShader: click_fragmentShader,
    });

    window.addEventListener(
      "click", event => {
        //console.log(event.clientX, event.clientY);
        //设置鼠标向量的x,y值
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        console.log(mouse.x, mouse.y);

        //通过摄像机和鼠标为位置,更新射线
        raycaster.setFromCamera(mouse, camera);
        //计算物体和射线的焦点
        intersects = raycaster.intersectObjects([
          mesh0,
          mesh1,
          mesh2,
          mesh3,
          mesh4,
          mesh5,
          mesh6,
          mesh7,
          mesh8,
          mesh9,
          mesh10,
          mesh11,
          mesh12,
          mesh13,
          //mesh14,
        ]);
        if (intersects.length > 0) {//有牙齿也要进行交互
          console.log('有人点击了一颗牙齿');
          if (hasTeethSelected == false)//表示还没有任何一颗牙齿被选择
          {
            hasTeethSelected = true;

            //解释一下:因为是第一次选,所以当前选中和上一次选中都是他自己(后来想了一下,这个没什么意义)
            selectedObject = intersects[0].object;
            lastObject = intersects[0].object;

            //你可以使用'lastObject.lastmaterial'保存当前牙齿上一次的材质
            //你可以使用'selectedObject.material.'对当前选中模型的材质进行更改
            lastObject.lastmaterial = selectedObject.material;
            selectedObject.material = change_material;

          } else {
            //说明已经有一颗牙齿被选中了
            //恢复上一颗牙齿的材质'lastObject.material = lastObject.material'
            lastObject.material = lastObject.lastmaterial;
            //此时,就和上一颗牙齿无关了.

            selectedObject = intersects[0].object;
            lastObject = intersects[0].object;
            
            //报存当前牙齿的材质
            lastObject.lastmaterial = selectedObject.material;
            //改变当前牙齿的材质
            selectedObject.material = change_material;
          }
          renderer.render(scene, camera);
          console.log('重新渲染了!');
        }
      });

如果你还不懂,再通俗易懂一点就是:
你需要将一些新的相关参数放在一个新的material材质中(即,参数需要以这个材质为载体),将新的材质赋值给模型,从而完成对模型的修改.
与物理材质的区别是: 物理材质你直接改他的属性就可以,不需要用一个新的材质作为中间载体

本地坐标和世界坐标

本地坐标(随便看一下)

本地坐标是对象在其自身的坐标系中的坐标,每个对象都会有一个本地坐标也就相对于其父对象的坐标系,比如,应该有层级关系的场景,它包含了一个父对象和一个子对象,父对象它的原点就是全局坐标系中的一个点,子对象相对于父对象来说它的原点是具有一组本地坐标。

// mesh的世界坐标就是mesh.position与group.position的累加
const mesh = new THREE .Mesh(geometry, materia1);
mesh.position.set(5000) ;
const group = new THREE .Group();
group .add(mesh) :
group.position.set(5000);

改变子对象的.position,子对象在3D空间中的坐标会发生改变
改变父对象的,position,子对象在3D空间中的位置也会跟着变化,也就是说父对象.position和子对象,position看加才是才是子对象的.position。

局部坐标系(重点)

  • 局部坐标系是每个对象或模型自身的坐标系。对于一个模型来说,它的局部坐标系原点通常位于模型的几何中心轴沿着模型的方向。
  • 这个坐标系是相对于对象自身的,并不会受到外部世界或其他对象的影响。
  • 在局部坐标系中,一个对象的位置和旋转是相对于它自己的,因此所有的坐标值都是相对于对象本身的坐标原点和方向的。

世界坐标(重点)

它的作用是对象相对于场景的坐标。它是考虑了对象的层级关系和父对象的位置、旋转和缩放等属性的坐标。

  • 世界坐标系是整个场景或空间的坐标系。它是所有对象的共同参考坐标系。

  • 在这个坐标系中,每个对象的位置和旋转是相对于整个场景的全局坐标原点和方向的。

  • 当一个对象位于世界坐标系中时,它的位置和旋转是相对于整个场景的,不受其他对象的影响

  • 学习这个知识点的目的:
    我想在一个场景中,给某一个单独的模型,添加箭头,这应该如何去做?

let axesHelper1 = new THREE.ArrowHelper(//x轴
	  new THREE.Vector3(1, 0, 0),
	  new THREE.Vector3(0, 0, 0),
	  30,
	  0xFF0000
);


//模型中心的对象
const modelCenter = new THREE.Vector3();
//计算网格对象的几何体的BoundingBox边界框
selectedObject.geometry.computeBoundingBox();
//获取模型geometry的边界框的getCenter(中心点)
selectedObject.geometry.boundingBox.getCenter(modelCenter);
//从局部坐标系转换到世界坐标系中,以便在世界空间中准确地定位边界框的中心点。
modelCenter.applyMatrix4(selectedObject.matrixWorld);

axesHelper1.position.copy(modelCenter);// 设置坐标轴位置与当前选中几何体的位置相同
scene.add(axesHelper1);//将箭头加入到场景中

你可能感兴趣的:(opengl,OpenGL,webGl,three.js)