达到效果

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

实现思路

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

实现难点

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. 图像拼接--Coarse-to-fine Seam Estimation for Image Stitching
  2. CTFshow php特性 web107
  3. NLP:利用DictVectorizer对使用字典存储的数据进行特征抽取与向量化
  4. SAP R/3 中会计凭证和物料凭证的对应关系
  5. Android 数据库(SQLite)【简介、创建、使用(增删改查、事务、实战演练)、数据显示控件(ListView、Adapter、实战演练-绿豆通讯录)】
  6. 深度学习模型大合集:GitHub 趋势榜第一,已斩获 8000+ 星
  7. swingworker_使用SwingWorker的Java Swing中的多线程
  8. centos源码安装PHP
  9. MasterPage 小谈
  10. Ibator生成iBATIS配置文件 DO及DAO操作记录
  11. python eval函数_Python基础元素语法总结
  12. python基础之socket
  13. matlab中fittype函数,fittype拟合函数
  14. ubuntu14.04中文楷体变默认字体
  15. mysql数据库用户名修改密码_如何修改mysql数据库中的用户名和密码
  16. 淘客基地免费商城CMS增加拾牛APP下载公告
  17. 搜狗老域名作用之快速大量搜狗收录
  18. Android之获取手机内部及sdcard存储空间
  19. 谷粒学苑 —— 9、课程管理:课程列表
  20. [安装fastfds中的nginx执行make命令报错]src/core/ngx_murmurhash.c:37:11: error

热门文章

  1. 银河证券集中备份系统改造应用
  2. 自定义header-php跨域
  3. 打造爆款内容的7字真言
  4. java开发中elasticsearch 的简单使用
  5. GIF图片裁剪出指定大小的GIF图片
  6. 魅族16支持html吗,魅族 16可以无线充电吗?
  7. 专业存储设备Yottachain芝麻云节点服务器泛圈科技专业矿机
  8. 含泪整理最优质手绘/插画矢量图素材,你想要的这里都有
  9. 企业电子招标采购系统源码之电子采购方案:构建高效智能数字化采购
  10. 2021-2027全球及中国康复理疗设备行业研究及十四五规划分析报告