Three.js—Fifteen、Box3、カメラ アニメーション、lookAt() 視線方向、パイプライン ローミングの場合、OrbitControls の回転とズームの制限、およびカメラ コントロール MapControls

正投影カメラ

正投影カメラと透視カメラの違い
高いところから全体を見下ろす場合、正投影カメラは2次元の視覚効果が得られ、透視カメラは人間の目に近い観察効果が得られます。 。

左、右、上、下の範囲サイズを調整する

すべての立方体を全体としてプレビューしたい場合は、上下左右の範囲を設定するなど、カメラの描画範囲を調整する必要があります。

使用シナリオ: 正射投影は、中国の地図をプレビューしたり、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()

カメラの位置を変更すると、キャンバス上で 3 次元シーンがさまざまな効果を示し、カメラの位置を変更し続けると、アニメーション効果を得ることができます。
以下はカメラアニメーションを実装します

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 カメラ上方向)

.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]、現在の点曲線の接線を近似的にシミュレートします。2 点間の距離が小さいほど、シミュレーションの精度は高くなります。

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

画像の説明を追加してください

カメラのコントロール.targetlookAt()パラメータは一貫しています

カメラ制御.targetlookAt()パラメータが一致している場合は、パイプライン内でカメラを回転できます。

// 相机控件
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; //禁止旋转

OrbitControlsProperties .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()

controls.getDistance()は、カメラのposition.positionとカメラのターゲット観測点controls.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;

おすすめ

転載: blog.csdn.net/nanchen_J/article/details/131759699