达到效果

指定一条折线路径,镜头沿着路径向前移动,类似第一视角走在当前路径上。

实现思路

很简单画一条折线路径,将镜头位置动态绑定在当前路径上,同时设置镜头朝向路径正前方。

实现难点

1、折现变曲线
画一条折线路径,通常将每一个转折点标出来画出的THREE.Line,会变成曲线。
难点解答:
1.1、以转折点分隔,一段一段的直线来画,上一个线段的终点是下一个线段的起点。
1.2、画一条折线,在转折点处,通过多加一个点,构成一个特别细微的短弧线。
2、镜头朝向不受控
对于controls绑定的camera,修改camera的lookAt和rotation并无反应。
难点解答:
相机观察方向camera.lookAt设置无效需要设置controls.target
3、镜头位置绑定不受控
对于controls绑定的camera,动态修改camera的位置总存在一定错位。
难点解答:
苍天啊,这个问题纠结我好久,怎么设置都不对,即便参考上一个问题控制controls.object.position也不对。
结果这是一个假的难点,镜头位置是受控的,感觉不受控是因为,设置了相机距离原点的最近距离!!!导致转弯时距离太近镜头会往回退着转弯,碰到旁边的东西啊,哭唧唧。

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

4、镜头抖动
镜头抖动,怀疑是设置位置和朝向时坐标被四舍五入时,导致一会上一会下一会左一会右的抖动。
难点解答:
开始以为是我整个场景太小了,放大场景,拉长折线,拉远相机,并没有什么用。
最后发现是在animate()动画中设置相机位置,y坐标加了0.01:

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

相机位置坐标和相机朝向坐标不在同一平面,导致的抖动,将+0.01去掉就正常了。

controls.object.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)

最终实现方法

在此通过两个相机,先观察相机cameraTest的移动路径和转向,再切换成原始相机camera。
公共代码如下:

// 外层相机,原始相机
let camera = null
// 内层相机和相机辅助线
let cameraTest = null
let cameraHelper = null
// 控制器
let controls = null
// 折线点的集合和索引
let testList = []
let testIndex = 0initCamera () {// 原始相机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)
}

方法一:镜头沿线推进

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 = 1000for (let i = 0; i < gap; i++) {let index = i / gaplet 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 = 0this.inspectSwitch = falsethis.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})}
}

方法二:使用tween动画

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 = thislet time = 3000if (point[k].time) {time = point[k].time}let count = point.lengthlet target = point[k].pointlet position = point[k].cameralet tween = new TWEEN.Tween({px: camera.position.x, // 起始相机位置xpy: camera.position.y, // 起始相机位置ypz: camera.position.z, // 起始相机位置ztx: controls.target.x, // 控制点的中心点x 起始目标位置xty: controls.target.y, // 控制点的中心点y 起始目标位置ytz: 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.pxcamera.position.y = this.pycamera.position.z = this.pzcontrols.target.x = this.txcontrols.target.y = this.tycontrols.target.z = this.tz// controls.update()})tween.onComplete(function () {// controls.enabled = trueif (self.inspectSwitch && k < count - 1) {self.animateInspect(point, k + 1)} else {self.inspectSwitch = falseself.addRoomLabel()self.removeLabel()}// callBack && callBack()})// tween.easing(TWEEN.Easing.Cubic.InOut)tween.start()
},

方法比较:

方法一:镜头控制简单,但是不够平滑。
方法二:镜头控制麻烦,要指定当前点和目标点,镜头切换平滑但不严格受控。
个人喜欢方法二,只要找好了线路上的控制点,动画效果更佳更容易控制每段动画的时间。

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

其他方法

过程中的使用过的其他方法,仅做记录用。

方法一:绘制一条折线+animate镜头推进

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

说明:
推进里层相机,相机移动和转向正常,且在直角转弯处,镜头转动>90°再切回90°;
推进外层相机,镜头突然开始乱切(因为设置了最近距离),且在直角转弯处,镜头转动>90°再切回90°。

方法二:绘制多条线段+animate镜头推进

// 获取折线点数组
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]) / 100for (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]) / 100for (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}}
}

说明:
推进里层相机,相机移动和转向正常,直角转弯处突兀,因为是多个线段拼接出来的点;
推进外层相机,相机移动有些许错位(因为设置了最近距离),相机转向正常,但是直角转弯处突兀,因为是多个线段拼接出来的点。

方法三:绘制多条线段+tween动画变化镜头

// 获取折线点数组
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 = thislet count = point.lengthlet derection = 0if (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 = derectionlet tween = new TWEEN.Tween({px: cameraTest.position.x, // 起始相机位置xpy: cameraTest.position.y, // 起始相机位置ypz: 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.pxcameraTest.position.y = this.pycameraTest.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 = thislet count = point.lengthlet derection = 0if (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 = derectionlet tween = new TWEEN.Tween({px: camera.position.x, // 起始相机位置xpy: camera.position.y, // 起始相机位置ypz: 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.pxcamera.position.y = this.pycamera.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()
}

说明:
控制里层相机使用tweenCameraTest()方法,相机移动正常,通过rotation.y控制直接转向,转弯时略突兀因为没有动画控制rotation.y转动;
控制外层相机使用tweenCamera()方法,相机移动有些许错位(因为设置了最近距离),相机转向完全不受控,似乎始终看向坐标原点。

方法四:优化方法一,绘制一条折线+animate镜头推进

// 获取折线点数组
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 = 500for (let i = 0; i < gap; i++) {let index = i / gaplet 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}}
}

说明:
解决了,直角转弯处,镜头转动>90°再切回90°的问题。
解决了,推进外层相机镜头乱切的问题。
但是,相机移动在转弯时有明显的往后闪(因为设置了最近距离),并不是严格跟随折线前进。

three.js镜头追踪的移动效果相关推荐

  1. THREE.JS镜头随鼠标晃动效果

    为了让动画更灵活并且简单 借助gsap让其具有更多可能,在未来更容易扩充其他动效 gsap Dom跟随鼠标移动 gsap.quickTo() 首先要监听鼠标移动,并且将移动的值转换到 -1 和 1 之 ...

  2. 【加强版】js原生实现拖拽效果,这次没有用document的mousedown、mousemove、mouseup事件我们来点实际的(但是有个弊端:拖拽过程中鼠标会变成一个禁用符号,不太友好)

    <div class='dragged'></div> //初始化需要拖拽的列initDrags() {var arr = document.querySelectorAll( ...

  3. 【墙裂推荐】【原生基础版】js原生实现拖拽效果,注意不要忘了div的cursor用grab和grabbing 还是古法炮制、传统工艺的原生代码兼容性最好,推荐

    以下方式的劣势就是在放弃拖拽那一刻会触发click事件,通常如果被拖拽元素还有其他点击事件,会重复触发,往往并非业务需求.优势就是-额-貌似这段代码没什么屌优势! <div class='dra ...

  4. js进阶 13-6 jquery动画效果相关常用函数有哪些

    js进阶 13-6 jquery动画效果相关常用函数有哪些 一.总结 一句话总结:animate(),stop(),finish(),delat()四个. 1.stop()方法的基本用法是什么(sto ...

  5. video.min.js php,使用flv.js与video.js做一个视频直播效果

    这次给大家带来使用flv.js与video.js做一个视频直播效果,使用flv.js与video.js做出视频直播效果的注意事项有哪些,下面就是实战案例,一起来看一下. 环境配置 首先运行livego ...

  6. 纯JS制作的窗户雨滴效果

    今天本站推荐的代码是用JS制作的窗户雨滴效果,绚丽的效果不亚于FLASH,由于不知出处在哪,总而言之, 在此感谢作者的慷慨分享. function demo() { var engine = new ...

  7. 原生js实现canvas气泡冒泡效果

    说明: 本文章主要分为ES5和ES6两个版本 ES5版本是早期版本,后面用ES6重写优化的,建议使用ES6版本. 1, 原生js实现canvas气泡冒泡效果的插件,api丰富,使用简单 2, 只需引入 ...

  8. html制作翻页效果代码,使用原生JS实现滚轮翻页效果的示例代码

    一.滚轮事件 当用户通过鼠标滚轮与页面交互.在垂直方向上滚动页面时,就会触发mousewheel事件,这个事件就是实现全屏切换效果需要用到的.在IE6, IE7, IE8, Opera 10+, Sa ...

  9. php实现飘窗,JS实现网站图片飘窗效果,JavaScript悬浮广告(附详细代码)

    原标题:JS实现网站图片飘窗效果,JavaScript悬浮广告(附详细代码) JS实现网站图片飘窗效果,Java悬浮广告,郑州SEO提供以下代码,仅供参考: 飘窗效果-丁光辉博客(www.dinggu ...

最新文章

  1. OpenCV标准霍夫直线检测详解
  2. 深度学习笔记 第五门课 序列模型 第三周 序列模型和注意力机制
  3. css(float浮动和clear清除)
  4. 如何在Java 8中使用filter()方法
  5. laravel查询重复的数据_php – 使用Laravel Collection获取重复值
  6. Codeforces 405D 数学问题
  7. oracle hcm 发展,甲骨文发布Oracle HCM Cloud云服务 呈现三大亮点
  8. window环境下搭建SVN服务器
  9. 透明计算:对当前主流计算模式的革命
  10. React配置代理proxy解决跨域问题
  11. Linux多线程编程入门
  12. 卷积神经网络应用领域和基本结构
  13. Java for循环的几种用法详解(转载)
  14. 适合小白的LayaAir使用说明(创建laya的简易程序教程1.0)
  15. 加一度解析百度搜索困局,小程序将开启搜索流量新机遇
  16. jadx卡死解决方案
  17. 使用机器人工具箱在matlab上进行六轴机器人(6R)运动学建模【个人简记】
  18. 程序员口中常说的“组件”是什么?简洁易懂
  19. C/C++ 假币问题
  20. [svn]最常用、频用的10个命令

热门文章

  1. games101学习笔记_Cameras, Lenses and Light Fields
  2. 认识系统函数$test$plusargs与$value$plusargs
  3. 机器视觉打光所注意的光学知识
  4. java开发中elasticsearch 的简单使用
  5. Vmware ESXi检测服务器RAID和硬盘健康状态
  6. 渣本安卓客户端Android秋招总结(重排了字号)
  7. linux shell 指令 awk 是什么意思
  8. 常用计算机软件,常用计算机软件使用.ppt
  9. 【4002】通过html做一个注册的静态静态页面(不含css)。
  10. 视频太长怎么办?将一个长视频分割成2段的方法