[3D] Efeito de movimento do rastreamento de câmera three.js

Trabalhem juntos para criar e crescer juntos! Este é o 4º dia da minha participação no "Nuggets Daily New Plan · August Update Challenge", clique para ver os detalhes do evento

Alcance o efeito desejado

Especifique um caminho de polilinha e a câmera se move para frente ao longo do caminho, semelhante ao primeiro ponto de vista andando no caminho atual.

Realizar ideias

É muito simples desenhar um caminho de polilinha, vincular dinamicamente a posição da câmera ao caminho atual e definir a câmera para a frente do caminho.

Dificuldades de Realização

1. Curva de desconto

Desenhe um caminho de polilinha, geralmente marque cada ponto de virada e desenhe TRÊS.Linha, que se tornará uma curva.

Dificuldade responde:

  • 1.1 Separados por pontos de virada, desenhe uma linha reta segmento por segmento. O ponto final do segmento de linha anterior é o ponto inicial do próximo segmento de linha.
  • 1.2 Desenhe uma polilinha. No ponto de virada, adicione um ponto extra para formar um arco curto particularmente sutil.

2. A orientação da lente não é controlada

Para a câmera vinculada a controles, modificar o lookAt e a rotação da câmera não responde.

Dificuldade responde:

A configuração de direção de visualização da câmera camera.lookAt é inválida, você precisa definir controls.target

3. A ligação da posição da lente não é controlada

Para a câmera limitada por controles, sempre há um certo desalinhamento na modificação dinâmica da posição da câmera.

Dificuldade responde:

Deus, estou enredado nesse problema há muito tempo, não importa como defini-lo, mesmo que eu me refira à pergunta anterior para controlar controles.objeto.posição, não está correto.

Acontece que esta é uma dificuldade falsa, a posição da lente é controlada e parece descontrolada porque a distância mais próxima da câmera até a origem está definida! ! ! Se a distância estiver muito próxima ao virar, a câmera vai voltar e virar, batendo em algo próximo a ela, chorando.

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

4. Trepidação da lente

A lente treme. Suspeita-se que quando as coordenadas são arredondadas ao definir a posição e orientação, causa o tremor uma vez, uma vez, uma vez, uma esquerda e uma direita.

Dificuldade responde:

No começo eu pensei que toda a cena era muito pequena. Ampliar a cena, esticar a polilinha e diminuir o zoom da câmera não ajudou.

Finalmente, descobri que a posição da câmera foi definida na animação animate() e a coordenada y foi adicionada por 0,01:

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

As coordenadas de posição da câmera e as coordenadas de orientação da câmera não estão no mesmo plano, e o jitter resultante é normal se +0,01 for removido.

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

método de implementação final

Aqui, por meio de duas câmeras, primeiro observe o caminho do movimento e a direção da câmera cameraTest e, em seguida, alterne para a câmera original da câmera.

O código público é o seguinte:

// 外层相机,原始相机
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 a câmera ao longo da linha

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: usar animação de interpolação

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()
},
复制代码

Comparação de métodos:

  • Método 1: O controle da lente é simples, mas não suave o suficiente.
  • Método 2: O controle da lente é problemático. Para especificar o ponto atual e o ponto alvo, a troca da lente é suave, mas não estritamente controlada.

Pessoalmente gosto do segundo método, desde que você encontre os pontos de controle na linha, o efeito da animação é melhor e fica mais fácil controlar o tempo de cada animação.

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

Outros métodos

Outros métodos usados ​​no processo são apenas para registro.

Método 1: desenhe uma polilinha + animar o avanço da lente

// 获取折线点数组
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:

Empurre a câmera na camada interna, a câmera se move e gira normalmente, e na virada em ângulo reto, a lente gira > 90° e depois corta para 90°;

Empurrando a câmera externa para dentro, a lente de repente começa a cortar aleatoriamente (porque a distância mais próxima é definida) e, em um ângulo reto, a lente gira > 90° e corta para 90°.

Método 2: desenhe vários segmentos de linha + animar o avanço da 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:

Empurre a câmera na camada interna, a câmera se move e gira normalmente, e o giro em ângulo reto é abrupto, pois é um ponto emendado por vários segmentos de linha;

Empurrando a câmera externa, a câmera se move um pouco desalinhada (porque a distância mais curta está definida), a câmera gira normalmente, mas o giro em ângulo reto é abrupto, pois é um ponto onde vários segmentos de linha são emendados.

Método 3: desenhe vários segmentos de linha + animação de interpolação para alterar a 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:

Use o método tweenCameraTest() para controlar a câmera na camada interna. A câmera se move normalmente e a rotação é controlada diretamente por rotation.y. Ao girar, é um pouco abrupto porque não há animação para controlar a rotação.y rotação ;

O método tweenCamera() é usado para controlar a câmera externa.A câmera se move um pouco desalinhada (porque a distância mais próxima está definida), e a direção da câmera é completamente descontrolada, e parece sempre olhar para a origem das coordenadas.

Método 4: Método de otimização 1, desenhe uma polilinha + animar o avanço da lente

// 获取折线点数组
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:

Resolvido o problema de que, em curvas em ângulo reto, a lente gira > 90° e depois volta para 90°.

Resolvido um problema em que o avanço da lente externa da câmera seria cortado aleatoriamente.

No entanto, o movimento da câmera tem uma cintilação para trás perceptível ao girar (porque a distância mais próxima é definida) e não segue estritamente a polilinha.

Acho que você gosta

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