用 Three.js 打造 NASA 级 3D 地球门户网——CupGIS 社区全栈实战
2026/7/16 4:32:28 网站建设 项目流程

用 Three.js 打造 NASA 级 3D 地球门户网——CupGIS 社区全栈实战

官网地址:https://cupgis.cn/

前言

在 WebGIS 领域,大多数门户站点还停留在"平面地图 + 列表"的传统形态。CupGIS 社区尝试打破这一惯例:以一颗真实渲染的 3D 地球为视觉核心,通过 WebGL 着色器实现昼夜交替、大气光晕、云层漂移、星空闪烁等天文级效果,同时保证页面作为门户站的简洁易读与高性能。

本文从架构设计、核心着色器、性能优化、UI/UX 设计四个维度,完整拆解 CupGIS 社区主站的技术实现。

**
**


一、项目概览

1.1 技术栈

层级技术选型说明
3D 渲染Three.js 0.162 + WebGLES Module + importmap 加载
着色器GLSL ES自定义顶点/片段着色器
前端架构原生 ES Module零框架依赖,极致轻量
国际化自研 i18n 模块中/英双语,localStorage 持久化
样式CSS3 + CSS 变量玻璃态、渐变、响应式
数据驱动JSON 配置模块/轨道参数全配置化

1.2 项目结构

CupGIS/ ├── index.html # 主站入口 ├── config/ │ └── modules.json # 模块与轨道配置(数据驱动) ├── css/ │ └── style.css # 全局样式(设计令牌 + 组件) ├── js/ │ ├── main.js # 应用入口,编排所有模块 │ ├── i18n.js # 国际化(中/英双语) │ ├── modules.js # 模块渲染器(全宽分区布局) │ ├── scroll.js # 滚动控制器 │ └── scene/ │ ├── manager.js # Three.js 场景管理器 │ └── earth.js # 真实地球(着色器核心) └── assets/ ├── textures/ # NASA 4K 地球纹理 └── modules/ # 模块主题配图

【在此处插入图片:项目目录结构截图——展示完整的文件组织】


二、核心架构设计

2.1 应用编排:main.js

整个应用采用单一CupGISApp类编排,初始化流程如下:

asyncinit(){// 1. 立即绑定 UI 交互(不阻塞加载)this._setupNav();this._setupLangToggle();this._setupSmoothScroll();this._setupScrollStates();// 2. 初始化 i18napplyTranslations(getCurrentLang());// 3. 并行:模块配置 fetch + 3D 场景初始化constconfigData=awaitfetchConfig();renderModules();// 4. 初始化 Three.js 场景(加载进度回调)this.sceneManager=newSceneManager(this.threeCanvas);awaitthis.sceneManager.init(orbitConfigs,(progress)=>{this._updateLoading(progress);});// 5. 滚动控制器绑定 3D 场景this.scrollController=newScrollController((progress)=>this.sceneManager.setScrollProgress(progress),(moduleIdx)=>this.sceneManager.setActiveModule(moduleIdx));}

关键设计:UI 交互绑定与 3D 场景初始化解耦。即使 Three.js CDN 加载失败,导航、滚动、模块内容仍可正常使用。

2.2 数据驱动的模块系统

所有模块信息存储在modules.json中,包括标题、描述、功能列表、轨道参数和主题色:

{"id":"data","title":{"zh":"数据索引","en":"Data Index"},"color":"#00b4d8","image":"assets/modules/data.jpg","orbit":{"radius":1.3,"inclination":28.5,"speed":0.15,"startAngle":0}}

每个模块的轨道参数(半径、倾角、速度)直接映射到 3D 场景中的卫星轨道,实现"配置即轨道"的数据驱动设计。主题色通过 CSS 变量--module-color驱动整个分区的视觉风格。

【在此处插入图片:数据驱动流程图——展示从 JSON 配置到 3D 轨道 + UI 渲染的数据流】


三、GLSL 着色器:真实地球的核心

这是整个项目技术含量最高的部分。地球的昼夜交替、云层、大气光晕全部通过自定义 GLSL 着色器实现。

【在此处插入图片:3D 地球渲染效果——展示昼夜交替、大气光晕、云层效果】

3.1 地球表面着色器

// 顶点着色器:输出世界空间法线 varying vec3 vNormalW; void main() { vNormalW = normalize((modelMatrix * vec4(normal, 0.0)).xyz); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
// 片段着色器核心逻辑 uniform sampler2D dayTexture; uniform sampler2D nightTexture; uniform sampler2D brcMap; // R=bump, G=roughness, B=clouds uniform vec3 sunPosition; uniform float transitionWidth; uniform float cloudOffset; void main() { vec3 normal = normalize(vNormalW); vec3 lightDir = normalize(sunPosition); // 法线与太阳方向的点积 = cos(θ) float dotProduct = dot(normal, lightDir); // 平滑晨昏线过渡(smoothstep 混合) float dayStrength = smoothstep( -transitionWidth * 0.5, transitionWidth * 0.5, dotProduct ); // 白天/夜间纹理采样 vec3 dayColor = texture2D(dayTexture, vUv).rgb; vec3 nightColor = texture2D(nightTexture, vUv).rgb; // 云层:从 BRC 蓝色通道提取,独立旋转 vec2 cloudUv = vec2(vUv.x - cloudOffset, vUv.y); float clouds = smoothstep(0.25, 1.0, texture2D(brcMap, cloudUv).b); // 白天侧云层混合 dayColor = mix(dayColor, vec3(0.92, 0.94, 0.96), clouds * 0.7 * dayStrength); dayColor *= 1.3; // 夜间灯光增强(城市灯火清晰可见) nightColor *= 7.0; // 昼夜混合 vec3 color = mix(nightColor, dayColor, dayStrength); gl_FragColor = vec4(color, 1.0); }

技术要点

  1. 世界空间法线:法线变换到世界空间(modelMatrix * normal),保证昼夜分界线不随地球自转或相机移动而漂移
  2. smoothstep 晨昏线:用smoothstep替代硬切step,产生柔和的日出/日落过渡带
  3. BRC 三通道纹理:将 bump(凹凸)、roughness(粗糙度)、clouds(云层)合并为一张纹理,减少纹理采样次数。云层从蓝色通道提取
  4. 云层独立旋转:通过cloudOffsetuniform 在 UV 的 U 方向偏移,使云层与地球自转产生相对运动
  5. 夜间灯光增强:夜间贴图乘以 7.0,确保黑暗面的城市灯火清晰可见

【在此处插入图片:昼夜交替效果对比——白天侧与夜间侧的地球渲染效果】

3.2 大气光晕着色器(Fresnel 效应)

uniform vec3 sunPosition; varying vec3 vNormalW; varying vec3 vPositionW; void main() { vec3 normal = normalize(vNormalW); vec3 viewDir = normalize(cameraPosition - vPositionW); // Fresnel:边缘越亮 float fresnel = 1.0 - abs(dot(viewDir, normal)); float alpha = clamp((fresnel - 0.6) / 0.4, 0.0, 1.0); alpha = 1.0 - alpha; alpha = pow(alpha, 3.0); // 太阳照射方向影响大气亮度 float sunOrientation = dot(normal, normalize(sunPosition)); alpha *= smoothstep(-0.5, 1.0, sunOrientation); // 大气色:白天蓝,暮光微暖 vec3 atmoDay = vec3(0.30, 0.70, 1.00); vec3 atmoTwilight = vec3(0.95, 0.45, 0.15); vec3 atmoColor = mix(atmoTwilight, atmoDay, smoothstep(-0.2, 0.7, sunOrientation)); gl_FragColor = vec4(atmoColor, alpha); }

大气层使用BackSide渲染(从内部看),配合AdditiveBlending叠加模式,产生从地球边缘向外扩散的蓝色光晕。暮光区域(晨昏线附近)呈现暖橙色,模拟真实大气散射。

【在此处插入图片:大气光晕效果——展示地球边缘的蓝色光晕和晨昏线暖色过渡】

3.3 闪烁星空着色器(GPU Points)

// 顶点着色器:每颗星独立闪烁 attribute float size; attribute vec3 color; attribute float phase; attribute float frequency; uniform float time; void main() { vColor = color; float blink = sin(time * frequency + phase) * 0.5 + 0.8; float noise = sin(dot(position, vec3(12.9898, 78.233, 45.5432)) * 43758.5453) * 0.1; float finalSize = size * (blink + noise); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_PointSize = finalSize * (300.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; }

1200 颗星在 GPU 上并行计算闪烁动画,每颗星有独立的频率和相位,配合随机噪声避免规律性闪烁。星体颜色按概率分布:70% 偏白、20% 偏蓝、10% 偏红橙,模拟真实恒星光谱。


四、性能优化策略

4.1 纹理并行加载

三个 4K 地球纹理(白天、夜间、BRC)从串行改为并行加载,首屏时间缩短约 60%:

async_loadTextures(onProgress){constloader=newTHREE.TextureLoader();constentries=Object.entries(TEXTURE_PATHS);letcompleted=0;// Promise.all 并行加载所有纹理constpromises=entries.map(([name,path])=>{returnnewPromise((resolve,reject)=>{loader.load(path,(tex)=>{tex.colorSpace=colorTextures.includes(name)?THREE.SRGBColorSpace:THREE.LinearSRGBColorSpace;tex.anisotropy=window.innerWidth<768?2:8;completed++;if(onProgress)onProgress(completed/entries.length);resolve([name,tex]);},undefined,reject);});});constresults=awaitPromise.all(promises);returnObject.fromEntries(results);}

4.2 渲染循环优化

_startRenderLoop(){constanimate=()=>{this._animationId=requestAnimationFrame(animate);constdelta=this.clock.getDelta();// 标签页不可见时跳过渲染,节省 GPUif(document.hidden)return;this.earth.update(this.scrollProgress,delta);if(this.controls)this.controls.update();this.renderer.render(this.scene,this.camera);};animate();}

优化要点:

  • document.hidden检测:标签页切换到后台时暂停渲染,避免无意义 GPU 消耗
  • clock.getDelta():基于增量时间的动画,保证不同帧率下速度一致
  • 像素比限制setPixelRatio(Math.min(devicePixelRatio, 2)),防止高 DPI 屏幕过度采样

4.3 移动端自适应降级

constseg=window.innerWidth<768?64:96;// 球体细分段数constSTARS_AMOUNT=window.innerWidth<768?600:1200;// 星星数量tex.anisotropy=window.innerWidth<768?2:8;// 各向异性

移动端自动降低几何体精度、粒子数量和纹理过滤质量,保证 60fps 流畅运行。

4.4 滚动性能优化

// passive: true 不阻塞主线程window.addEventListener('scroll',this._onScroll.bind(this),{passive:true});_onScroll(){if(this._ticking)return;this._ticking=true;requestAnimationFrame(()=>{this._recalculate();this._ticking=false;});}

使用requestAnimationFrame节流 +passive事件监听,滚动事件不阻塞主线程。

4.5 资源加载优化

  • 字体display=swap:Google Fonts 使用swap策略,避免字体加载阻塞文字渲染
  • preload关键资源:CSS 和 JSON 配置使用<link rel="preload">提前下载
  • 图片loading="lazy":模块配图懒加载,首屏不请求
  • importmap CDN 加载:Three.js 从 jsdelivr CDN 加载,无需打包

【在此处插入图片:加载性能对比图——优化前后的首屏时间对比】


五、UI/UX 设计

5.1 全宽分区式布局

参考 NASA 官网设计语言,模块区域从传统卡片改为全宽分区:

.module-panel{display:grid;grid-template-columns:1fr 1fr;min-height:90vh;}/* 交替布局:偶数左图右文,奇数右图左文 */.module-panel.image-left .panel-visual{order:1;}.module-panel.image-left .panel-content{order:2;}.module-panel.image-right .panel-visual{order:2;}.module-panel.image-right .panel-content{order:1;}

每个模块占据全屏宽度,图文左右交替排列,配合大号编号、图标框和装饰线标签,形成类似 NASA 官网的机构级视觉层次。

【在此处插入图片:全宽分区布局截图——展示图文交替排列的模块分区效果】

5.2 六色主题体系

每个模块拥有独立主题色,通过 CSS 变量--module-color驱动整个分区的视觉元素:

.panel-number{color:var(--module-color);text-shadow:0 0 20pxcolor-mix(in srgb,var(--module-color)40%,transparent);}.panel-visual-glow{background:radial-gradient(ellipse,transparent 50%,color-mix(in srgb,var(--module-color)20%,transparent)100%);}
模块主题色色调含义
数据索引#00b4d8青色遥感/海洋
在线工具#9d4edd紫色科技/工具
技术博客#f77f00橙色活力/内容
开发文档#06a77d绿色成长/参考
社区#4361ee蓝色连接/协作
关于#adb5bd银色纯净/愿景

【在此处插入图片:六色主题色对比图——展示六个模块各自的配色方案】

5.3 玻璃态导航与滚动状态

.navbar{background:rgba(5,5,7,0.55);backdrop-filter:blur(20px)saturate(140%);border-bottom:1px solidvar(--color-border);}.navbar.scrolled{padding:var(--spacing-sm)var(--spacing-lg);background:rgba(5,5,7,0.85);}

导航栏滚动时自动收紧、加深背景透明度,实现"呼吸感"视觉反馈。

5.4 国际化 i18n

自研轻量 i18n 模块,通过data-i18n属性驱动 DOM 文本,支持运行时切换:

exportfunctionapplyTranslations(lang){constdict=translations[lang];document.querySelectorAll("[data-i18n]").forEach((el)=>{constkey=el.getAttribute("data-i18n");constvalue=resolveKey(dict,key);if(value)el.textContent=value;});localStorage.setItem("cupgis-lang",lang);window.dispatchEvent(newCustomEvent("languagechange"));}

切换语言时通过languagechange事件触发模块重新渲染,无需刷新页面。


六、3D 场景与滚动联动

6.1 滚动控制器

exportclassScrollController{_recalculate(){constscrollHeight=document.documentElement.scrollHeight-window.innerHeight;constscrollTop=window.scrollY;this.progress=Math.min(1,scrollTop/scrollHeight);// 按进度推断当前模块constheroEnd=0.08;constmoduleSpan=(1-heroEnd)/this.moduleCount;letmoduleIdx=-1;if(this.progress>heroEnd){moduleIdx=Math.floor((this.progress-heroEnd)/moduleSpan);}// 通知 3D 场景if(this.onProgressChange)this.onProgressChange(this.progress);if(moduleIdx!==this.activeModuleIndex){this.activeModuleIndex=moduleIdx;if(this.onModuleChange)this.onModuleChange(moduleIdx);}}}

滚动进度通过回调传入 3D 场景管理器,驱动地球旋转角度和卫星高亮,实现"滚动即航行"的沉浸式体验。

6.2 OrbitControls 交互

this.controls=newOrbitControls(this.camera,this.canvas);this.controls.enableRotate=true;// 鼠标拖拽旋转this.controls.enableZoom=true;// 滚轮缩放this.controls.enablePan=false;// 禁用平移this.controls.autoRotate=true;// 自动旋转this.controls.autoRotateSpeed=0.4;// 缓慢自转this.controls.enableDamping=true;// 惯性阻尼this.controls.dampingFactor=0.06;

用户可直接拖拽地球旋转、滚轮缩放,松手后地球保持惯性缓慢自转。

【在此处插入图片:滚动联动效果 GIF——展示滚动时地球视角变化和卫星高亮】


七、程序化纹理生成

太阳和光晕纹理使用 Canvas API 程序化生成,无需额外图片资源:

_makeSunTexture(){constcanvas=document.createElement('canvas');canvas.width=canvas.height=512;constctx=canvas.getContext('2d');// 径向渐变:从白热中心到红色边缘constgrad=ctx.createRadialGradient(256,256,0,256,256,256);grad.addColorStop(0,'#fff7d6');grad.addColorStop(0.3,'#ffd24a');grad.addColorStop(0.7,'#ff8a1e');grad.addColorStop(1,'#c43c00');ctx.fillStyle=grad;ctx.fillRect(0,0,512,512);// 随机黑子/亮斑for(leti=0;i<1400;i++){constx=Math.random()*512;consty=Math.random()*512;constr=Math.random()*18+2;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fillStyle=Math.random()<0.5?`rgba(255, 240, 180,${Math.random()*0.15+0.05})`:`rgba(180, 60, 0,${Math.random()*0.15})`;ctx.fill();}returnnewTHREE.CanvasTexture(canvas);}

1400 个随机黑子和亮斑模拟太阳表面活动,配合AdditiveBlending的光晕精灵,产生真实的恒星视觉效果。

【在此处插入图片:程序化太阳纹理——展示 Canvas 生成的太阳表面效果】


八、降级容错

3D 场景初始化失败时(如 CDN 不可用),自动降级为静态深空背景:

try{awaitthis.sceneManager.init(orbitConfigs,(progress)=>{this._updateLoading(progress);});}catch(err){console.error('3D 场景初始化失败:',err);this._fallbackScene();}_fallbackScene(){document.body.classList.add('scene-fallback');constcanvas=document.getElementById('threeCanvas');if(canvas)canvas.style.display='none';}
body.scene-fallback{background:radial-gradient(ellipse at 20% 30%,rgba(40,20,60,0.4)0%,transparent 50%),radial-gradient(ellipse at 80% 70%,rgba(20,30,70,0.4)0%,transparent 50%),radial-gradient(circle at 50% 50%,#0a0a12 0%,#050507 70%,#000 100%);}

即使 WebGL 完全不可用,门户站的内容、导航、模块分区仍可正常浏览。


总结

CupGIS 社区主站的技术实践表明:在不依赖前端框架的前提下,通过原生 ES Module + Three.js + 自定义 GLSL 着色器,完全可以构建出 NASA 级别的 3D Web 门户。核心技术要点回顾:

  1. GLSL 着色器:世界空间法线 + smoothstep 晨昏线 + BRC 多通道纹理 + Fresnel 大气
  2. 性能优化:纹理并行加载 + 渲染循环节流 + 移动端降级 + 懒加载
  3. 架构设计:数据驱动模块 + i18n 双语 + 滚动联动 3D + 降级容错
  4. UI/UX:全宽分区布局 + 六色主题体系 + 玻璃态导航 + 响应式

项目开源,欢迎交流:654671523

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

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

立即咨询