Web3D-相机篇

289 阅读4分钟

正交与透视相机的区别

image.png image.png image.png image.png

  • 箭头朝向表示,例如,人拿着相机是站着拍摄、侧着拍摄、躺着拍摄;
  • 可通过透视相机去观察,正交相机与物体之间的情况:
  createCamera () {
    const size = 4;
    // 创建正交相机
    const orthoCamera = new THREE.OrthographicCamera(-size, size, size / 2, -size / 2, 0.1, 1000);
    // 相机位置
    orthoCamera.position.set(2, 2, 3)
    // 设置相机朝向
    orthoCamera.lookAt(this.scene.position)
    // 相机添加场景中
    this.scene.add(orthoCamera)
    this.orthoCamera = orthoCamera
    // this.camera = orthoCamera
    // console.log(orthoCamera);

    // 透视相机 第二个相机
    const watcherCamera = new THREE.PerspectiveCamera(75, this.width / this.height)
    // 设置相机位置
    watcherCamera.position.set(2, 2, 6)
    // 设置相机朝向
    watcherCamera.lookAt(this.scene.position)
    // 将相机添加到场景中
    this.scene.add(watcherCamera)
    this.camera = watcherCamera
  },
  // 添加辅助
  helpers () {
    // 创建辅助坐标系
    const axesHelper = new THREE.AxesHelper();
    // 相机辅助 观察正交相机
    const cameraHelper = new THREE.CameraHelper(this.orthoCamera)
    this.cameraHelper = cameraHelper
    this.scene.add(axesHelper, cameraHelper)
  },
  tick () {
    //相机辅助更新
    this.cameraHelper.update()
  }

dat.gui库调试

  datGui () {
    const _this = this
    const gui = new dat.GUI();

    const params = {
      wireframe: false,
      switchCamera () {
        console.log(this.camera);
        if (_this.camera.type === 'OrthographicCamera') {
          // 透视相机
          _this.camera = _this.watcherCamera;
          // 轨道控制器启用 消除切换之间的干扰
          _this.orbitControls.enabled = true;
        } else {
          _this.camera = _this.orthoCamera;
          _this.orbitControls.enabled = false;
        }
      },
    }

    gui.add(this.camera.position, 'x', 0.1, 100, 0.1).name('positionX')
    gui.add(this.camera.rotation, 'x', 0.1, 100, 0.1).name('rotationX')
    gui.add(this.camera, 'near', 0.01, 10, 0.01).onChange(val => {
      this.camera.near = val
      this.camera.updateProjectionMatrix();
    })
    gui.add(this.camera, 'far', 1, 100, 1).onChange(val => {
      this.camera.far = val
      this.camera.updateProjectionMatrix();
    })
    // 缩放
    gui.add(this.camera, 'zoom', 0.1, 10, 0.1).onChange(val => {
      this.camera.zoom = val
      this.camera.updateProjectionMatrix();
    })
    // 透视线框
    gui.add(params, 'wireframe').onChange(val => {
      this.mesh.material.wireframe = val;
    })
    // 相机切换
    gui.add(params, 'switchCamera');
    
    // 添加一个文件夹 可将多个参数放到一个文件夹下
    const lightFolder = gui.addFolder('光照')
    lightFolder.add(_this.directionalLight, 'intensity', 0, 1, 0.1)
    lightFolder.add(_this.directionalLight.position, 'x', -10, 10, 0.1)
    lightFolder.add(_this.directionalLight.position, 'y', -10, 10, 0.1)
    lightFolder.add(_this.directionalLight.position, 'z', -10, 10, 0.1)
    lightFolder.open() // 展开    默认不展开
  },

相机视锥辅助对象

image.png

  createCamera () {
    const pCamera = new THREE.PerspectiveCamera(75, this.width / this.height, 1, 10)

    pCamera.position.set(2, 2, 3)
    pCamera.lookAt(this.scene.position)
    this.scene.add(pCamera)
    this.pCamera = pCamera;
    this.camera = pCamera;

    // 透视相机 第二个相机
    const watcherCamera = new THREE.PerspectiveCamera(120, this.width / this.height, 0.1, 1000)
    // 设置相机位置
    watcherCamera.position.set(2, 2, 6)
    // 设置相机朝向
    watcherCamera.lookAt(this.scene.position)
    // 将相机添加到场景中
    this.watcherCamera = watcherCamera
    // this.camera = watcherCamera
    this.scene.add(watcherCamera)
  },
  datGui () {
    const _this = this;
    const gui = new dat.GUI();
    const params = {
      wireframe: false,
      switchCamera() {
        if ( _this.cameraIndex === 0 ) {
          _this.camera = _this.watcherCamera;
          _this.cameraIndex = 1;
        } else {
          _this.camera = _this.pCamera;
          _this.cameraIndex = 0
        }
      },
    }

    // gui.add(this.camera.position, 'x').min(0.1).max(100).step(0.1).name('positionX');
    gui.add(this.camera.position, 'x', 0.1, 100, 0.1).name('positionX')
    gui.add(this.camera, 'near', 0.01, 10, 0.01).onChange(val => {
      this.camera.near = val
      this.camera.updateProjectionMatrix();
    })
    gui.add(this.camera, 'far', 1, 100, 1).onChange(val => {
      this.camera.far = val
      this.camera.updateProjectionMatrix();
    })
    // 缩放
    gui.add(this.camera, 'zoom', 0.1, 10, 0.1).onChange(val => {
      this.camera.zoom = val
      this.camera.updateProjectionMatrix();
    })
    // 透视线框
    gui.add(params, 'wireframe').onChange(val => {
      this.mesh.material.wireframe = val;
    })
    gui.add(this.camera, 'fov', 40, 150, 1).onChange(val => {
      this.camera.fov = val;
      this.camera.updateProjectionMatrix();
    })
    gui.add(params, 'switchCamera');
  },
  // 添加辅助
  helpers () {
    // 相机辅助 观察相机
    const cameraHelper = new THREE.CameraHelper(this.pCamera)
  }

是否包含在视锥内

  • 可通过 mesh对象的原型链上 computeBoundingBox()方法计算得出 image.png
  • 调用结果:包围盒最小、最大的空间向量 image.png
  frustumResult () {
    // 通过camera计算视锥
    const frustum = new THREE.Frustum()
    // 更新以保证拿到最终结果
    this.pCamera.updateProjectionMatrix()
    frustum.setFromProjectionMatrix(
      // 得到视锥体的矩阵
      new THREE.Matrix4().multiplyMatrices(
        this.pCamera.projectionMatrix,
        this.pCamera.matrixWorldInverse,
      )
    )
    const result = frustum.intersectsBox(this.mesh.geometry.boundingBox)
    console.log(result);  // true false 
  },
  gui.add(this.camera, 'near', 0.01, 10, 0.01).onChange(val => {
      this.frustumResult()
  })

三维矩阵Matrix3-API

DragControls拖拽与OrbitControls轨道,同时开启

  1. 拖拽场景时,只有相机跟随着物体旋转,即仅 OrbitControls轨道生效
  2. 拖拽物体时,仅物体跟随鼠标移动,即仅 DragControls拖拽生效
  controls () {
    // 创建轨道控制器
    const orbitControls = new OrbitControls(this.camera, this.canvas)
    // 开启惯性
    orbitControls.enableDamping = true;
    this.orbitControls = orbitControls
    // orbitControls 该对象上有datGui能调试的属性
    console.log(orbitControls);

    // 拖拽控制器
    const dragControls = new DragControls([this.mesh], this.camera, this.canvas)

    // 开启事件监听
    dragControls.addEventListener('dragstart', ()=>{
      // 拖拽开始事件
      orbitControls.enabled = false
    })
    dragControls.addEventListener('dragend',()=>{
      // 拖拽结束
      orbitControls.enabled = true
    })
  },
  datGui () {
    const _this = this
    const gui = new dat.GUI();

    // 是否启用
    gui.add(_this.orbitControls, 'enabled')
    // 阻尼系数 物体运动的惯性大小
    gui.add(_this.orbitControls, 'dampingFactor', 0.01, 0.2, 0.01)
    // 相机平移的速度
    gui.add(_this.orbitControls, 'panSpeed', 1, 10, 1)
    // 实现物体自转
    gui.add(_this.orbitControls, 'autoRotate')
    // 物体自转速度
    gui.add(_this.orbitControls, 'autoRotateSpeed', 1, 10, 1)
    // 鼠标滚轮是否能缩放
    gui.add(_this.orbitControls, 'enableZoom')
    // 鼠标滚轮缩放速度 
    gui.add(_this.orbitControls, 'zoomSpeed')
  },

在小窗口上展示,画布中裁剪的区域

  1. 需要2个相机对象观察
  2. 渲染2个相机看到的场景
  3. 裁剪时,需要裁剪2次,并且要开启 renderer.setScissorTest(true)裁剪检测
  4. 以上方法都是相互独立的,在各自的裁剪函数中,执行渲染;
  5. WebGLRenderer对象中,setScissor裁剪属性、setViewport视口属性,两者的x、y坐标轴如下: image.png
  6. 相机对象中,rotation旋转 = quaternion四元数,只要物体发生旋转效果,两者都会触发,因此使用的时候,二选一即可;
  7. 缩略图与非缩略图出现宽高比例不一致问题:
  createCamera () {
    // 正交相机 
    const frustumSize = 2 // 设置显示相机前方高为2的内容 视锥尺寸
    const aspect  = this.width / this.height
    const pCamera = new THREE.OrthographicCamera(-aspect*frustumSize, aspect*frustumSize, aspect, -aspect, 0.1, 1000)
    // 设置相机位置
    pCamera.position.set(1, 1, 2)
    // 设置相机朝向
    pCamera.lookAt(this.scene.position)
    // 将相机添加到场景中
    this.scene.add(pCamera)
    this.pCamera = pCamera
    this.camera = pCamera

    // 缩略图相机
    const thumbnailAspect = 150 / 200
    const thumbnailCamera = new THREE.OrthographicCamera(-thumbnailAspect*frustumSize, thumbnailAspect*frustumSize, thumbnailAspect, -thumbnailAspect, 0.1, 1000)
    // 位置
    thumbnailCamera.position.set(1, 1, 2)
    // 朝向
    thumbnailCamera.lookAt(this.scene.position)
    // 场景
    this.scene.add(thumbnailCamera)
    this.thumbnailCamera = thumbnailCamera
  },
  // 全局裁剪
  clipScene(renderer) {
    const dpr = window.devicePixelRatio || 1

    // 裁剪
    renderer.setScissor(0, 0, this.width, this.height)
    // 背景色 透明度
    renderer.setClearColor(0x999999, 0.5)

    // 设置渲染器屏幕像素比 移动端解决像素问题
    renderer.setPixelRatio(dpr)
    // 设置渲染器大小
    // 调用setSize() 相当于使用了 setViewport(0, 0, this.width, this.height)
    renderer.setSize(this.width, this.height)
    // 执行渲染
    renderer.render(this.scene, this.camera)
  },
  // 裁剪缩略图
  clipThumbnail (renderer) {
    // 小窗口 w: 150  h: 200   margin: 10
    const w = this.width - 150 - 10

    // 更新位置
    this.thumbnailCamera.position.copy(this.camera.position)
    // 更新旋转 rotation = quaternion 物体触发旋转效果时,两个属性都会触发,仅用其一即可
    this.thumbnailCamera.rotation.copy(this.camera.rotation)
    // 更新四元数(更新旋转)
    // this.thumbnailCamera.quaternion.copy(this.camera.quaternion)
    // 更新缩放
    this.thumbnailCamera.zoom = this.camera.zoom
    // 更新相机矩阵
    this.thumbnailCamera.updateProjectionMatrix()

    renderer.setScissor(w, 20, 150, 200)
    // 设置原因:需要同步 全局裁剪 视口的变化
    renderer.setViewport(w, 20, 150, 200)
    // 背景色
    renderer.setClearColor(0x000000)

    //执行渲染
    renderer.render(this.scene, this.thumbnailCamera)
  },
  
// 开启裁剪检测
this.renderer.setScissorTest(true)