Three.js——Fifteen, Box3, camera animation, lookAt() sight direction, pipeline roaming case, OrbitControls rotation and zoom restrictions, and camera control MapControls

orthographic projection camera

The difference between an orthographic projection camera and a perspective camera.
If you look down at the entire scene from a high place, the orthographic projection camera will have a 2D visualization effect, while the perspective camera will have an observation effect similar to the human eye.

Adjust left, right, top, bottom range size

If you want to preview all cubes as a whole, you need to adjust the rendering range of the camera, such as setting the range of up, down, left, and right.

Usage scenarios: Orthographic projection can be used to preview the map of China, or to achieve 2D visualization effects.
Perspective projection cameras are generally used by people to roam around the scene, or to look down at the entire scene.

Bounding boxBox3

It is to wrap all the vertices of the entire model to form a cuboid. This cuboid is 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);

Please add image description

Bounding box size.getSize()

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

Please add image description

Bounding box geometric center

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

Please add image description

camera animation.position和lookAt()

Change the camera's position.position, and the three-dimensional scene will show different effects on the canvas. If you continuously change the camera's position.position, you can get an animation effect.
The following implements a camera animation

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();

Please add image description

Perform lookAt()calculation of camera gaze direction

After changing the .position property, if the .lookAt() method is not executed, the camera's viewing direction will remain unchanged by default.

If you want the camera to move in a circular motion, change the direction of the camera's sight, and keep the camera lens always pointing to the origin of the coordinates or other positions, you need to re-execute the .lookAt() method every time you change the .position property, which means that the camera is in the middle of the animation. At the same time, keep the lens pointed at the origin position.

Projection views in different directions

<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); //重新计算相机视线方向
  }
};

Starting axis position:
Please add image description

The lens effect is as follows:
Please add image description

Please add image description

Please add image description

Rotate rendering results

This concept is actually that when you take a camera to shoot an object, for example, from up, down, left, right, or diagonally, the effect of your photo will be the same 旋转效果.

Rotate rendering results (.up camera up direction)

.upThe default value of the property is new THREE.Vector3(0,1,0), meaning upward along the y-axis.

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

Please add image description

Please add image description

Change up to see the effect

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

Please add image description

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

Please add image description

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

Please add image description

Execution order problem

Note .upthat properties, like properties, need to be re-executed .positionif they change after execution ..lookAt().lookAt()

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

Pipe roaming effect

Create pipeline

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);

For cameras, choose a perspective projection camera

Get the vertices on the motion trajectory

Through the curve's .getSpacedPoints() method, a series of vertex coordinate data can be obtained evenly from the trajectory line, and then you can use these vertex coordinates on the trajectory line to set the camera position.

const pointsArr = path.getSpacedPoints(500);

Please add image description

Place the camera on the internal trajectory of the pipe

The camera is placed at any position on the trajectory within the pipeline, and the camera's line of sight is controlled to coincide with the tangent line of the curve. The current point and the next point
on the curve approximately simulate the tangent line of the current point curve. The smaller the distance between the two points, the higher the simulation accuracy.pointsArr[i]pointsArr[i+1]

.lookAt()Set the camera observation point to pointsArr[i]the next point of the current point pointsArr[i + 1], so that the camera line of sight coincides with the tangent line of the current point on the curve.

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

Change the field of view angle fovto adjust the rendering effect

fovAdjust the visual effects corresponding to different field of view angles of the camera's frustum

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

Please add image description

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

Please add image description

Camera controls .targetand lookAt()parameters are consistent

If the camera control .targetand lookAt()the parameters are consistent, you can rotate the camera inside the pipeline

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

The effect is as follows: (This picture is the effect of GIF compression, the actual effect is ideal)

Please add image description

Add full animation to camera

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();

Rendering with a point model will render an effect similar to a wormhole.

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

Please add image description

Complete code:

/* * @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>

OrbitControlsRotation zoom limit

Disable right-click panning .enablePanproperties

example:

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

No scaling or rotation

.enableZoomYou can control whether to allow the middle mouse button to zoom the scene through the attribute. The .enableZoomattribute defaults to true.

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

No rotation

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

OrbitControlsProperties .target_

Please note: The OrbitControls .targetproperty of the camera control corresponds to the camera's .lookAt()observation target.

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

Perspective projection camera zoom range

.minDistanceand .maxDistanceused to control the range of the middle mouse button zoom when the current perspective projection camera is

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

Orthographic camera zoom range

.minZoomand .maxZoomused to control the middle mouse button zoom range when the current orthographic projection camera is

The distance between the camera position and the target observation point.getDistance()

controls.getDistance() can calculate the distance between the camera position.position and the camera target observation point controls.target.

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

If you are not sure about the specific range of zoom, you can use .getDistance()assistance to solve it.

Bind it to the change event

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

Please add image description

At this time, when the zoom is almost the same, just take the minimum and maximum zoom values.

Set the rotation range .minPolarAngleand.maxPolarAngle

Display a model. If you don’t want to rotate to the bottom, you can set the camera rotation attribute range.
Control the up, down, left, and right rotation range through .minPolarAngle, .maxPolarAngle, .minAzimuthAngleand .maxAzimuthAngleattributes. The default is from 0 to 180 degrees. By default, it is 0 degrees. The XOZ plane is parallel to the canvas canvas. The y-axis points vertically outside the screen. At 90 degrees, the y-axis of the rendering result is vertically upward. At 180 degrees, the XOZ plane is parallel to the canvas, and the y-axis points vertically into the screen.

Default rotation range:

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

Set .maxPolarAnglethe property to 90 degrees so you can't see the bottom

controls.maxPolarAngle = Math.PI/2;

Set left and right rotation range

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

Please add image description

camera controlsMapControls

Introduction method

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

Replace camera controls

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

Effect:
Please add image description

MapControls essentially changes the parameters of the camera, such as the camera's position attributes and the camera's target observation point.

Zooming, rotating or panning disabled

The effect is the same as OrbitControls, but the mouse operation is different.

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

Perspective projection camera zoom range

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

Set rotation range

Same reason

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

Guess you like

Origin blog.csdn.net/nanchen_J/article/details/131759699