☰
Three.js旋转地球实战:从场景搭建到材质贴图的完整实现
2026/10/20 6:37:41 网站建设 项目流程

简介:一款基于HTML与Three.js的旋转地球交互源码,主要面向Three.js初学者,也适用于需要在网页中快速嵌入3D地球模型的前端开发者。项目已内置完整的地球表面贴图,打开即可在浏览器中看到绕轴旋转的地球效果,同时把HTML、CSS、JavaScript与图片素材分目录存放,方便移植到数据可视化大屏、企业官网、科技展厅等场景。资源包共5个文件,涵盖JS逻辑、HTML入口、CSS样式及JPG贴图等核心类型,整体仅957KB,轻量小巧、依赖简单;其中JS负责三维渲染与动画控制,CSS辅助页面布局,贴图直接提供地球表面纹理。目前已有1351人学习查看,热度表现良好。通过这份源码,读者可以学到Three.js场景初始化、球体几何体创建、材质贴图绑定以及旋转动画实现等关键环节;无论是初学者练手,还是项目开发中直接复用,都能获得清晰可执行的参考。 最近在折腾HTML+Threejs旋转地球源码,发现这个效果虽然看起来唬人,但核心就是一个带材质贴图的球体在转。很多朋友卡住,不是因为Three.js有多难,而是没搞懂场景、相机、渲染器、贴图这几样东西怎么串起来。这篇就用一个能直接运行的旋转地球demo,把整个实现过程、材质贴图的选择、以及我实操时踩过的坑都捋一遍,希望帮你省点时间。

1. 旋转地球看起来炫,本质就三件事

1.1 需求拆解:从"地球"到"可运行程序"

如果只看最终效果,你可能会觉得旋转地球很复杂。拆开需求就三件事:创建一个三维场景,在场景里放一个球体,球体表面贴上地球纹理,最后让球体绕着Y轴持续旋转。这句话翻译成Three.js的代码逻辑,对应的是Scene(场景)、Camera(相机)、Renderer(渲染器)、SphereGeometry(球体几何体)、TextureLoader(纹理加载器)和requestAnimationFrame动画循环。

Three.js项目的起步套路非常固定,所有新手都必须先跑通这个最小闭环:Scene负责装所有物体,Camera决定从哪个角度观察,Renderer负责把看到的画面绘制到浏览器的canvas上。你可能会问,为什么不直接用原生WebGL?因为原生WebGL要自己写顶点着色器、片元着色器、矩阵变换和UV坐标,一个小球体没有几百行代码下不来。Three.js把这一层封装好了,我们能更专注在“怎么让效果好看”上。

1.2 技术选型:为什么是Three.js

实现旋转地球,可选方案其实不少。纯CSS3D做一个转动的平面地球很简单,但想要光照、明暗过渡、鼠标拖拽观察,就会非常别扭。原生WebGL足够自由,但投入产出比太低,光是一个纹理贴图就够劝退新手。Cesium这种GIS引擎当然也能做地球,但它偏重真实地理数据和地图交互,为一个小demo引入这么大一套框架,不划算。

Three.js处在中间位置:生态成熟、文档多、社区案例丰富,而且整个学习曲线比较平滑。你不需要成为图形学专家,只需要理解场景、相机、渲染器、几何体、材质、灯光、动画循环这几个概念,就能做出一个像模像样的3D效果。旋转地球刚好把这些核心概念全部串起来,所以我觉得它是Three.js入门非常值得复刻的一个项目。

2. 开工之前的准备:贴图、引入方式和项目结构

2.1 地球材质贴图怎么选、去哪找

材质贴图是“旋转地球”的灵魂。最常用的是equirectangular全景地球图,宽高比是2:1,可以理解为把地球表面展开成一张长方形图片。选贴图时要注意三件事:第一,图片比例最好接近2:1,否则贴到球体上会明显变形;第二,分辨率不要低于1024×512,否则放大后地表细节糊成一团;第三,使用前确认授权,自己学习无所谓,如果有发布或商用打算,需要留意图片来源。

找贴图的地方有很多,Three.js官方示例里就自带一个earth_atmos_2048.jpg,尺寸和比例都适合入门。NASA的一些开放地球纹理也可以直接用。我的建议是刚开始别追求4K超高清,2048×1024的贴图对于demo来说已经绰绰有余,加载速度也合适。你完全可以把后续优化重点放在灯光和交互上,而不是一直纠结纹理细节。

2.2 Three.js的引入方式对比

现在Three.js的主推用法是ES Module,配合importmap在HTML里声明依赖,然后通过import * as THREE from 'three'引入。这样代码结构清晰,也能按需加载OrbitControls等附加模块。早期很多教程用的是<script src="three.min.js"></script>的UMD方式,在小demo里也能跑,但新版Three.js已经逐渐弱化UMD版本,如果你照着旧教程写,可能遇到版本兼容问题。

用importmap时有个细节值得注意:主库版本和附加模块版本要保持一致,否则容易出现类名不匹配、方法找不到的报错。比如主库用了r160,OrbitControls的CDN地址也应该是r160。很多莫名其妙的白屏或报错,本质上都是版本不一致造成的,不是你的代码有问题。

2.3 项目目录和运行姿势

我的项目结构很简单:

earth-demo/ ├── index.html └── textures/ └── earth.jpg

把HTML和贴图分开管理,避免根目录文件太乱。这个项目虽然简单,但运行方式有个大坑:直接双击index.html,大概率会白屏。因为TextureLoader用浏览器的fetch机制加载本地图片,而file://协议下Chrome等浏览器会出于安全策略拦截跨文件读取。所以要用本地静态服务来跑。

最简单的办法是在VS Code里装Live Server插件,右键Open with Live Server;或者命令行进入项目目录,执行python -m http.server 8080,然后访问http://localhost:8080。相信我,这一步能解决大量“为什么贴图不显示”的求助帖。

3. 逐行实现:一个带材质贴图的旋转地球

3.1 搭建场景、相机和渲染器

先从最基础的场景搭建开始。

const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(0, 0, 4);

这里用了透视相机(PerspectiveCamera),45是视野角度,第二项是画布宽高比,后两项是近裁剪面和远裁剪面。相机放在Z轴正方向4个单位的位置,也就是从正面观察地球。如果相机放得太近,地球会充满整个画面;放得太远,地球会显得很小,4这个距离在默认球体半径1的情况下观感比较舒服。

然后是渲染器:

const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);

antialias: true是抗锯齿开关,能明显减少模型边缘的“狗牙”。一定别忘了把渲染器生成的canvas挂到body上,否则屏幕上什么都看不到。

3.2 创建球体并贴上地球材质

有了场景和相机,接下来就可以放主角了。

const textureLoader = new THREE.TextureLoader(); const earthTexture = textureLoader.load('textures/earth.jpg'); const geometry = new THREE.SphereGeometry(1, 64, 64); const material = new THREE.MeshPhongMaterial({ map: earthTexture, shininess: 25 }); const earth = new THREE.Mesh(geometry, material); scene.add(earth);

SphereGeometry(1, 64, 64)表示半径1,横向和纵向都分成64段。分段数越多,球体越平滑,但顶点数和渲染开销也会增加。64在普通电脑上足够流畅,如果目标平台是低端手机,可以降到32;想要更高清,再调到128。没必要一上来就追求极高精度,旋转地球的动态效果会掩盖一部分低模的棱角感。

材质方面,我选了MeshPhongMaterial而不是MeshBasicMaterial。区别很关键:MeshBasicMaterial不受光照影响,贴图会显得平淡,像一张纸贴在球上;MeshPhongMaterial可以配合灯光产生明暗过渡,让地球有立体感。这里是旋转地球看起来“真实”的核心细节。

3.3 让地球转起来并加入交互

旋转逻辑非常简单,但要注意放在哪个循环里。

function animate() { requestAnimationFrame(animate); earth.rotation.y += 0.002; renderer.render(scene, camera); } animate();

earth.rotation.y += 0.002是每帧增加0.002弧度。假设60帧每秒,地球每秒转约0.12弧度,转一圈约52秒。这个速度比较舒缓,适合作为页面背景。想让节奏更快,可以改成0.005或者0.01。

交互方面,我用OrbitControls来支持鼠标拖拽观察:

import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.minDistance = 1.5; controls.maxDistance = 10;

enableDamping开启阻尼效果,拖拽结束后会有一个惯性缓冲,手感没有那么生硬。限制缩放距离可以避免用户把相机怼进地球内部,或者拉到完全看不见。

3.4 完整源码与效果说明

下面是我这个demo最终用的完整HTML源码,配合textures/earth.jpg就能跑通。

<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>HTML + Three.js 旋转地球</title> <style> html, body { margin: 0; padding: 0; overflow: hidden; background: #000; } </style> </head> <body> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(0, 0, 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 textureLoader = new THREE.TextureLoader(); const earthTexture = textureLoader.load('textures/earth.jpg'); const geometry = new THREE.SphereGeometry(1, 64, 64); const material = new THREE.MeshPhongMaterial({ map: earthTexture, shininess: 25 }); const earth = new THREE.Mesh(geometry, material); scene.add(earth); const ambientLight = new THREE.AmbientLight(0xffffff, 0.4); scene.add(ambientLight); const sunLight = new THREE.DirectionalLight(0xffffff, 1.6); sunLight.position.set(5, 3, 5); scene.add(sunLight); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.minDistance = 1.5; controls.maxDistance = 10; function animate() { requestAnimationFrame(animate); earth.rotation.y += 0.002; controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html>

这段代码运行起来后,你会看到一个带有纹理贴图的地球在自转,同时可以用鼠标拖拽旋转视角、滚动滚轮拉近拉远。窗口大小变化时,画面不会变形,因为resize事件里同步更新了相机宽高比和渲染器尺寸。

4. 实操中的坑:锯齿、跨域和性能

4.1 高版本Three.js的锯齿问题

有朋友在使用较新版本Three.js时发现,即使开了antialias: true,地球边缘还是有明显锯齿。这里容易忽略的是渲染器的像素比(pixelRatio)。在高分屏上,如果不设置renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)),渲染出的画面分辨率可能低于物理屏幕分辨率,边缘自然会有毛刺。

加上这一行之后,大部分锯齿问题都能缓解。如果还觉得不够细腻,可以把球体分段从64提高到128,不过对性能影响会比像素比更大,需要在视觉和流畅度之间平衡。还有个思路是走后处理抗锯齿,比如FXAA,但对这个demo来说有点杀鸡用牛刀,我一般不推荐新手一上来就上。

4.2 贴图不显示的排查

贴图不显示,最常见的原因有两个:一是路径错误,二是材质没有收到足够光照。

先说路径。textureLoader.load('textures/earth.jpg')是相对当前HTML文件的路径,不是相对JS文件的路径。建议先打开浏览器F12,看Network面板里earth.jpg是否请求成功。如果显示404,大概率是路径写错了。

再说光照。如果你用的是MeshPhongMaterial或MeshStandardMaterial,但场景里一盏灯都没有,球体会被渲染成黑色,看起来像贴图没加载成功。这里我有一个排查技巧:临时把材质改成MeshBasicMaterial。如果改成Basic后地球纹理正常显示,说明贴图本身没问题,问题出在光照配置上。此时加一盏AmbientLight或DirectionalLight,明暗关系就出来了。

4.3 本地打开HTML报跨域错误

很多新手把代码写完,直接双击HTML文件,结果页面空白、控制台报错,错误信息里通常带着CORS相关字样。这不是代码逻辑问题,而是浏览器安全策略阻止了在file://协议下通过fetch方式读取本地图片。

解决方案是起一个本地服务。我日常最常用两个方式:VS Code装Live Server,右键选择Open with Live Server;或者项目目录执行python -m http.server 8080,然后打开http://localhost:8080。用本地服务之后,贴图加载和页面预览都会正常。如果你只是临时看一眼效果,也可以把图片传到支持跨域的图床,用完整URL引用,但这种方式受网络影响,不推荐作为常规做法。

4.4 性能优化和帧率建议

旋转地球本身开销不高,但有些细节会影响流畅度。第一,贴图尺寸别太大,2048×1024已经足够浏览器实时处理,如果你强行上8192分辨率,低端设备会明显掉帧。第二,动画循环里不要频繁创建新对象,我这个demo里每帧只是修改rotation.y,开销非常低;如果你需要动态更新顶点位置,就要注意合理控制频率。第三,setPixelRatio不要直接设成window.devicePixelRatio,有些手机devicePixelRatio能到3甚至更高,渲染分辨率过高会让GPU压力很大,建议统一用Math.min(window.devicePixelRatio, 2)。

另外,如果这个地球只是页面背景,不需要用户交互,可以考虑不引入OrbitControls,省下一点内存和事件监听开销。如果还需要加星空背景,可以在Scene里加一个大球体或者用THREE.Points做粒子星空,有兴趣可以继续扩展。

5. 常见问题速查表与避坑清单

5.1 问题速查表

现象可能原因解决办法
页面白屏,控制台报跨域直接双击HTML打开,file://协议被浏览器限制用Live Server或python -m http.server启动本地服务
地球显示为黑色,没有纹理感使用了MeshPhongMaterial但场景没有灯光添加环境光和平行光
贴图加载404纹理路径不对检查textures文件夹位置和相对路径
球体边缘锯齿明显没有设置renderer.setPixelRatio设置Math.min(window.devicePixelRatio, 2)
球体变形,贴图拉伸贴图宽高比不是2:1换用equirectangular全景地球贴图
鼠标拖拽不生效忘记在动画循环里调用controls.update()在animate循环中加入controls.update()
引入OrbitControls报错主库版本和附加模块版本不一致统一CDN地址中的版本号

5.2 我的排错顺序和习惯

做了这么多次Three.js小demo,我现在遇到问题会按固定顺序查:先看控制台有没有报错,再看Network面板对应静态资源是否加载成功,最后才检查光照、材质和渲染器配置。绝大多数问题都出在前两步,尤其本地文件路径和跨域服务,基本占了七八成。

如果你也想顺利跑通这个旋转地球,建议先把项目目录和本地服务准备好,再从头敲一遍代码。不要直接复制粘贴就完事,要理解每一行在做什么。这样以后换成云层、凹凸贴图、夜景灯光效果时,你才有能力自己去改材质和光源。

最后分享一个我个人的体会:这种小项目最怕的不是“不会写”,而是“没思路”。你把场景、相机、渲染器、几何体、材质、贴图、动画循环这几个关键词串起来,旋转地球就只是第一次综合练习而已。以后想做到更丰富的效果,比如鼠标交互、大气层光晕、城市光点,都是在这个基础上往材质和粒子系统方向扩展。希望这份源码和踩坑记录能让你少走一点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询