简介:HTML+Three.js旋转地球源码是一份面向Web前端开发者和Three.js初学者的可直接运行示例,用于在网页中快速构建并展示三维旋转地球模型,帮助开发者免去从零搭建3D场景的繁琐步骤。压缩包采用RAR格式,共5个文件:2个JavaScript脚本负责页面中的核心3D渲染逻辑(包含Three.js库与主程序),1个HTML页面承载页面结构,1个CSS文件控制页面布局与样式,1张JPG图片作为地球表面材质贴图,整体仅957KB,体积小巧、结构清晰。目前已有1351人学习下载。代码将三维场景、摄像机、球体模型、材质贴图与旋转动画完整串联,适合初学者对照理解Three.js的基础流程,包括场景搭建、几何体创建、纹理映射以及动画循环等关键环节;同时具备良好的复用性,可方便地移植到需要地球模型的前端项目中,节省开发调试时间。配套的地球贴图材质更能帮助理解贴图坐标与球面渲染的对应关系,对正在研究Three.js或需要快速集成地球效果的开发者来说,是一份不错的参考。 做 Web 可视化的时候,想在页面上放一个能转的地球,第一个反应就是 Three.js。这个“HTML+Threejs旋转地球源码,含地球材质贴图”的项目,其实就是把那套 3D 地球最核心的玩法抽出来:一个 HTML 文件、一个 Three.js 的库、一张地球贴图,拼出一个自带光照、能自动旋转、还能适配屏幕的地球。源码拿到手之后,不用装环境、不用跑构建,双击就能看到效果。这篇博文我就拿自己的实操过程来讲讲这个项目怎么玩,踩过哪些坑,怎么改出自己想要的效果。适合刚开始接触 Three.js 的网页前端,也适合项目里要快速加一个“地球”做视觉元素的开发者。
1. 先把旋转地球的骨架想清楚
1.1 为什么选 Three.js 而不是 CSS 或原生 Canvas
做旋转地球这件事,我见过有人用 CSS 的transform: rotateY()配合平面图硬转,成品看起来就像一张图片在翻面,没有球面的立体感;也有人用原生 Canvas 配合数学计算把经纬度投影成球面,能把轮廓画出来,但一旦要加光照、阴影、纹理畸变矫正,代码量就压不住了。Three.js 的优势在于它把 WebGL 底层逻辑封装成了“场景、相机、网格、材质”这一套面向对象模型,思考方式更接近美术做三维建模:先搭台子,再放物体,再打光。
回到这个项目本身,核心就三件事:
- 一个
Scene(场景),用来装所有 3D 对象; - 一个
PerspectiveCamera(透视相机),决定用户从哪个角度观察; - 一个
WebGLRenderer(渲染器),把三维场景画到<canvas>上。
再往场景里放地球模型,也就是一个SphereGeometry球体,外表面贴一张世界地图纹理,补一个方向光模拟太阳照射,最后用一个requestAnimationFrame循环让地球绕 Y 轴旋转。这个架构可以扩展到任何 Three.js 3D 项目,不只是地球,做产品展示、数据可视化、3D 模型预览,套路都是这一套。
1.2 项目目录与运行方式
这个项目不需要 Node.js,也不需要 webpack,解法就是把 Three.js 通过<script>标签引入。我用的是 CDN 方式,加载three.min.js的 UMD 版本,挂到全局的THREE对象上。这样最省事,复制源码就能跑。
运行方式就两种:
- 本地双击 HTML 文件:大多数情况下能跑,但注意浏览器对
file://协议下加载本地资源有限制,详见后面常见问题部分。 - 起一个本地静态服务:推荐用 VS Code 的 Live Server,或者 Python 的
python -m http.server 8080,把页面放在服务根目录下,访问localhost:8080即可。
文件结构上,我习惯把贴图单独放在assets/目录里,HTML 放根目录,这样后面想换纹理、加天空盒子、加云层贴图时,不用改动目录结构。
2. 核心细节:贴图、材质与光照的配合
2.1 地球贴图从哪里来
“含地球材质贴图”是这个项目最实在的价值点。地球贴图本质上是一张**等距圆柱投影(Equirectangular Projection)**的平面图,就是你在平面世界地图上看到的那种方方正正的样式。Three.js 的SphereGeometry会把这纹理按 UV 坐标包到球体上,很像把一张长方形纸包在一个球外面。
贴图选择建议:
- 分辨率:市面上常见的成品地球贴图有 1K、2K、4K 之分。网页项目里 2K 足够,4K 只有在拍特写镜头、拉近看岛屿轮廓的时候才有意义,但纹理显存占用和整体性能压力也会上去一个级别。
- 格式:JPG 够用,体积小;如果追求大陆边缘文字清晰度、或者需要透明通道的图层,用 PNG。
- 来源:NASA 官网有不少公开的地球卫星影像,Three.js 官方仓库的
examples/textures/planets/目录下面也有现成的贴图资源。商用项目用之前先确认授权。
这里有个坑要专门说一下:贴图的 UV 接缝问题。如果你的贴图是从左到右的一个连续世界平面图,那么当它包到球体上,左边缘和右边缘会形成一条拼接缝。Three.js 里可以通过设置texture.wrapS = THREE.RepeatWrapping减少接缝痕迹,但如果你是在预览一个贴图品质很高、边缘又非常明显的素材,最好的办法是换一张左右无缝的贴图。
2.2 材质的取舍:Phong 还是 Standard
Three.js 里最常用的球体材质有两个:MeshPhongMaterial和MeshStandardMaterial。
项目源码里我用的是MeshPhongMaterial,原因很直接:
- 场景就一个球,不需要 PBR 那套粗糙度、金属度参数;
- Phong 材质光照计算快,对手机端 GPU 压力小;
- 要模拟的地球表面(陆地、海洋)本质是漫反射为主,Phong 的表现力已经足够。
如果你想要更加“真实”的地球,比如海洋有明显的镜面反射(像卫星照片里阳光打在海上那一块白亮的区域),可以换成MeshStandardMaterial,然后把roughness调低到 0.3 左右,metalness设为 0。这样海水会有一层光泽感,代价是渲染变慢。对照页面上有没有其他特效,再决定要不要换。
2.3 光源怎么布置才自然
一个纯贴图的地球,如果没有光照,看起来就是你贴了一张“图”在球面上,扁平的,没有立体感。光源决定了明暗交界线,看起来才像个球。
实际测试下来,一组光源的搭配效果最好:
| 光源类型 | 颜色/强度 | 作用 |
|---|---|---|
AmbientLight | 0x333333,弱一点 | 保证背光面不至于全黑 |
DirectionalLight | 0xffffff,强度 1.0 | 模拟太阳平行光方向,产生明暗过渡 |
这里有个反直觉的点:环境光不要加太亮。很多人为了让球体更清晰,把环境光加到0xffffff,结果整个球被“洗”得很平,立体感立刻消失。环境光加暗色,比如0x404040都行,方向光负责制造对比,才是一个“球”。
方向光的位置也很讲究。我习惯放在(5, 3, 5)附近,这样光从右前上方打过来,地球的左上部分会有阴影,右下部分更亮,看起来最有立体感。如果放在正前方(0,0,5),整个球没有明暗面,又变回一张贴图了。
3. 实操:手写一个完整的旋转地球
3.1 页面基础结构与样式
先看 HTML 结构。body里不需要放任何 DOM 元素,Three.js 的渲染器会帮我们生成一个<canvas>,直接塞到页面里。页面的样式做两件事:全屏展示,去掉滚动条。
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Three.js 旋转地球</title> <style> * { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; overflow: hidden; background: #000; } canvas { display: block; } </style> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="./main.js"></script> </body> </html>版本这里我说一下为什么用r128而不是最新的r16x:r128 是 UMD 版本,用 script 标签引入最简单,代码写法和一些 API 名称也足够稳定,全项目 demo 完全够用。新版本的 Three.js 走向 ES Module 化,引入方式更复杂,还要借助 import map,对只是想快速做一个旋转地球的朋友来说没必要。
3.2 创建场景、相机与渲染器
这部分是 Three.js 项目的“铁三角”,顺序不能乱:场景负责装东西,相机负责看东西,渲染器负责画出来。
// 场景 const scene = new THREE.Scene(); // 相机 const camera = new THREE.PerspectiveCamera( 45, // 视角,越大看到的东西越多,透视越夸张 window.innerWidth / window.innerHeight, // 宽高比 0.1, // 近裁剪面 1000 // 远裁剪面 ); camera.position.z = 4; // 相机往后拉,让地球完整落在视野里 // 渲染器 const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement);视角45是模拟人眼的常规数值,z轴距离4是在半径1的球体下比较合适的观看距离。如果你把球体缩放成2,那相机距离要拉到6以上,否则球会溢出画面。
3.3 添加地球球体与纹理
这一步就是项目标题里的重点:创建球体几何体,加载贴图,套上材质,生成网格,加入场景。
const geometry = new THREE.SphereGeometry(1, 64, 64); const textureLoader = new THREE.TextureLoader(); const texture = textureLoader.load('./assets/earth.jpg'); const material = new THREE.MeshPhongMaterial({ map: texture, shininess: 25 // 高光强度适中,模拟海面轻微反光 }); const earth = new THREE.Mesh(geometry, material); scene.add(earth);这里有两个参数值得写清楚:
SphereGeometry(1, 64, 64):半径 1,宽度分段 64,高度分段 64。分段数越多,球面越平滑,三角形数量越多。默认的 32 段在较新设备上边缘已经会有轻微的多边形感,64 段是性能和视觉的平衡点。改到 128 段会在球体快速的边缘线条上显得更圆滑,但没必要。textureLoader.load的第二个参数:可以传一个回调函数,在纹理加载完成后执行,比如给页面加一点淡入效果。
贴图本身也会影响观感。光源、材质参数这种影响是全局的,但如果你拿到的贴图是“黑底星空图”、“带大气层的透明图”,那效果就完全不同了。所以我建议拿到一张新贴图之后,先用最简单的方式加载出来看一眼,再做其他特效叠加。
3.4 让地球转起来
旋转动画用的是requestAnimationFrame。一个容易被忽略的问题:每一帧地球转动的角度应该是固定的吗?如果固定写成earth.rotation.y += 0.005,那么 60Hz 的显示器上地球转速是0.3弧度/秒,而在 144Hz 的显示器上转速会变成0.72弧度/秒,转得明显快很多。这就是“帧率挂勾”问题。
解决办法是引入时间增量delta,Three.js 给我们提供了现成的THREE.Clock:
const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); earth.rotation.y += 0.4 * delta; // 每秒 0.4 弧度,转一圈约 15.7 秒 renderer.render(scene, camera); } animate();每秒0.4弧度的转动速度,视觉效果是比较舒服的:不会慢到像静止,也不会快到像陀螺仪。想要慢节奏一点的展示,可以改成0.15。
3.5 完整的快速可跑源码
把上面的片段拼起来,再加个自适应窗口的resize监听,就是一份完整可运行的旋转地球源码。这份代码我建议你就当作一个基础模板,后面所有升级都在这上面改:
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 4; const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); const ambient = new THREE.AmbientLight(0x404040); scene.add(ambient); const sun = new THREE.DirectionalLight(0xffffff, 1.0); sun.position.set(5, 3, 5); scene.add(sun); const geometry = new THREE.SphereGeometry(1, 64, 64); const texture = new THREE.TextureLoader().load('./assets/earth.jpg'); const material = new THREE.MeshPhongMaterial({ map: texture, shininess: 25 }); const earth = new THREE.Mesh(geometry, material); scene.add(earth); const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); earth.rotation.y += 0.4 * delta; renderer.render(scene, camera); } animate(); window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });到这里,一个旋转地球已经跑起来了。但如果你想把这种“旋转展示”加到现有页面上,或者希望地球有一个初始角度、可以看到亚洲或者美洲的正面,那还需要做点小调整。比如把地球的经纬度旋转拆开:rotation.x控制南北倾角,rotation.z控制轴线倾角,rotation.y控制自转。我最常用的初始画面是让中国在正面,这样演示的时候更亲切。
4. 常见问题与排查技巧实录
实操过程中你一定会遇到各种问题。我把自己在跑这个项目时踩过的、以及帮别人排查过的典型问题整理了一下,按出现频率排序。
4.1 页面黑屏或只显示背景色
黑屏是最高频的问题。无非三种原因:
| 现象 | 原因 | 处理方式 |
|---|---|---|
| 页面全黑,看不到地球 | 相机看不到球 | 检查camera.position.z,距离球体太近时被裁剪掉;检查球体是否加入了场景 |
| 地球在但很暗淡 | 缺少光源或光源设置不对 | 加上AmbientLight和DirectionalLight |
| 只有黑背景 | 贴图路径 404 | 打开控制台看 Network 面板,确认earth.jpg路径是否正确 |
贴图加载失败其实不是最明显的报错,因为图片请求失败不会让页面崩掉,只会在控制台留下一条资源请求日志。这时候地球会呈现出默认的白色材质,如果你没注意看,会以为“球没渲染出来”。
4.2 地球边缘有锯齿
Three.js 里所谓的“锯齿问题”,在 r185 版本附近讨论热度很高,其实根源一致:WebGL 默认的抗锯齿机制是 MSAA,开启方式是在创建渲染器时传{ antialias: true }。如果你创建时没开,后面改renderer.antialias是没有用的,只能重新初始化渲染器。
另外还有一个影响清晰度的重点是setPixelRatio。不设置的话,Mac 的 Retina 屏上 1 个 CSS 像素对应 2 个物理像素,画面会发糊;但直接设置为设备像素比也可能导致 GPU 负载过高。我惯用Math.min(window.devicePixelRatio, 2),把上限压住,兼顾清晰度和性能。
如果你用了后处理脚本或者自定义着色器,MSAA 通常会失效,锯齿又会冒出来。这时候可以考虑叠加渲染分辨率,比如renderer.setPixelRatio(2)强制超采样,能有效缓解。
4.3 贴图“接缝”或者“贴反了”
贴图包到球体上之后,如果看到:
- 有明显的左右接缝线:说明贴图左右边缘在球面上是断开的两条线,可以通过
texture.wrapS = THREE.ClampToEdgeWrapping配合重复采样的方式规避,但根治方法还是换一张做好的无缝贴图。 - 上下颠倒:经纬度投影贴图通常是北在上、南在下,如果颠倒了说明贴图本身是“南极在上”的图,可以用
texture.rotation = Math.PI配合texture.center设置来解决。 - 地图位置不对,正面不是你想要的大陆:在场景初始化时直接设置
earth.rotation.y为对应的弧度值。比如想让本初子午线在正前方,把rotation.y设为-Math.PI / 2附近。
4.4 在低端设备上发烫、掉帧
旋转地球是一个持续的动画循环,即使你不操作它,也在不间断渲染,GPU 一直在干活。低配手机上如果转起来比较吃力,优先做三件事:
- 把球体分段从
64降到48,三角形数量减少将近一半; - 把
renderer.setPixelRatio上限从2降到1.5; - 把贴图从 4K 压缩到 2K,纹理加载和显存占用都会明显下降。
掉帧还有一种情况是页面里同时开了很多动画,比如背景粒子、云层、星空贴图等,多个 Canvas 叠加会让帧率雪崩。两个选择:把所有动画放到同一个渲染器里,或者用 CSS 动画去做纯平面的背景,不要让多个 WebGL 渲染器同时跑。
4.5 用一个技巧直达“源码能跑”
上面的问题排查完,你应该已经拿到一个能跑、画质可调的基础项目了。最后再分享一个小技巧,我自己常用它来快速区分是代码问题还是资源问题:把textureLoader.load的 URL 换成一张网络图片(比如一个公开的静态图地址,只要跨域允许),如果页面能正常显示“另一颗星星”,说明代码没问题,问题在你的本地贴图资源上;如果还是黑的,那问题在场景、相机这一层,先别折腾贴图了。
5. 你还可以这样扩展
旋转地球做出来之后,我通常给它加三个小部件,按投入产出比排序:
第一是星空背景。不用复杂的粒子系统,直接给场景的背景设置成一张深空星星贴图,或者用THREE.Points摆几千个随机点,效果立刻不一样。最简单的方式就是设一个scene.background = textureLoader.load('./assets/stars.jpg')。
第二是云层。找一张带透明通道的云层贴图,做第二个SphereGeometry,半径比地球大一点点(比如 1.01),材质用MeshPhongMaterial的alphaMap,透明度设为 0.4,然后让它以比地球略慢的速度旋转。这样一个“活”的地球就出来了。
第三是鼠标交互。给earth加上THREE.OrbitControls(在 r128 里要单独引入),就能实现鼠标拖拽旋转、滚轮缩放,适合产品展示场景。注意引入方式在 r128 里是<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/examples/js/controls/OrbitControls.js"></script>,挂载在THREE.OrbitControls下,新版本里则是从three/examples/jsm/controls/OrbitControls.js导入 ES Module,用法略有差别。
我个人做这个旋转地球项目最大的体会是:Three.js 的 API 又多又杂,但它最常用的套路就是那几步,千万不要一开始就陷入“把所有示例学一遍”的焦虑里。先把场景、相机、渲染器、网格、材质、光照、动画循环这七个关键词吃透,你已经能覆盖绝大多数 3D 展示场景了。旋转地球只是一个起点,把这份源码留在手边,下次要做地球数据可视化、3D 落地页、大屏数字地球时,直接在这个骨架上填充,比从零起步要快得多。
本文还有配套的精品资源,点击获取