头图

海上生明月,天涯共此时。又是一年中秋时,回想上一次赏月已是那遥远的童年时光,忙碌使我们忘却了假日应有的舒缓。今天在这假期即将开始的时候,让我们用代码来过个节。

今天的主题是基于threejs画出月球环绕地球运动的效果,并增加飞跃星空的感觉,如封面图所示。

球体绘制

首先绘制出地球和月球,基于SphereBufferGeometry类绘制三维球体,参数如下:

参数描述
radius该属性定义球体的半径,默认值是50
widthSegments该属性指定竖直方向上的分段数,段数越多,球体的表面越光滑,默认值是8,最小值是3
heightSegments该属性指定水平方向上的分段数,段数越多,球体的表面越光滑,默认值是6,最小值是2
phiStart该属性用来指定从x轴的什么位置开始绘制,取值范围是0到2*π,默认值0
phiLength该属性用来指定从phiStart开始画多少,默认2*π(画整球)
thetaStart该属性用来指定从y轴的什么位置开始绘制,取值范围是0到2*π,默认值0
thetaLength该属性用来指定从thetaStart开始画多少,默认2*π(画整球)

首先在网上找一张地球平面图,此处只需要用到前面三个参数即可new THREE.SphereBufferGeometry(10, 50, 50),效果图如下:

月球图类似,只是会相对地球绘制的球体会小一些。

球体动画

然后是给两个球体增加动画的效果,地球本身的自转效果,月球围绕地球环绕运动的效果。

地球自转效果比较简单,只需要不断修改地球实例的y轴的值即可。

planet.rotation.y += 0.002;

月球比较复杂,除开自转的同时还需增加一个环线运动效果,这里增加一个t值,默认为0,在动画运动函数中不断增加t值,并结合数学三角函数实现环绕效果。

moon.rotation.y -= 0.007;
moon.position.x = 15 * Math.cos(t) + 0;
moon.position.z = 20 * Math.sin(t) - 35;
t += 0.015;

最终动画的效果如下所示:

星空轨迹

只有两个球体运动相对单调,所以再增加星空运动的轨迹提示氛围感。由于星星是不断运动飞出屏幕之外的,所以我们要有重新绘制的机制。

绘制星星

let lineTotal = 1000;
let linesGeometry = new THREE.BufferGeometry();
linesGeometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(6 * lineTotal), 3));
linesGeometry.setAttribute("velocity", new THREE.BufferAttribute(new Float32Array(2 * lineTotal), 1));
let l_positionAttr = linesGeometry.getAttribute("position");
let l_vertex_Array = linesGeometry.getAttribute("position").array;
let l_velocity_Array = linesGeometry.getAttribute("velocity").array;

for (let i = 0; i < lineTotal; i++) {
    let x = THREE.MathUtils.randInt(-100, 100);
    let y = THREE.MathUtils.randInt(10, 100);
    if (x < 7 && x > -7 && y < 20) x += 14;
    let z = THREE.MathUtils.randInt(0, -300);

    l_vertex_Array[6 * i + 0] = l_vertex_Array[6 * i + 3] = x;
    l_vertex_Array[6 * i + 1] = l_vertex_Array[6 * i + 4] = y;
    l_vertex_Array[6 * i + 2] = l_vertex_Array[6 * i + 5] = z;

    l_velocity_Array[2 * i] = l_velocity_Array[2 * i + 1] = 0;
}
let starsMaterial = new THREE.LineBasicMaterial({
    color: "#ffffff",
    transparent: true,
    opacity: 0.5,
    fog: false
});
let lines = new THREE.LineSegments(linesGeometry, starsMaterial);
linesGeometry.getAttribute("position").setUsage(THREE.DynamicDrawUsage);
scene.add(lines);

增加动画及重置

for (let i = 0; i < lineTotal; i++) {
    l_velocity_Array[2 * i] += 0.0049;
    l_velocity_Array[2 * i + 1] += 0.005;

    l_vertex_Array[6 * i + 2] += l_velocity_Array[2 * i];
    l_vertex_Array[6 * i + 5] += l_velocity_Array[2 * i + 1];

    if (l_vertex_Array[6 * i + 2] > 50) {
        l_vertex_Array[6 * i + 2] = l_vertex_Array[6 * i + 5] = THREE.MathUtils.randInt(-200, 10);
        l_velocity_Array[2 * i] = 0;
        l_velocity_Array[2 * i + 1] = 0;
    }
}

星空背景动画

正常的星空背景都会有一些星云,在此基础上再增加一张背景图的运动使之更加真实。星空背景本质也是一个球体,只是半径相对较大,肉眼看不出来。

const textureSphereBg = loader.load('https://xxx.jpg');
textureSphereBg.anisotropy = 16;
const geometrySphereBg = new THREE.SphereBufferGeometry(50, 32, 32);
const materialSphereBg = new THREE.MeshBasicMaterial({
    side: THREE.BackSide,
    map: textureSphereBg,
    fog: false
});
sphereBg = new THREE.Mesh(geometrySphereBg, materialSphereBg);
sphereBg.position.set(0, 50, 0);
scene.add(sphereBg); 

然后在整体的动画函数中修改不同方向的数值,使之感觉游荡在太空中的感觉。

sphereBg.rotation.x += 0.002;
sphereBg.rotation.y += 0.002;
sphereBg.rotation.z += 0.002;

最后

整体实现功能就结束了,里面用到大量的threejs的相关API,有兴趣的同学可以去研究研究,最后的完整代码如下:

https://code.juejin.cn/pen/71...

参考

https://codepen.io/isladjan/p...

看完如果觉得有趣,记得点赞收藏起来。说不定哪天就用上啦~
最后祝大家中秋快乐,假期玩得开心~

专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)


南城FE
2.2k 声望574 粉丝