[3D] Efecto de movimiento del seguimiento de la cámara three.js

¡Trabajar juntos para crear y crecer juntos! Este es el 4to día de mi participación en el "Nuggets Daily New Plan · August Update Challenge", haz clic para ver los detalles del evento

Lograr el efecto deseado

Especifique una ruta de polilínea y la cámara se moverá hacia adelante a lo largo de la ruta, similar al primer punto de vista caminando en la ruta actual.

Realizar ideas

Es muy sencillo dibujar una ruta de polilínea, vincular dinámicamente la posición de la cámara a la ruta actual y configurar la cámara para que mire hacia el frente de la ruta.

Dificultades de realización

1. Curva de descuento

Dibuje una ruta de polilínea, generalmente marque cada punto de inflexión y dibuje TRES. Línea, que se convertirá en una curva.

Respuestas de dificultad:

  • 1.1 Separados por puntos de inflexión, dibuje una línea recta segmento por segmento. El punto final del segmento de línea anterior es el punto de inicio del siguiente segmento de línea.
  • 1.2 Dibuje una polilínea En el punto de inflexión, agregue un punto adicional para formar un arco corto particularmente sutil.

2. No se controla la orientación de la lente

Para la cámara limitada por controles, la modificación de LookAt y la rotación de la cámara no responde.

Respuestas de dificultad:

La configuración de la dirección de visualización de la cámara camera.lookAt no es válida, debe configurar los controles.objetivo

3. La unión de la posición de la lente no está controlada

Para la cámara sujeta por controles, siempre hay una cierta desalineación al modificar dinámicamente la posición de la cámara.

Respuestas de dificultad:

Dios, he estado enredado en este problema durante mucho tiempo, no importa cómo configurarlo, incluso si me refiero a la pregunta anterior para controlar controles.objeto.posición, no es correcto.

¡Resulta que esta es una dificultad falsa, la posición de la lente está controlada y se siente descontrolada porque se establece la distancia más cercana desde la cámara hasta el origen! ! ! Si la distancia es demasiado cercana al girar, la cámara retrocederá y girará, golpeando algo a su lado, llorando.

// 设置相机距离原点的最近距离 即可控制放大限值
// controls.minDistance = 4
// 设置相机距离原点的最远距离 即可控制缩小限值
controls.maxDistance = 40
复制代码

4. Movimiento de la lente

La lente tiembla Se sospecha que cuando las coordenadas se redondean al establecer la posición y la orientación, causa la sacudida por una vez, una vez, una vez, una izquierda y una derecha.

Respuestas de dificultad:

Al principio pensé que toda la escena era demasiado pequeña. Acercar la escena, estirar la polilínea y alejar la cámara no ayudó.

Finalmente, descubrí que la posición de la cámara se estableció en la animación animate(), y la coordenada y se agregó en 0.01:

controls.object.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z)
复制代码

Las coordenadas de posición de la cámara y las coordenadas de orientación de la cámara no están en el mismo plano, y la fluctuación resultante es normal si se elimina +0.01.

controls.object.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
复制代码

método de implementación final

Aquí, a través de dos cámaras, primero observe la ruta de movimiento y la dirección de la cámara cameraTest, y luego cambie a la cámara original.

El código público es el siguiente:

// 外层相机,原始相机
let camera = null
// 内层相机和相机辅助线
let cameraTest = null
let cameraHelper = null
// 控制器
let controls = null
// 折线点的集合和索引
let testList = []
let testIndex = 0

initCamera () {
  // 原始相机
  camera = new THREE.PerspectiveCamera(45, div3D.clientWidth / div3D.clientHeight, 0.1, 1000)
  camera.position.set(16, 6, 10)
  // scene.add(camera)
  // camera.lookAt(new THREE.Vector3(0, 0, 0))
  // 设置第二个相机
  cameraTest = new THREE.PerspectiveCamera(45, div3D.clientWidth / div3D.clientHeight, 0.1, 1000)
  cameraTest.position.set(0, 0.6, 0)
  cameraTest.lookAt(new THREE.Vector3(0, 0, 0))
  cameraTest.rotation.x = 0
  // 照相机帮助线
  cameraHelper = new THREE.CameraHelper(cameraTest)
  scene.add(cameraTest)
  scene.add(cameraHelper)
}
// 初始化控制器
initControls () {
  controls = new OrbitControls(camera, renderer.domElement)
}
复制代码

Método 1: avance la cámara a lo largo de la línea

inspectCurveList () {
  let curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(2.9, 0.6, 7),
    new THREE.Vector3(2.9, 0.6, 1.6),
    new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
    new THREE.Vector3(2.2, 0.6, 1.6),
    new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
    new THREE.Vector3(2.2, 0.6, -5),
    new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
    new THREE.Vector3(8, 0.6, -5),
    new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
    new THREE.Vector3(8, 0.6, -17),
    new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
    new THREE.Vector3(-1, 0.6, -17),
    // new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
    new THREE.Vector3(-3, 0.6, -20.4),
    new THREE.Vector3(-2, 0.6, 5)
  ])
  let geometry = new THREE.Geometry()
  let gap = 1000
  for (let i = 0; i < gap; i++) {
    let index = i / gap
    let point = curve.getPointAt(index)
    let position = point.clone()
    curveList.push(position)
    geometry.vertices.push(position)
  }
  // geometry.vertices = curve.getPoints(500)
  // curveList = geometry.vertices
  // let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
  // let line = new THREE.Line(geometry, material) // 连成线
  // line.name = 'switchInspectLine'
  // scene.add(line) // 加入到场景中
}
// 模仿管道的镜头推进
if (curveList.length !== 0) {
	if (curveIndex < curveList.length - 20) {
	  // 推进里层相机
	  /* cameraTest.position.set(curveList[curveIndex].x, curveList[curveIndex].y, curveList[curveIndex].z)
	  controls = new OrbitControls(cameraTest, labelRenderer.domElement) */
	  // 推进外层相机
	  // camera.position.set(curveList[curveIndex].x, curveList[curveIndex].y + 1, curveList[curveIndex].z)
	  controls.object.position.set(curveList[curveIndex].x, curveList[curveIndex].y, curveList[curveIndex].z)
	  controls.target = curveList[curveIndex + 20]
	  // controls.target = new THREE.Vector3(curveList[curveIndex + 2].x, curveList[curveIndex + 2].y, curveList[curveIndex + 2].z)
	  curveIndex += 1
	} else {
	  curveList = []
	  curveIndex = 0
	  this.inspectSwitch = false
	  this.addRoomLabel()
	  this.removeLabel()
	  // 移除场景中的线
	  // let removeLine = scene.getObjectByName('switchInspectLine')
	  // if (removeLine !== undefined) {
	  //   scene.remove(removeLine)
	  // }
	  // 还原镜头位置
	  this.animateCamera({x: 16, y: 6, z: 10}, {x: 0, y: 0, z: 0})
	}
}
复制代码

Método 2: Utilice la animación de interpolación

inspectTween () {
  let wayPoints = [
    {
      point: {x: 2.9, y: 0.6, z: 1.6},
      camera: {x: 2.9, y: 0.6, z: 7},
      time: 3000
    },
    {
      point: {x: 2.2, y: 0.6, z: 1.6},
      camera: {x: 2.9, y: 0.6, z: 1.6},
      time: 5000
    },
    {
      point: {x: 2.2, y: 0.6, z: -5},
      camera: {x: 2.2, y: 0.6, z: 1.6},
      time: 2000
    },
    {
      point: {x: 8, y: 0.6, z: -5},
      camera: {x: 2.2, y: 0.6, z: -5},
      time: 6000
    },
    {
      point: {x: 8, y: 0.6, z: -17},
      camera: {x: 8, y: 0.6, z: -5},
      time: 3000
    },
    {
      point: {x: -2, y: 0.6, z: -17},
      camera: {x: 8, y: 0.6, z: -17},
      time: 3000
    },
    {
      point: {x: -2, y: 0.6, z: -20.4},
      camera: {x: -2, y: 0.6, z: -17},
      time: 3000
    },
    {
      point: {x: -2, y: 0.6, z: 5},
      camera: {x: -3, y: 0.6, z: -17},
      time: 3000
    },
    // {
    //   point: {x: -2, y: 0.6, z: 5},
    //   camera: {x: -2, y: 0.6, z: -20.4}
    // },
    {
      point: {x: 0, y: 0, z: 0},
      camera: {x: -2, y: 0.6, z: 5},
      time: 3000
    }
  ]
  this.animateInspect(wayPoints, 0)
}
animateInspect (point, k) {
  let self = this
  let time = 3000
  if (point[k].time) {
    time = point[k].time
  }
  let count = point.length
  let target = point[k].point
  let position = point[k].camera
  let tween = new TWEEN.Tween({
    px: camera.position.x, // 起始相机位置x
    py: camera.position.y, // 起始相机位置y
    pz: camera.position.z, // 起始相机位置z
    tx: controls.target.x, // 控制点的中心点x 起始目标位置x
    ty: controls.target.y, // 控制点的中心点y 起始目标位置y
    tz: controls.target.z // 控制点的中心点z 起始目标位置z
  })
  tween.to({
    px: position.x,
    py: position.y,
    pz: position.z,
    tx: target.x,
    ty: target.y,
    tz: target.z
  }, time)
  tween.onUpdate(function () {
    camera.position.x = this.px
    camera.position.y = this.py
    camera.position.z = this.pz
    controls.target.x = this.tx
    controls.target.y = this.ty
    controls.target.z = this.tz
    // controls.update()
  })
  tween.onComplete(function () {
    // controls.enabled = true
    if (self.inspectSwitch && k < count - 1) {
      self.animateInspect(point, k + 1)
    } else {
      self.inspectSwitch = false
      self.addRoomLabel()
      self.removeLabel()
    }
    // callBack && callBack()
  })
  // tween.easing(TWEEN.Easing.Cubic.InOut)
  tween.start()
},
复制代码

Comparación de métodos:

  • Método 1: el control de la lente es simple, pero no lo suficientemente fluido.
  • Método 2: El control de la lente es problemático. Para especificar el punto actual y el punto de destino, el cambio de la lente es suave pero no está estrictamente controlado.

Personalmente, me gusta el segundo método, siempre que encuentre los puntos de control en la línea, el efecto de animación es mejor y es más fácil controlar el tiempo de cada animación.

—————————————————————————

Otros metodos

Otros métodos utilizados en el proceso son solo para registro.

Método 1: dibujar una polilínea + avance de lente animado

// 获取折线点数组
testInspect () {
	// 描折线点,为了能使一条折线能直角转弯,特添加“用于直角转折”的辅助点,尝试将所有标为“用于直角转折”的点去掉,折线马上变曲线。
	let curve = new THREE.CatmullRomCurve3([
	    new THREE.Vector3(2.9, 0.6, 7),
	    new THREE.Vector3(2.9, 0.6, 1.6),
	    new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
	    new THREE.Vector3(2.2, 0.6, 1.6),
	    new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
	    new THREE.Vector3(2.2, 0.6, -5),
	    new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
	    new THREE.Vector3(8, 0.6, -5),
	    new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
	    new THREE.Vector3(8, 0.6, -17),
	    new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
	    new THREE.Vector3(-2, 0.6, -17),
	    new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
	    new THREE.Vector3(-2, 0.6, -20.4),
	    new THREE.Vector3(-2, 0.6, 5),
	])
	let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
	let geometry = new THREE.Geometry()
	geometry.vertices = curve.getPoints(1500)
	let line = new THREE.Line(geometry, material) // 连成线
	scene.add(line) // 加入到场景中
	testList = geometry.vertices
}
// 场景动画-推进相机
animate () {
  // 模仿管道的镜头推进
  if (testList.length !== 0) {
    if (testIndex < testList.length - 2) {
      // 推进里层相机
      // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
      // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
      // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
      // testIndex += 1
      // 推进外层相机
      camera.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
      controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
      testIndex += 1
    } else {
      testList = []
      testIndex = 0
    }
  }
}
复制代码

ilustrar:

Empuje la cámara en la capa interna, la cámara se mueve y gira normalmente, y en el giro en ángulo recto, la lente gira >90° y luego vuelve a reducirse a 90°;

Al empujar la cámara exterior hacia adentro, la lente de repente comienza a cortar aleatoriamente (porque se establece la distancia más cercana) y, en un giro en ángulo recto, la lente gira >90° y vuelve a cortar a 90°.

Método 2: Dibuja varios segmentos de línea + anima el avance de la lente

// 获取折线点数组
testInspect () {
	let points = [	    [2.9, 7],
	    [2.9, 1.6],
	    [2.2, 1.6],
	    [2.2, -5],
	    [8, -5],
	    [8, -17],
	    [-2, -17],
	    [-2, -20.4],
	    [-2, 5]
	  ]
	testList = this.linePointList(points, 0.6)
}
linePointList (xz, y) {
  let allPoint = []
  for (let i = 0; i < xz.length - 1; i++) {
    if (xz[i][0] === xz[i + 1][0]) {
      let gap = (xz[i][1] - xz[i + 1][1]) / 100
      for (let j = 0; j < 100; j++) {
        allPoint.push(new THREE.Vector3(xz[i][0], y, xz[i][1] - gap * j))
      }
    } else {
      let gap = (xz[i][0] - xz[i + 1][0]) / 100
      for (let j = 0; j < 100; j++) {
        allPoint.push(new THREE.Vector3(xz[i][0] - gap * j, y, xz[i][1]))
      }
    }
  }
  return allPoint
}
// 场景动画-推进相机
animate () {
  // 模仿管道的镜头推进
  if (testList.length !== 0) {
    if (testIndex < testList.length - 2) {
      // 推进里层相机
      // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
      // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
      // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
      // testIndex += 1
      // 推进外层相机
      camera.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
      controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
      testIndex += 1
    } else {
      testList = []
      testIndex = 0
    }
  }
}
复制代码

ilustrar:

Empuje la cámara en la capa interna, la cámara se mueve y gira normalmente, y el giro en ángulo recto es abrupto, porque es un punto empalmado por múltiples segmentos de línea;

Al empujar la cámara exterior, la cámara se mueve un poco desalineada (porque se establece la distancia más corta), la cámara gira normalmente, pero el giro en ángulo recto es abrupto, porque es un punto donde se empalman varios segmentos de línea.

Método 3: Dibuje múltiples segmentos de línea + interpolación de animación para cambiar la lente

// 获取折线点数组
testInspect () {
	let points = [
        [2.9, 7],
        [2.9, 1.6],
        [2.2, 1.6],
        [2.2, -5],
        [8, -5],
        [8, -17],
        [-2, -17],
        [-2, -20.4],
        [-2, 5]
      ]
    this.tweenCameraTest(points, 0) // tween动画-控制里层相机
    // this.tweenCamera(points, 0) // tween动画-控制外层相机
}
// tween动画-控制里层相机
tweenCameraTest (point, k) {
  let self = this
  let count = point.length
  let derection = 0
  if (cameraTest.position.x === point[k][0]) {
    // x相同
    if (cameraTest.position.z - point[k][1] > 0) {
      derection = 0
    } else {
      derection = Math.PI
    }
  } else {
    // z相同
    if (cameraTest.position.x - point[k][0] > 0) {
      derection = Math.PI / 2
    } else {
      derection = - Math.PI / 2
    }
  }
  cameraTest.rotation.y = derection
  let tween = new TWEEN.Tween({
    px: cameraTest.position.x, // 起始相机位置x
    py: cameraTest.position.y, // 起始相机位置y
    pz: cameraTest.position.z // 起始相机位置z
  })
  tween.to({
    px: point[k][0],
    py: 0.6,
    pz: point[k][1]
  }, 3000)
  tween.onUpdate(function () {
    cameraTest.position.x = this.px
    cameraTest.position.y = this.py
    cameraTest.position.z = this.pz
  })
  tween.onComplete(function () {
    if (k < count - 1) {
      self.tweenCameraTest(point, k + 1)
    } else {
      console.log('结束了!!!!!!')
    }
    // callBack && callBack()
  })
  // tween.easing(TWEEN.Easing.Cubic.InOut)
  tween.start()
}
// tween动画-控制外层相机
tweenCamera (point, k) {
  let self = this
  let count = point.length
  let derection = 0
  if (camera.position.x === point[k][0]) {
    // x相同
    if (camera.position.z - point[k][1] > 0) {
      derection = 0
    } else {
      derection = Math.PI
    }
  } else {
    // z相同
    if (camera.position.x - point[k][0] > 0) {
      derection = Math.PI / 2
    } else {
      derection = - Math.PI / 2
    }
  }
  camera.rotation.y = derection
  let tween = new TWEEN.Tween({
    px: camera.position.x, // 起始相机位置x
    py: camera.position.y, // 起始相机位置y
    pz: camera.position.z // 起始相机位置z
  })
  tween.to({
    px: point[k][0],
    py: 0.6,
    pz: point[k][1]
  }, 3000)
  tween.onUpdate(function () {
    camera.position.x = this.px
    camera.position.y = this.py
    camera.position.z = this.pz
  })
  tween.onComplete(function () {
    if (k < count - 1) {
      self.tweenCamera(point, k + 1)
    } else {
      console.log('结束了!!!!!!')
    }
    // callBack && callBack()
  })
  // tween.easing(TWEEN.Easing.Cubic.InOut)
  tween.start()
}
复制代码

ilustrar:

Utilice el método tweenCameraTest() para controlar la cámara en la capa interna. La cámara se mueve normalmente y la rotación se controla directamente mediante la rotación.y. Al girar, es un poco brusco porque no hay animación para controlar la rotación.y rotación ;

El método tweenCamera() se usa para controlar la cámara exterior. La cámara se mueve un poco desalineada (porque se establece la distancia más cercana), y la dirección de la cámara está completamente descontrolada, y parece mirar siempre al origen de coordenadas.

Método 4: método de optimización 1, dibujar una polilínea + avance de lente animado

// 获取折线点数组
testInspect () {
	// 描折线点,为了能使一条折线能直角转弯,特添加“用于直角转折”的辅助点,尝试将所有标为“用于直角转折”的点去掉,折线马上变曲线。
	let curve = new THREE.CatmullRomCurve3([
	    new THREE.Vector3(2.9, 0.6, 7),
	    new THREE.Vector3(2.9, 0.6, 1.6),
	    new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
	    new THREE.Vector3(2.2, 0.6, 1.6),
	    new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
	    new THREE.Vector3(2.2, 0.6, -5),
	    new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
	    new THREE.Vector3(8, 0.6, -5),
	    new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
	    new THREE.Vector3(8, 0.6, -17),
	    new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
	    new THREE.Vector3(-2, 0.6, -17),
	    new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
	    new THREE.Vector3(-2, 0.6, -20.4),
	    new THREE.Vector3(-2, 0.6, 5),
	])
	let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
    let geometry = new THREE.Geometry()
    let gap = 500
    for (let i = 0; i < gap; i++) {
        let index = i / gap
        let point = curve.getPointAt(index)
        let position = point.clone()
        testList.push(position) // 通过此方法获取点比curve.getPoints(1500)更好,不信你试试,用getPoints获取,镜头会有明显的俯视效果不知为何。
        geometry.vertices.push(position)
    }
    let line = new THREE.Line(geometry, material) // 连成线
    scene.add(line) // 加入到场景中
}
// 场景动画-推进外层相机
animate () {
  // 模仿管道的镜头推进
  if (testList.length !== 0) {
    if (testIndex < testList.length - 2) {
      // 推进里层相机
      // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
      // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
      // 推进外层相机
      // camera.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z)
      controls.object.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z) // 稍微讲相机位置上移,就不会出现似乎乱切镜头穿过旁边物体的效果。
      controls.target = testList[testIndex + 2]
      // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
      testIndex += 1
    } else {
      testList = []
      testIndex = 0
    }
  }
}
复制代码

ilustrar:

Se resolvió el problema de que en los giros en ángulo recto, la lente gira >90° y luego vuelve a cambiar a 90°.

Se resolvió un problema por el que al avanzar la lente exterior de la cámara se cortaba al azar.

Sin embargo, el movimiento de la cámara tiene un parpadeo hacia atrás notable al girar (porque se establece la distancia más cercana), y no sigue estrictamente la polilínea.

Supongo que te gusta

Origin juejin.im/post/7134221112405131278
Recomendado
Clasificación