封面图
上次说到了室内的漫游效果需要给相机架设一条轨道,这里就这个简单的示例来实现一个相机跟随轨道运动的基本功能。
基本要实现的效果如封面图所示,创建一个十字结的通道,然后相机在通道内按照通道的轨迹变换位置。
需要用到的知识
这里我们需要用到 THREE.TubeGeometry()
管道几何体和GrannyKnot
十字结。十字结是继承自Curve的子类。其源码如下:
class GrannyKnot extends Curve {
getPoint( t, optionalTarget = new Vector3() ) {
const point = optionalTarget;
t = 2 * Math.PI * t;
const x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t );
const y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t );
const z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t );
return point.set( x, y, z ).multiplyScalar( 20 );
}
}
我们可以用它建造一条十字结的曲线。
TubeGeometry中所需的参数如下:
- path: 需要继承THREE.Curve类
- tublarSegments: 沿曲线的线段数
- radius: 管道的半径
- radialSegments: 横截面中的分段数
- closed: 是否关闭管道
开始实现
在真正开始实现之前,我们依旧要先创建场景、相机、渲染器这些基本的东西。
// 场景
const scene = new THREE.Scent()
// 相机
const camera = new THREE.PerspectiveCamera(60, (window.innerWidth / window.innerHeight), 1, 1000)
camera.position.set(0, 4, 57)
camera.lookAt(0, 1.5, 0);
// 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, })
renderer.setSize(window.innerWidth, window.innerHeight);
OrbitDom.value.appendChild(renderer.domElement)
因为我是用vue3j进行开发,所以在这个组件中定义了一个OrbitDom
的ref ,用来承载渲染器的dom,也就是canvase。
此时我们刷新界面,发现界面上什么都没有,如图:
这里有个辅助线,是因为我在场景中添加了坐标轴的辅助器。
// 坐标辅助线
const axesHelper = new THREE.AxesHelper(500)
scene.add(axesHelper)
因为我们相机设置的lookAt为0,1.5,0
所以看起来就是这种效果。
接下来我们用GrannyKnot创建一条十字结曲线。
// 十字结曲线
const curve = new GrannyKnot()
console.log('curve', curve)
我们可以在控制台简单查看一下该曲线,它是一个继承自Curve的GrannyKnot对象。
曲线并不会直接展示到场景中,我们需要用到TubeGeometry
这个几何体,TubeGeometry
可以沿着threejs 中的曲线创建一条管道,代码如下:
const curve = new GrannyKnot()
console.log('curve', curve)
const geometry = new THREE.TubeGeometry(
curve, 100, 2, 8, true
)
const material = new THREE.MeshBasicMaterial({
color: 0xffffff,
wireframe: true,
side: THREE.DoubleSide
})
const tube = new THREE.Mesh(geometry, material)
scene.add(tube)
这时候刷新界面,我们就可以得到下面的效果:
接下来我们需要让相机沿着这个通道不断的变换位置,我们定义一个方法moveCamrea
,然后在该方法中计算相机的位置:
// 移动相机
const clock = new THREE.Clock()
const moveCamera = () => {
const time = clock.getElapsedTime()
const looptime = 100
const t = (time % looptime) / looptime
const t2 = (time + 0.1) % looptime / looptime
const pos = tube.geometry.parameters.path.getPointAt(t)
const pos2 = tube.geometry.parameters.path.getPointAt(t2)
camera.position.copy(pos)
camera.lookAt(pos2)
}
Clock
是THREEjs中核心方法中的时钟对象,你可以理解为它用来保持对时间的一个记录。
getElapsedTime
这个方法用来获取时钟启动后经过的秒数,并将.oldTime
属性设置为当前时间。如果.autoStart
属性为true
且时钟未运行,则也会启动时钟。
我们定义time 用来获取当前时间,设置循环时间为100,然后定义
const t = (time % looptime) / looptime
当前时间节点,用来计算相机当前所在的位置的时间点
接下来定义t2
const t2 = (time + 0.1) % looptime / looptime
来定义相机要看向的位置的时间点
然后通过getPointAt
方法来获取两个时间点的位置。
const pos = tube.geometry.parameters.path.getPointAt(t)
const pos2 = tube.geometry.parameters.path.getPointAt(t2)
最后我们设置相机的位置,和lookAt()的坐标。
camera.position.copy(pos)
camera.lookAt(pos2)
这样,我们就基本完成了移动相机的方法。但是此时我们还不能达到封面图中的效果,我们需要一个动画来不断的更新相机的位置。
这就要用到requestAnimationFrame
方法,我们定义update方法:
const update = () => {
requestAnimationFrame(update)
moveCamera()
renderer.render(scene, camera)
}
在update方法中调用moveCamera
并且让renderer
重新渲染,此时,执行update
方法,我们就可以得到封面中的效果。
即:相机按照预定的十字结曲线进行拍摄
最后
这里用到的知识如下:
- TubeGeometry 管道几何体
- GrannyKnot 曲线
- Clock 时间轨迹
- 以及如何计算相机的位置
谢谢大家~