Three.js — Пятнадцать, Box3, анимация камеры, направление взгляда LookAt(), случай роуминга конвейера, ограничения вращения и масштабирования OrbitControls и управление камерой MapControls

ортогональная проекционная камера

Разница между камерой ортогональной проекции и перспективной камерой.Если
вы посмотрите на всю сцену сверху вниз, камера ортогональной проекции будет иметь эффект 2D-визуализации, а перспективная камера будет иметь эффект наблюдения, аналогичный человеческому глазу. .

Отрегулируйте размер левого, правого, верхнего и нижнего диапазона.

Если вы хотите просмотреть все кубы целиком, вам необходимо настроить диапазон рендеринга камеры, например, установив диапазон вверх, вниз, влево и вправо.

Сценарии использования: Ортографическая проекция может использоваться для предварительного просмотра карты Китая или для достижения эффектов 2D-визуализации.Камеры с
перспективной проекцией обычно используются людьми для перемещения по сцене или для просмотра всей сцены сверху.

Ограничивающая рамкаBox3

Это значит обернуть все вершины всей модели в кубоид.Этот кубоид — Box3.

const geometry = new THREE.BoxGeometry(10, 10, 10);
// 材质
const material = new THREE.MeshPhysicalMaterial({
    
    
  color: 0x51efe4, //0x51efe4设置材质颜色
});
// 网络模型
mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);
const box3 = new THREE.Box3();
box3.expandByObject(mesh); // 计算模型包围盒
console.log("查看包围盒", box3);
scene.add(mesh);

Пожалуйста, добавьте описание изображения

Размер ограничивающей рамки.getSize()

const scale = new THREE.Vector3();
box3.getSize(scale);
console.log("模型包围盒尺寸", scale);

Пожалуйста, добавьте описание изображения

Геометрический центр ограничивающей рамки

const scale = new THREE.Vector3();
box3.getCenter(center);
console.log("模型中心坐标", center);

Пожалуйста, добавьте описание изображения

анимация камеры.position和lookAt()

Измените положение камеры, и трехмерная сцена будет показывать различные эффекты на холсте. Если вы постоянно меняете положение камеры, вы можете получить эффект анимации.
Следующее реализует анимацию камеры.

let angle = 0; //用于圆周运动计算的角度值
const R = 10; //相机圆周运动的半径
const render = () => {
    
    
  angle += 0.01;
  // 相机y坐标不变,在XOZ平面上做圆周运动
  camera.position.x = R * Math.sin(angle);
  camera.lookAt(0, 0, 0);
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

Пожалуйста, добавьте описание изображения

Выполнить lookAt()расчет направления взгляда камеры

После изменения свойства .position, если метод .lookAt() не выполняется, направление обзора камеры по умолчанию останется неизменным.

Если вы хотите, чтобы камера двигалась по кругу, меняла направление взгляда камеры и чтобы объектив камеры всегда указывал на начало координат или другие положения, вам необходимо каждый раз перевыполнять метод .lookAt(). Когда вы меняете свойство .position, это означает, что камера находится в середине анимации. В то же время держите объектив направленным в исходное положение.

Проекционные виды в разных направлениях

<div class="btn">
  <el-button id="x" @click="xyzClick('x')">x</el-button>
  <el-button id="y" @click="xyzClick('y')">y</el-button>
  <el-button id="z" @click="xyzClick('z')">z</el-button>
</div>
body {
    
    
  position: relative;
}
.btn {
    
    
  position: absolute;
  top: 0;
  left: 0;
  z-index: 99;
}
const xyzClick = (name) => {
    
    
  if (name == "x") {
    
    
    camera.position.set(30, 0, 0); //x轴方向观察
    camera.lookAt(0, 0, 0); //重新计算相机视线方向
  } else if (name == "y") {
    
    
    camera.position.set(0, 30, 0); //x轴方向观察
    camera.lookAt(0, 0, 0); //重新计算相机视线方向
  } else {
    
    
    camera.position.set(0, 0, 30); //x轴方向观察
    camera.lookAt(0, 0, 0); //重新计算相机视线方向
  }
};

Исходное положение оси:
Пожалуйста, добавьте описание изображения

Эффект линзы заключается в следующем:
Пожалуйста, добавьте описание изображения

Пожалуйста, добавьте описание изображения

Пожалуйста, добавьте описание изображения

Поворот результатов рендеринга

На самом деле эта концепция заключается в том, что когда вы берете камеру и снимаете объект, например, сверху, снизу, влево, вправо или по диагонали, эффект вашей фотографии будет одинаковым 旋转效果.

Поворот результатов рендеринга (направление камеры вверх)

.upЗначение свойства по умолчанию — new THREE.Vector3(0,1,0), что означает вверх по оси Y.

console.log(".up默认值", camera.up);

Пожалуйста, добавьте описание изображения

Пожалуйста, добавьте описание изображения

Измените, чтобы увидеть эффект

camera.up.set(0, -1, 0);

Пожалуйста, добавьте описание изображения

//渲染效果:红色x轴向上
camera.up.set(1, 0, 0);

Пожалуйста, добавьте описание изображения

//渲染效果:蓝色z轴向上
camera.up.set(0, 0, 1);

Пожалуйста, добавьте описание изображения

Проблема с порядком выполнения

Обратите внимание .up, что свойства, как и свойства, необходимо повторно выполнить, .positionесли они изменяются после выполнения ..lookAt().lookAt()

camera.lookAt(0,0,0);
camera.up.set(0, 0, 1);//改变up
camera.lookAt(0,0,0);//执行lookAt重新计算相机姿态

Эффект роуминга труб

Создать конвейер

const path = new THREE.CatmullRomCurve3([
  new THREE.Vector3(-50, 20, 90),
  new THREE.Vector3(-10, 40, 40),
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(60, -60, 0),
  new THREE.Vector3(90, -40, 60),
  new THREE.Vector3(120, 30, 30),
]);
// 样条曲线path作为TubeGeometry参数生成管道
const geometry = new THREE.TubeGeometry(path, 200, 5, 30);
const texLoader = new THREE.TextureLoader();
//纹理贴图
const texture = texLoader.load(
  new URL(`../assets/wood_table_001_diff_2k.jpg`, import.meta.url).href
);
//UV坐标U方向阵列模式
texture.wrapS = THREE.RepeatWrapping;
//纹理沿着管道方向阵列(UV坐标U方向)
texture.repeat.x = 10;
const material = new THREE.MeshLambertMaterial({
    
    
  map: texture,
  side: THREE.DoubleSide, //双面显示看到管道内壁
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

Для камер выберите камеру с перспективной проекцией.

Получить вершины на траектории движения

С помощью метода .getSpacedPoints() кривой можно равномерно получить ряд данных координат вершин из линии траектории, а затем использовать эти координаты вершин на линии траектории для установки положения камеры.

const pointsArr = path.getSpacedPoints(500);

Пожалуйста, добавьте описание изображения

Разместите камеру на внутренней траектории трубы.

Камера размещается в любом месте траектории внутри трубопровода, а линия обзора камеры контролируется так, чтобы она совпадала с касательной к кривой. Текущая точка и следующая точка
кривой приблизительно имитируют касательную к кривой текущей точки. Чем меньше расстояние между двумя точками, тем выше точность моделирования.pointsArr[i]pointsArr[i+1]

.lookAt()Установите точку наблюдения камеры на pointsArr[i]следующую точку текущей точки pointsArr[i + 1]так, чтобы линия обзора камеры совпадала с касательной к текущей точке кривой.

// 从曲线上等间距获取一定数量点坐标
const pointsArr = path.getSpacedPoints(500);
const i = 100;
// 相机位置:曲线上当前点pointsArr[i]
camera.position.copy(pointsArr[i]);
// 相机观察目标:当前点的下一个点pointsArr[i + 1]
camera.lookAt(pointsArr[i + 1]);

Измените угол поля зрения fov, чтобы настроить эффект рендеринга.

fovНастройте визуальные эффекты, соответствующие различным углам обзора усеченной пирамиды камеры.

// fov:90度
const camera = new THREE.PerspectiveCamera(90, width / height, 1, 3000);

Пожалуйста, добавьте описание изображения

// fov:30度
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);

Пожалуйста, добавьте описание изображения

.targetЭлементы управления и параметры камеры lookAt()согласованы

Если управление камерой .targetи lookAt()параметры совпадают, вы можете вращать камеру внутри конвейера.

// 相机控件
const control = () => {
    
    
  controls = new OrbitControls(camera, renderer.domElement);
  controls.target.copy(pointsArr[i + 1]);
  controls.update();
};
control();

Эффект следующий: (Это изображение представляет собой эффект сжатия GIF, фактический эффект идеален)

Пожалуйста, добавьте описание изображения

Добавить полную анимацию в камеру

let num = 0;
const render = () => {
    
    
  if (num < pointsArr.length - 1) {
    
    
    // 相机位置设置在当前点位置
    camera.position.copy(pointsArr[num]);
    // 曲线上当前点pointsArr[num]和下一个点pointsArr[num+1]近似模拟当前点曲线切线
    // 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
    camera.lookAt(pointsArr[num + 1]);
    num += 1; //调节速度
  } else {
    
    
    num = 0;
  }
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

Рендеринг с помощью точечной модели создаст эффект, похожий на червоточину.

material = new THREE.PointsMaterial({
    
    
  map: texture,
  side: THREE.DoubleSide, //双面显示看到管道内壁
});
mesh = new THREE.Points(geometry, material);

Пожалуйста, добавьте описание изображения

Полный код:

/* * @Author: SouthernWind * 
@Date: 2023-06-14 16:38:59 * 
@Last Modified by:SouthernWind * 
@Last Modified time: 2023-06-27 14:10:21 */
<template>
  <div class="btn">
    <el-button id="x" @click="xyzClick('x')">x</el-button>
    <el-button id="y" @click="xyzClick('y')">y</el-button>
    <el-button id="z" @click="xyzClick('z')">z</el-button>
  </div>
  <div class="container" ref="container"></div>
</template>

<script setup>
  import * as THREE from "three";
  // 轨道
  import {
      
       OrbitControls } from "three/examples/jsm/controls/OrbitControls";
  import {
      
       GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
  import {
      
       GUI } from "three/addons/libs/lil-gui.module.min.js";

  import {
      
       ref, reactive, onMounted } from "vue";
  // 三个必备的参数
  let scene,
    camera,
    renderer,
    controls,
    mesh,
    material,
    group,
    texture,
    gui,
    textureCube,
    pointsArr,
    i;

  onMounted(() => {
      
      
    // 外层需要获取到dom元素以及浏览器宽高,来对画布设置长宽
    // clientWidth等同于container.value.clientWidth
    let container = document.querySelector(".container");
    const {
      
       clientWidth, clientHeight } = container;
    console.log(clientHeight);
    // 首先需要获取场景,这里公共方法放在init函数中
    const init = () => {
      
      
      scene = new THREE.Scene();
      // 给相机设置一个背景
      scene.background = new THREE.Color(0xaaaaaa);
      // 透视投影相机PerspectiveCamera
      // 支持的参数:fov, aspect, near, far
      /* camera = new THREE.PerspectiveCamera(
        60,
        clientWidth / clientHeight,
        0.001,
        6000
      ); */
      camera = new THREE.PerspectiveCamera(
        90,
        clientWidth / clientHeight,
        1,
        3000
      );
      camera.position.set(150, 150, 150);
      camera.lookAt(0, 0, 0);
      /*     const width = window.innerWidth; //canvas画布宽度
      const height = window.innerHeight; //canvas画布高度
      const k = width / height; //canvas画布宽高比
      const s = 50; //控制left, right, top, bottom范围大小
      camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 8000);
      camera.position.set(15, 15, 15);
      camera.lookAt(0, 0, 0); //指向坐标原点 */
      /*     const width = window.innerWidth; //canvas画布宽度
      const height = window.innerHeight; //canvas画布高度
      const k = width / height; //canvas画布宽高比
      const s = 2000; //控制left, right, top, bottom范围大小
      camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 8000); */
      // 相机坐标
      // camera.position.set(30, 30, 30);
      // camera.lookAt(2000, 0, 2000);

      // 渲染器
      renderer = new THREE.WebGLRenderer({
      
      
        antialias: true,
        preserveDrawingBuffer: true,
        logarithmicDepthBuffer: true,
      });
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      // 渲染多大的地方
      renderer.setClearAlpha(0.0);
      renderer.setSize(clientWidth, clientHeight);
      /* renderer.outputEncoding = THREE.sRGBEncoding; */
      const axesHelper = new THREE.AxesHelper(150);
      scene.add(axesHelper);
      container.appendChild(renderer.domElement);
      addBox();
      console.log("查看当前屏幕设备像素比", window.devicePixelRatio);
    };
    init();
    function addBox() {
      
      
      const path = new THREE.CatmullRomCurve3([
        new THREE.Vector3(-50, 20, 90),
        new THREE.Vector3(-10, 40, 40),
        new THREE.Vector3(0, 0, 0),
        new THREE.Vector3(60, -60, 0),
        new THREE.Vector3(90, -40, 60),
        new THREE.Vector3(120, 30, 30),
      ]);
      // 样条曲线path作为TubeGeometry参数生成管道
      const geometry = new THREE.TubeGeometry(path, 200, 5, 30);
      const texLoader = new THREE.TextureLoader();
      //纹理贴图
      const texture = texLoader.load(
        new URL(`../assets/wood_table_001_diff_2k.jpg`, import.meta.url).href
      );
      //UV坐标U方向阵列模式
      texture.wrapS = THREE.RepeatWrapping;
      //纹理沿着管道方向阵列(UV坐标U方向)
      texture.repeat.x = 10;
      material = new THREE.PointsMaterial({
      
      
        map: texture,
        side: THREE.DoubleSide, //双面显示看到管道内壁
      });
      mesh = new THREE.Points(geometry, material);
      scene.add(mesh);
      pointsArr = path.getSpacedPoints(500);
      console.log(pointsArr);
       i = 100;
      // 相机位置:曲线上当前点pointsArr[i]
      camera.position.copy(pointsArr[i]);
      // 相机观察目标:当前点的下一个点pointsArr[i + 1]
      camera.lookAt(pointsArr[i + 1]);

      // 创建线模型对象
      // const geometry = new THREE.BoxGeometry(10, 10, 10);
      // // 材质
      // const material = new THREE.MeshPhysicalMaterial({
      
      
      //   color: 0x51efe4, //0x51efe4设置材质颜色
      // });
      // // 网络模型
      // mesh = new THREE.Mesh(geometry, material);
      // mesh.position.set(0, 0, 0);
      // const box3 = new THREE.Box3();
      // box3.expandByObject(mesh); // 计算模型包围盒
      // console.log("查看包围盒", box3);
      // const scale = new THREE.Vector3();
      // box3.getSize(scale);
      // console.log("模型包围盒尺寸", scale);
      // box3.getCenter(scale);
      // console.log("模型中心坐标", scale);
      // scene.add(mesh);
      // console.log('.up默认值',camera.up);
      // // camera.up.set(0,-1,0)
      // //渲染效果:红色x轴向上
      // // camera.up.set(1, 0, 0);
      // //渲染效果:蓝色z轴向上
      // camera.up.set(0, 0, 1);
      // /*     new GLTFLoader().load(
      //   new URL(`../assets/1.glb`, import.meta.url).href,
      //   (gltf) => {
      
      
      //     let gltfs = gltf.scene;
      //     gltfs.scale.set(5, 5, 5);

      //     scene.add(gltfs);
      //   }
      // ); */
    }

    // 相机控件
    const control = () => {
      
      
      controls = new OrbitControls(camera, renderer.domElement);
      controls.target.copy(pointsArr[i + 1]);
      controls.update();
      controls.addEventListener("change", function () {
      
      });
    };
    control();

    // 光源
    const linght = () => {
      
      
      /*     const pointLight = new THREE.PointLight(0xffffff, 1.0);
      // pointLight.position.set(400, 0, 0);//点光源放在x轴上
      pointLight.position.set(10, 10, 10); //设置光源的位置
      // 光源和网格模型Mesh对应一样是三维场景的一部分,自然需要添加到三维场景中才能起作用。
      scene.add(pointLight); // 添加光源到场景中 */

      const pointLight = new THREE.AmbientLight(0xffffff, 1.0);
      pointLight.position.set(10, 10, 10);
      scene.add(pointLight);
      /*     const pointLightHelper = new THREE.PointLightHelper(pointLight, 1);
      scene.add(pointLightHelper); */
    };
    linght();
    // 渲染循环
    /*   let angle = 0; //用于圆周运动计算的角度值
    const R = 10; //相机圆周运动的半径 */
    let num = 0;
    const render = () => {
      
      
      /*     angle += 0.01;
      // 相机y坐标不变,在XOZ平面上做圆周运动
      camera.position.x = R * Math.sin(angle);
      camera.lookAt(0, 0, 0); */
      if (num < pointsArr.length - 1) {
      
      
          // 相机位置设置在当前点位置
          camera.position.copy(pointsArr[num]);
          // 曲线上当前点pointsArr[num]和下一个点pointsArr[num+1]近似模拟当前点曲线切线
          // 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
          camera.lookAt(pointsArr[num + 1]);
          num += 1; //调节速度
      } else {
      
      
          num = 0
      }
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    };
    render();
    window.addEventListener("resize", () => {
      
      
      // 更新摄像头
      camera.aspect = window.innerWidth / window.innerHeight;
      // 相机参数更新
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
  });
  const xyzClick = (name) => {
      
      
    if (name == "x") {
      
      
      camera.position.set(30, 0, 0); //x轴方向观察
      camera.lookAt(0, 0, 0); //重新计算相机视线方向
    } else if (name == "y") {
      
      
      camera.position.set(0, 30, 0); //x轴方向观察
      camera.lookAt(0, 0, 0); //重新计算相机视线方向
    } else {
      
      
      camera.position.set(0, 0, 30); //x轴方向观察
      camera.lookAt(0, 0, 0); //重新计算相机视线方向
    }
  };
</script>

<style>
  .container {
      
      
    width: 100%;
    height: 100vh;
    position: relative;
    z-index: 1;
    /* background: #ff5810; */
  }
  .btn {
      
      
    position: absolute;
    top: 0;
    left: 0;
    z-index: 99;
  }
</style>

OrbitControlsПредел масштабирования вращения

.enablePanОтключить свойства панорамирования при щелчке правой кнопкой мыши

пример:

controls.enablePan = false; //禁止右键拖拽

Никакого масштабирования и вращения.

.enableZoomВы можете контролировать, разрешать ли средней кнопке мыши масштабировать сцену с помощью атрибута.По умолчанию атрибут .enableZoomимеет значение true.

controls.enableZoom = false; //禁止缩放

Нет вращения

controls.enableRotate = false; //禁止旋转

Свойства OrbitControls .target_

Обратите внимание: .targetсвойство OrbitControls элемента управления камерой соответствует цели наблюдения камеры .lookAt().

// controls.target默认值是坐标原点
controls.target.set(x, y, z);
//update()函数内会执行camera.lookAt(x, y, z)
controls.update();

Диапазон масштабирования перспективной проекционной камеры

.minDistanceи .maxDistanceиспользуется для управления диапазоном масштабирования средней кнопки мыши, когда текущая камера перспективной проекции

// 相机位置与观察目标点最小值
controls.minDistance = 10;
// 相机位置与观察目标点最大值
controls.maxDistance = 30;

Диапазон масштабирования ортогональной камеры

.minZoomи .maxZoomиспользуется для управления диапазоном масштабирования средней кнопки мыши, когда текущая ортогональная проекционная камера

Расстояние между положением камеры и целевой точкой наблюдения.getDistance()

control.getDistance() может вычислить расстояние между положением камеры и целевой точкой наблюдения камеры control.target.

//相机位置与目标观察点距离
const dis = controls.getDistance();
console.log("dis", dis);

Если вы не уверены в конкретном диапазоне масштабирования, вы можете воспользоваться .getDistance()помощью для решения этой проблемы.

Привяжите его к событию изменения

controls.addEventListener("change", function () {
    
    
  //相机位置与目标观察点距离
  const dis = controls.getDistance();
  console.log("dis", dis);
});

Пожалуйста, добавьте описание изображения

На этот раз, когда масштаб практически одинаковый, просто возьмите минимальное и максимальное значения масштабирования.

Установите диапазон вращения .minPolarAngleи.maxPolarAngle

Отобразите модель. Если вы не хотите поворачивать вниз, вы можете установить диапазон атрибутов вращения камеры. Управляйте диапазоном вращения вверх, вниз, влево и вправо
с помощью атрибутов и . Значение по умолчанию — от 0 до 180 . градусов. По умолчанию это 0 градусов. Плоскость XOZ параллельна холсту холста. Ось Y направлена ​​вертикально за пределы экрана. При угле 90 градусов ось Y результата рендеринга направлена ​​вертикально вверх. При угле 180 градусов плоскость XOZ параллельна холсту, а ось Y направлена ​​вертикально на экран..minPolarAngle.maxPolarAngle.minAzimuthAngle.maxAzimuthAngle

Диапазон вращения по умолчанию:

    controls.minPolarAngle = 0; //默认值0
    controls.maxPolarAngle = Math.PI/2; //默认值Math.PI

Установите .maxPolarAngleсвойство на 90 градусов, чтобы не было видно дна.

controls.maxPolarAngle = Math.PI/2;

Установите диапазон вращения влево и вправо

// 左右旋转范围
controls.minAzimuthAngle = -Math.PI/2;
controls.maxAzimuthAngle = Math.PI/2;

Пожалуйста, добавьте описание изображения

управление камеройMapControls

Метод введения

// 引入相机控件`MapControls`
import {
    
     MapControls } from 'three/addons/controls/OrbitControls.js';

Заменить элементы управления камерой

const controls = new MapControls(camera, renderer.domElement);

Эффект:
Пожалуйста, добавьте описание изображения

MapControls существенно изменяет параметры камеры, такие как атрибуты положения камеры и целевую точку наблюдения камеры.

Масштабирование, вращение или панорамирование отключены.

Эффект тот же, что и у OrbitControls, но работа с мышью другая.

controls.enablePan = false; //禁止平移
controls.enableZoom = false;//禁止缩放
controls.enableRotate = false; //禁止旋转

Диапазон масштабирования перспективной проекционной камеры

//相机位置与观察目标点最小值
controls.minDistance = 2;
//相机位置与观察目标点最大值
controls.maxDistance = 5;

Установить диапазон вращения

Та же самая причина

// 上下旋转范围
controls.minPolarAngle = 0;
controls.maxPolarAngle = Math.PI/2;
// 左右旋转范围
controls.minAzimuthAngle = -Math.PI/2;
controls.maxAzimuthAngle = Math.PI/2;

Supongo que te gusta

Origin blog.csdn.net/nanchen_J/article/details/131759699
Recomendado
Clasificación