当前位置:网站首页>使用 Three.js 实现'雪糕'地球,让地球也凉爽一夏
使用 Three.js 实现'雪糕'地球,让地球也凉爽一夏
2022-07-01 21:47:00 【前端技术站】

前言
最近的天气有几分酷热,去实验室的道路也有几分漫长,走着走着,小包感觉灵魂已经被热出窍了。回到实验室,把空调打开,雪糕吃上,静坐了几分钟,才重新感觉到灵魂的滋味,葛优躺在实验室的小床上,思维开始天马行空,世上有一万种方式能让小包凉快,但地球母亲呐,她却日渐炎热,谁能来给她降降温?
躺着躺着,进入了梦乡,小包梦到未来有一天,人类超级发达,可以穿梭时空,但发展的代价也是巨大的,地球母亲不堪重负,热度超标,我们却束手无策,科学家最后想出一个古老的办法,将地球的一周用冰包裹起来,进行物理降温。这很让人惊悚,小包醒来后,枯坐了一会,决定做一个雪糕地球,不只是一种整活调侃,也是一种反思与警示,保护地球,人人有责。
在线体验(支持PC与移动端): 雪糕地球线上预览
源码仓库: 雪糕地球
分享一款面试题库 web前端面试题库 VS java后端面试题库大全

ThreeJS 基础——实现转动的球体
Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,大家或多或少应该都见识过 Three 的传说。这是小包第一次使用 Three,因此小包会围绕雪糕地球实现的各种细节讲起。
下面首先来看一下 Three 框架的基本组成要素(图源: Three.js 教程)

Three 中最重要的三个对象即场景、相机和渲染器。场景即放置模型、光照的场地;相机设置以何种方式何种角度来观看场景,渲染器将效果渲染到网页中。这三个概念都不难理解,下面我们用代码实现这三个对象。
// 场景const scene = new THREE.Scene();// 透视相机const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000);// 渲染器const renderer = new THREE.WebGLRenderer();// 设置渲染区域尺寸renderer.setSize(window.innerWidth, window.innerHeight);// body元素中插入canvas对象document.body.appendChild(renderer.domElement);// 设置背景颜色renderer.setClearColor("hotpink");// 执行渲染操作 指定场景、相机作为参数renderer.render(scene, camera);Three 中有多种相机,本文章主要使用透视相机(PerspectiveCamera),其原理与人眼所看的景象类似,共有四个参数:
PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )fov: 表示能看到的角度范围,值为角度,类似于人的视角。aspect: 表示渲染窗口的长宽比,如果网页中只有一个canvas,其值通常设置为网页视口的宽高比near/far:near/far分别代表摄像机的近剪切面和远剪切面
文字有些难以理解,可以参考一下下图:

打开浏览器,看一下会渲染出什么?目前只能看到全粉色的网页,这是因为目前的场景中并没有添加 3D 模型对象。
接下来我们来添加一个球体模型,作为地球的基底。
const cRadius = 100;const geometry = new THREE.SphereBufferGeometry(
cRadius,
cRadius * 6.4,
cRadius * 6.4);SphereBufferGeometry 是 Three 中实现球体的 API,参数非常多,这里只介绍前三个参数
radius: 球体半径widthSegments: 沿经线方向分段数heightSegments: 沿纬线方向分段数
为球体添加材质 Material,目前我们只添加一个颜色属性。
// 材质对象Materialconst material = new THREE.MeshLambertMaterial({ color: 0x0000ff,
});渲染网格体 Mesh,并将其添加到场景 Scene 中。
// 网格体 Mesh,两个参数分别为几何体和材质const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);重新打开网站,并没有看到球体,还是一片粉茫茫的寂寥,天理何在?

Three 相机的初始位置默认为 (0,0,0),相机焦点默认为 Z 轴负半轴方向,球体的半径是 100,也就是说目前相机位于球体内部,因此我们需要调整相机位置。
// 设置相机的位置camera.position.set(0, 0, 220);// 设置相机焦点的方向camera.lookAt(scene.position);当当当当,网页中就可以成功看到一个黑色球体了,额有点奇怪,我们明明设置的是 0x0000ff 颜色,怎么会显示一个黑色模型?

小包苦思冥想: 万物本没有颜色,颜色是光的反射。在整个场景中,目前是没有光源的,因此下面分别添加平行光(DirectionalLight)和点光源(PointLight)
平行光是沿着特定方向发射的光,其表现类似无限远的阳光,文章使用它来模拟太阳光。点光源是从一个点向各个方向发射的光源,使用它来增加整体的亮度。
// 声明平行光const light = new THREE.DirectionalLight();// 设置平行光源位置light.position.set(0, 0, 1);// 将平行光源添加到场景中scene.add(light);// 声明点光源const point = new THREE.PointLight(0xeeeeee);// 设置点光源位置point.position.set(400, 200, 300);// 点光源添加到场景中scene.add(point);
立体效果看起来不明显,没事,接下来我们让球体动起来。接下来,给球体添加一个 z 轴和 y 轴的转动。
const createAnimRotation = () => { const speed = 0.005;
sphere.rotation.z += speed / 2;
sphere.rotation.y += speed;
};const render = () => {
createAnimRotation();
renderer.render(scene, camera);
requestAnimationFrame(render);
};
render();由于球体是对称的,转动看起来并不明显,如果你特别想看到转动效果,可以将 SphereBufferGeometry 暂时更换为 BoxBufferGeometry。
ThreeJS 纹理——实现转动的地球
上文已经成功实现地球,接下来我们来为地球披上衣服。本文实现的是雪糕地球,因此小包直接为其披上雪糕外衣。
Three 可以将一张纹理图映射到几何体上,具体的映射原理我们不做探究,映射的思想可以参考下图。

选取一张雪糕地球的纹理图,使用下面的代码实现纹理贴图效果。

// 纹理加载器对象const textureLoader = new THREE.TextureLoader();const textureSurface = textureLoader.load( "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg");// 设置纹理贴图const material = new THREE.MeshLambertMaterial({ map: textureSurface });
只使用普通贴图的雪糕地球看起来已经非常不错了,但还有进一步美化的空间,Three 提供了高光贴图,使用高光贴图,会有高亮部分显示。
const textureSpecular = textureLoader.load( "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg");const material = new THREE.MeshPhongMaterial({ map: textureSurface, specularMap: textureSpecular, shininess: 80, // 高光部分的亮度});
虽然动图录制的帧数太低,还是依稀可以看到一些高亮区域。
Three 还提供了环境贴图,环境贴图可以增加表面的细节,使三维模型更加立体。
const textureElevation = textureLoader.load( "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg");const material = new THREE.MeshPhongMaterial({ map: textureSurface, normalMap: textureElevation, specularMap: textureSpecular, shininess: 80,
});立体效果是有了,但是具体看起来一言难尽,颜色有些许暗淡,不符合雪糕的风格。

小包继续开始查看文档,环境贴图中有 normalScale 属性,可以设置颜色的深浅程度,减少对应属性值为 0.5,0.5。
sphere.material.normalScale.set(0.5, 0.5);
交互式雪糕地球
给地球加一些交互效果:
当鼠标靠近,地球放大;鼠标远离时,地球缩小
地球随鼠标方向转动
上述动效我们可以通过移动相机位置实现。首先设定地球旋转的最大正负角度为 270。
// 定义动效对象let mouseX = 0;let mouseY = 0;const moveAnimate = { coordinates(clientX, clientY) { const limit = 270; const limitNeg = limit * -1;
mouseX = clientX - window.innerWidth / 2;
mouseY = clientY - window.innerHeight / 2;
mouseX = mouseX >= limit ? limit : mouseX;
mouseX = mouseX <= limitNeg ? limitNeg : mouseX;
mouseY = mouseY >= limit ? limit : mouseY;
mouseY = mouseY <= limitNeg ? limitNeg : mouseY;
}, onMouseMove(e) {
moveAnimate.coordinates(e.clientX, e.clientY);
},
};document.addEventListener("mousemove", moveAnimate.onMouseMove);通过上述事件计算出 mouseX 与 mouseY 的值,在 render 函数中,修改 camera 的位置。
camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;
camera.position.y += (mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
移动端同步监听 touchmove 事件,手机也可以看到雪糕地球的动态效果。
const moveAnimate = { onTouchMove(e) { const touchX = e.changedTouches[0].clientX; const touchY = e.changedTouches[0].clientY;
moveAnimate.coordinates(touchX, touchY);
},
};document.addEventListener("touchmove", moveAnimate.onTouchMove);添加 loading 效果
纹理的加载需要一定的时间,因此添加一个转场 loading 效果。
loading 效果使用小包前面的实现跃动的文字中的效果。
.loader { display: flex; color: white; display: flex; justify-content: center; align-items: center; font-size: 5em; width: 100%; height: 100%; font-family: "Baloo Bhaijaan", cursive;
}.loader span { text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px
transparent, 0 7px transparent, 0 8px transparent, 0 9px transparent, 0
10px 10px rgba(0, 0, 0, 0.4); text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px
#bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb, 0 50px 25px rgba(0, 0, 0, 0.2); transform: translateY(-20px);
}Three 提供了 LoadingManager,其功能是处理并跟踪已加载和待处理的数据。当所有加载器加载完成后,会调用 LoadingManager 上的 onLoad 事件。
因此我们定义一个 LoadingManager,当触发 onLoad 事件后,将页面中的加载标志移除。
const loadingScreen = { scene: new THREE.Scene(), camera: new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000
), // 移除加载标志的函数
removeText() { const loadingText = document.querySelector("#canvas-loader"); if (loadingText.parentNode) {
loadingText.parentNode.removeChild(loadingText);
}
},
};// 初始化加载器let loadingManager = new THREE.LoadingManager();// 监听加载器 onLoad 事件loadingManager.onLoad = () => {
loadingScreen.removeText();
isLoaded = true;
};// 纹理图加载器传入 loadingManagerconst textureLoader = new THREE.TextureLoader(loadingManager);参考链接
Three中文文档
后语
我是 战场小包 ,一个快速成长中的小前端,希望可以和大家一起进步。
如果喜欢小包,可以在 掘金 关注我,同样也可以关注我的小小公众号——小包学前端。
一路加油,冲向未来!!!
疫情早日结束 人间恢复太平
分享一款面试题库 web前端面试题库 VS java后端面试题库大全

边栏推荐
猜你喜欢

Dark horse programmer - software testing - stage 06 2-linux and database-01-08 Chapter 1 - description of the content of the Linux operating system stage, description of the basic format and common fo

Kubernetes创建Service访问Pod

Mysql——》MyISAM存储引擎的索引

对象内存布局

GenICam GenTL 标准 ver1.5(4)第五章 采集引擎

Microsoft, Columbia University | Godel: large scale pre training of goal oriented dialogue

Slope compensation

【juc学习之路第9天】屏障衍生工具

Compensation des créneaux horaires
![比较版本号[双指针截取自己想要的字串]](/img/19/4f858ffdc1281d6b8b18a996467f10.png)
比较版本号[双指针截取自己想要的字串]
随机推荐
Burpsuite simple packet capturing tutorial [easy to understand]
Redis配置与优化
Redis configuration and optimization
Show member variables and methods in classes in idea
详解ThreadLocal
Internet of things RFID, etc
LC501. 二叉搜索树中的众数
QT版本华睿相机的Demo程序实现
恶意软件反向关闭EDR的原理、测试和反制思考
互联网的智算架构设计
IDA动态调试apk
QT uses ffmpeg4 to convert the qimage of ARGB to yuv422p
【MySQL】索引的分类
RestTemplate 远程调用工具类
Basic knowledge of ngnix
Clean up system cache and free memory under Linux
Is PMP certificate really useful?
Separate the letters and numbers in the string so that the letters come first and the array comes last
Ida dynamic debugging apk
【MySQL】数据库优化方法