3D地球可视化教程 - 第6篇:蜂巢网格与自定义几何体

技术栈: Three.js + GLSL + 程序化几何体 + 数学几何

本篇学习目标

  • 掌握程序化几何体生成技术
  • 理解球面几何学和六边形密铺
  • 学习高级着色器编程
  • 实现复杂的扫光动画算法
  • 创造科幻感的视觉效果

几何学理论基础

六边形密铺的数学原理

六边形是自然界中最高效的平面密铺形状:

  • 蜂巢结构 - 蜜蜂用六边形建造蜂巢
  • 数学最优 - 相同面积下周长最小
  • 工程应用 - 材料科学、建筑设计广泛应用
  • 游戏开发 - 策略游戏的地图系统

球面六边形的挑战

将平面六边形映射到球面上面临的问题:

javascript 复制代码
// 球面几何的挑战
const sphericalChallenges = {
  曲率问题: "平面图形无法完美贴合球面",
  极点聚集: "极点附近六边形会过度聚集", 
  大小变化: "不同纬度的六边形大小不一致",
  密铺困难: "球面上无法实现完美的六边形密铺",
};

// 我们的解决方案
const solutions = {
  自适应密度: "根据纬度调整六边形数量",
  极点特殊处理: "极点附近跳过或减少六边形",
  角度尺寸控制: "使用角度而非线性尺寸",
  偏移模式: "奇偶行偏移实现更好的密铺",
};

程序化几何体生成

1. 球面网格算法

javascript 复制代码
// HoneycombGrid.js - 球面网格生成
createHoneycombGeometry() {
  const vertices = [];
  const indices = [];
  const radius = EARTH_RADIUS * 1.001; // 稍大于地球,避免Z-fighting

  //  球面参数化
  const latitudeDivisions = 192;   // 纬度分割数
  const longitudeDivisions = 384;  // 经度分割数

  for (let lat = 0; lat <= latitudeDivisions; lat++) {
    //  计算纬度角:0 到 π
    const phi = (lat / latitudeDivisions) * Math.PI;
    const sinPhi = Math.sin(phi);
    const cosPhi = Math.cos(phi);

    //  极点特殊处理
    if (sinPhi < 0.05) {
      if (lat === 0) this.createHexagon(vertices, indices, 0, radius, 0, hexRadius);
      if (lat === latitudeDivisions) this.createHexagon(vertices, indices, 0, -radius, 0, hexRadius);
      continue;
    }

    //  自适应经度分割
    const lonDivisions = Math.max(3, Math.floor(longitudeDivisions * sinPhi));

    for (let lon = 0; lon < lonDivisions; lon++) {
      //  六边形偏移模式
      const offset = (lat % 2) * 0.5;
      const theta = ((lon + offset) / lonDivisions) * Math.PI * 2;

      //  球面坐标转换
      const x = radius * sinPhi * Math.cos(theta);
      const y = radius * cosPhi;
      const z = radius * sinPhi * Math.sin(theta);

      this.createHexagon(vertices, indices, x, y, z, hexRadius);
    }
  }
}

2. 六边形生成算法

javascript 复制代码
// 在球面上创建单个六边形
createHexagon(vertices, indices, centerX, centerY, centerZ, size) {
  const center = new THREE.Vector3(centerX, centerY, centerZ);
  const normal = center.clone().normalize();

  //  建立局部坐标系
  const up = Math.abs(normal.y) < 0.9 ? 
    new THREE.Vector3(0, 1, 0) : new THREE.Vector3(1, 0, 0);
  const right = new THREE.Vector3().crossVectors(up, normal).normalize();
  const forward = new THREE.Vector3().crossVectors(normal, right).normalize();

  //  生成六边形顶点
  for (let i = 0; i < 6; i++) {
    const angle = (i / 6) * Math.PI * 2;
    
    //  使用角度尺寸确保一致性
    const angularSize = size / radius;
    const localX = Math.cos(angle) * angularSize;
    const localY = Math.sin(angle) * angularSize;

    //  计算球面位置
    const vertex = center.clone()
      .add(right.clone().multiplyScalar(localX * radius))
      .add(forward.clone().multiplyScalar(localY * radius));

    //  投影到球面
    vertex.normalize().multiplyScalar(radius);
    vertices.push(vertex.x, vertex.y, vertex.z);
  }

  //  连接六边形边
  for (let i = 0; i < 6; i++) {
    const next = (i + 1) % 6;
    indices.push(startIndex + i, startIndex + next);
  }
}

关键技术点

  • 局部坐标系 - 在球面任意点建立正交坐标系
  • 角度尺寸 - 使用角度而非线性距离保证一致性
  • 球面投影 - 将局部坐标投影回球面
  • 边连接 - 生成线段索引用于线框渲染

扫光着色器系统

1. 扫光算法核心

glsl 复制代码
// fragmentShader - 扫光计算函数
float calculateLinearSweep(vec3 position, vec3 direction, float time, float speed, float width) {
  //  球面坐标标准化
  float sphereRadius = length(position);
  vec3 normalizedPos = position / sphereRadius;

  // 应用旋转变换
  float rotationRad = uSweepRotation * 3.14159265 / 180.0;
  float cosRot = cos(rotationRad);
  float sinRot = sin(rotationRad);
  
  vec3 rotatedPos = vec3(
    normalizedPos.x * cosRot - normalizedPos.y * sinRot,
    normalizedPos.x * sinRot + normalizedPos.y * cosRot,
    normalizedPos.z
  );

  // 计算扫光坐标
  float sweepCoord = rotatedPos.y;

  //  创建连续扫光运动
  float sweepCycle = mod(time * speed, 4.0);           // 4秒周期
  float sweepPos = 1.0 - (sweepCycle / 4.0) * 2.0;    // 从顶部(1)到底部(-1)

  //  计算到扫光线的距离
  float distanceFromSweep = abs(sweepCoord - sweepPos);

  //  应用平滑过渡
  float falloffPower = mix(0.5, 3.0, uSweepFalloff);
  float sweepFalloff = 1.0 - smoothstep(0.0, width * 0.5, distanceFromSweep);
  sweepFalloff = pow(sweepFalloff, falloffPower);

  //  强度衰减计算
  float sweepProgress = (1.0 - sweepPos) * 0.5;
  float intensityMultiplier = exp(-sweepProgress * uIntensityDecay);

  return sweepFalloff * intensityMultiplier;
}

2. 扫光参数系统

javascript 复制代码
// HoneycombGrid.js - 扫光参数
const sweepConfig = {
  //  基础扫光参数
  sweepSpeed: 0.42,         // 扫光速度 (0.1 - 1.0)
  sweepWidth: 0.34,         // 扫光宽度 (0.1 - 1.0)
  sweepIntensity: 0.33,     // 扫光强度 (0.1 - 1.0)
  
  //  视觉效果参数
  sweepFalloff: 1.0,        // 边缘过渡强度 (0=柔和, 1=锐利)
  sweepRotation: 66,        // 扫光旋转角度 (0-360度)
  intensityDecay: 4.8,      // 强度衰减速度 (0=无衰减, 5=快速衰减)
  
  //  颜色和透明度
  baseOpacity: 0.01,        // 基础透明度
  color: "#00ccff",         // 扫光颜色
};

3. 拖尾效果算法

glsl 复制代码
// 扫光拖尾效果
float trailEffect = 0.0;
if (sweepCoord < sweepPos) {  // 只在扫光后方产生拖尾
  float trailDistance = sweepPos - sweepCoord;
  float trailFalloff = mix(2.0, 5.0, uSweepFalloff);
  
  // 指数衰减拖尾
  trailEffect = exp(-trailDistance * trailFalloff) * 
                (0.3 * (1.0 - uSweepFalloff * 0.5));
  
  //  应用强度衰减
  trailEffect *= intensityMultiplier;
}

// 合并主扫光和拖尾效果
float intensity = (sweepFalloff * intensityMultiplier) + trailEffect;

视觉效果技术解析

1. 科幻感设计原理

蜂巢网格的科幻感来源于:

  • 几何美学 - 六边形的完美对称性
    -⚡ 动态扫光 - 持续的能量流动感
  • 发光效果 - 加法混合的光晕感
  • 技术质感 - 精密的网格结构

2. 扫光动画的视觉层次

javascript 复制代码
// 扫光效果的多层次设计
const sweepLayers = {
  //  主扫光带
  mainSweep: {
    width: "0.34",
    intensity: "0.33", 
    effect: "主要的亮光带"
  },
  
  //  拖尾效果
  trailEffect: {
    width: "渐变衰减",
    intensity: "0.3 × (1 - falloff × 0.5)",
    effect: "扫光后的余晖"
  },
  
  // 强度衰减
  intensityDecay: {
    formula: "exp(-progress × decay)",
    effect: "从顶部到底部的强度递减"
  }
};

3. 颜色和透明度设计

javascript 复制代码
// 最终颜色计算
const colorCalculation = {
  baseOpacity: 0.01,        // 极低的基础透明度
  sweepHighlight: "动态计算", // 扫光时的高亮
  finalOpacity: "base + sweep", // 最终透明度
  
  //  颜色选择
  科技蓝: "#00ccff",         // 冷色调,科技感
  能量绿: "#00ff88",         // 绿色,能量感
  警告橙: "#ff8800",         // 橙色,警告感
};

核心算法详解

1. 球面坐标系统

javascript 复制代码
// 球面坐标参数化
const sphericalCoordinates = {
  //  纬度角:从北极到南极
  phi: "lat / latitudeDivisions × π",     // [0, π]
  
  //  经度角:围绕地球一周  
  theta: "lon / lonDivisions × 2π",       // [0, 2π]
  
  //  笛卡尔转换
  x: "radius × sin(phi) × cos(theta)",
  y: "radius × cos(phi)", 
  z: "radius × sin(phi) × sin(theta)"
};

2. 自适应密度算法

javascript 复制代码
// 根据纬度调整六边形密度
const adaptiveDensity = (lat, latitudeDivisions, longitudeDivisions) => {
  const phi = (lat / latitudeDivisions) * Math.PI;
  const sinPhi = Math.sin(phi);
  
  //  密度随纬度变化
  const lonDivisions = Math.max(3, Math.floor(longitudeDivisions * sinPhi));
  
  //  密度分布
  return {
    赤道附近: "最高密度,sinPhi ≈ 1",
    中纬度: "中等密度,sinPhi ≈ 0.7", 
    极地附近: "最低密度,sinPhi ≈ 0.1"
  };
};

3. 局部坐标系建立

javascript 复制代码
// 在球面任意点建立正交坐标系
createLocalCoordinateSystem(center) {
  const normal = center.clone().normalize();
  
  //  选择参考向量(避免平行)
  const up = Math.abs(normal.y) < 0.9 ? 
    new THREE.Vector3(0, 1, 0) :     // 通常情况
    new THREE.Vector3(1, 0, 0);      // normal接近Y轴时
  
  //  计算正交基
  const right = new THREE.Vector3().crossVectors(up, normal).normalize();
  const forward = new THREE.Vector3().crossVectors(normal, right).normalize();
  
  return { normal, right, forward };
}

扫光着色器深度解析

1. 顶点着色器

glsl 复制代码
// vertexShader - 数据传递
uniform float uTime;
uniform vec3 uSweepDirection;
uniform float uSweepSpeed;

varying vec3 vWorldPosition;
varying vec3 vLocalPosition;
varying vec2 vUv;
varying float vDistanceFromCenter;

void main() {
  // 🌍世界坐标计算
  vec4 worldPosition = modelMatrix * vec4(position, 1.0);
  vWorldPosition = worldPosition.xyz;
  vLocalPosition = position;

  //  球面UV映射
  vec3 normalized = normalize(position);
  vUv.x = atan(normalized.z, normalized.x) / (2.0 * PI) + 0.5;
  vUv.y = asin(normalized.y) / PI + 0.5;

  //  距离计算
  vDistanceFromCenter = length(position);

  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

2. 片段着色器核心算法

glsl 复制代码
// fragmentShader - 扫光计算
void main() {
  //  计算扫光强度
  float sweepIntensity = calculateLinearSweep(
    vLocalPosition,    // 当前位置
    uSweepDirection,   // 扫光方向
    uTime,             // 时间
    uSweepSpeed,       // 速度
    uSweepWidth        // 宽度
  );

  //  应用强度倍数
  sweepIntensity *= uSweepIntensity;

  //  计算最终透明度
  float finalOpacity = uBaseOpacity + sweepIntensity;
  finalOpacity = clamp(finalOpacity, 0.0, 1.0);

  //  输出最终颜色
  gl_FragColor = vec4(uColor, finalOpacity);
}

3. 复杂扫光算法

glsl 复制代码
// 扫光计算的完整算法
float calculateLinearSweep(vec3 position, vec3 direction, float time, float speed, float width) {
  //  坐标旋转
  float rotationRad = uSweepRotation * PI / 180.0;
  vec3 rotatedPos = vec3(
    position.x * cos(rotationRad) - position.y * sin(rotationRad),
    position.x * sin(rotationRad) + position.y * cos(rotationRad),
    position.z
  );

  //  周期性扫光
  float sweepCycle = mod(time * speed, 4.0);
  float sweepPos = 1.0 - (sweepCycle / 4.0) * 2.0;  // [1, -1]

  //  距离计算
  float distanceFromSweep = abs(rotatedPos.y - sweepPos);

  //  平滑过渡
  float falloffPower = mix(0.5, 3.0, uSweepFalloff);
  float sweepFalloff = 1.0 - smoothstep(0.0, width * 0.5, distanceFromSweep);
  sweepFalloff = pow(sweepFalloff, falloffPower);

  //  强度衰减
  float sweepProgress = (1.0 - sweepPos) * 0.5;
  float intensityMultiplier = exp(-sweepProgress * uIntensityDecay);

  return sweepFalloff * intensityMultiplier;
}

参数调节指南

获得最佳扫光效果

科幻扫描效果
javascript 复制代码
{
  sweepSpeed: 0.5,          // 较快扫描
  sweepWidth: 0.2,          // 窄扫光带
  sweepIntensity: 0.6,      // 高强度
  sweepFalloff: 0.8,        // 锐利边缘
  intensityDecay: 2.0,      // 慢衰减
  color: "#00ffff"          // 青色科技感
}
雷达扫描效果
javascript 复制代码
{
  sweepSpeed: 0.3,          // 中等速度
  sweepWidth: 0.4,          // 宽扫光带
  sweepIntensity: 0.4,      // 中等强度
  sweepFalloff: 0.5,        // 柔和边缘
  intensityDecay: 3.0,      // 中等衰减
  color: "#00ff00"          // 绿色雷达感
}
能量波动效果
javascript 复制代码
{
  sweepSpeed: 0.8,          // 快速扫描
  sweepWidth: 0.6,          // 很宽扫光带
  sweepIntensity: 0.8,      // 高强度
  sweepFalloff: 0.2,        // 很柔和
  intensityDecay: 1.0,      // 慢衰减
  color: "#ff8800"          // 橙色能量感
}

技术实现细节

1. 几何体优化

javascript 复制代码
// 性能优化策略
const geometryOptimization = {
  //  顶点数量控制
  latitudeDivisions: 192,    // 足够精细,不过度密集
  longitudeDivisions: 384,   // 2:1比例,符合球面特性
  
  //  极点优化
  polarOptimization: "极点附近跳过,避免过度聚集",
  
  //  索引优化
  indexBuffer: "使用索引缓冲区,减少顶点重复",
  
  //  内存布局
  bufferGeometry: "连续内存布局,GPU友好"
};

2. 着色器性能优化

glsl 复制代码
// 着色器优化技巧
const shaderOptimizations = {
  //  预计算常量
  "const float PI = 3.14159265359;",
  "const float TWO_PI = 6.28318530718;",
  
  //  条件优化
  "使用 mix() 替代 if-else",
  "使用 step() 和 smoothstep() 进行条件判断",
  
  //  数学优化
  "预计算 sin/cos 值",
  "使用 mad() 指令优化乘加运算"
};

3. 渲染状态管理

javascript 复制代码
// 渲染状态优化
const renderState = {
  //  材质配置
  transparent: true,
  depthWrite: false,           // 避免透明度问题
  blending: THREE.AdditiveBlending, // 加法混合增强发光
  
  //  渲染配置
  side: THREE.DoubleSide,      // 双面渲染
  wireframe: true,             // 线框模式显示网格
  
  //  性能配置
  frustumCulled: true,         // 启用视锥剔除
  renderOrder: 1,              // 确保在地球之后渲染
};

尝试修改以下参数观察效果:

javascript 复制代码
// 扫光效果实验
{
  sweepSpeed: 0.6,          // 尝试 0.2, 0.4, 0.8
  sweepWidth: 0.5,          // 尝试 0.2, 0.4, 0.6
  sweepIntensity: 0.5,      // 尝试 0.2, 0.4, 0.8
  sweepRotation: 45,        // 尝试 0, 45, 90度
}

// 网格密度实验
{
  latitudeDivisions: 128,   // 尝试 96, 128, 256
  longitudeDivisions: 256,  // 尝试 192, 256, 512
  hexagonSize: 0.15,        // 尝试 0.08, 0.12, 0.2
}

高级技术实现

1. 球面UV映射

glsl 复制代码
// 球面到平面的UV映射
vec3 normalized = normalize(position);

//  球面坐标映射
vUv.x = atan(normalized.z, normalized.x) / (2.0 * PI) + 0.5;  // 经度 [0,1]
vUv.y = asin(normalized.y) / PI + 0.5;                        // 纬度 [0,1]

映射原理

  • atan2函数 - 将XZ平面角度映射到[0, 2π]
  • asin函数 - 将Y坐标映射到[-π/2, π/2]
  • 归一化 - 转换到[0, 1]范围供纹理采样

2. 旋转矩阵变换

glsl 复制代码
// 2D旋转矩阵应用
float rotationRad = uSweepRotation * PI / 180.0;
float cosRot = cos(rotationRad);
float sinRot = sin(rotationRad);

// 应用旋转变换
vec3 rotatedPos = vec3(
  position.x * cosRot - position.y * sinRot,  // 新X坐标
  position.x * sinRot + position.y * cosRot,  // 新Y坐标
  position.z                                  // Z坐标不变
);

3. 指数衰减函数

glsl 复制代码
// 强度衰减的数学模型
float intensityMultiplier = exp(-sweepProgress * uIntensityDecay);

//  衰减效果对比
const decayComparison = {
  "decay = 0": "无衰减,强度恒定",
  "decay = 1": "缓慢衰减,底部仍有50%强度", 
  "decay = 3": "中等衰减,底部约5%强度",
  "decay = 5": "快速衰减,底部几乎为0"
};

视觉设计技巧

1. 科幻感营造

javascript 复制代码
// 科幻视觉的关键要素
const sciFiElements = {
  //  几何精确性
  geometry: "完美的数学几何体",
  
  // ⚡ 能量流动
  animation: "持续的扫光运动",
  
  //  发光效果
  glow: "加法混合的光晕",
  
  //  冷色调
  color: "青色/蓝色的技术感色彩",
  
  //  动态变化
  dynamics: "强度衰减和拖尾效果"
};

2. 层次感设计

javascript 复制代码
// 当前的完整视觉层次
const completeVisualHierarchy = {
  背景层: "星空背景 (radius: 500)",
  装饰层: "轨道星星 (radius: 20-23)",
  大气层: "云层效果 (radius: 20.6)",
  技术层: "蜂巢网格 (radius: 20.02)", // 🆕 新增
  表面层: "夜晚纹理 (radius: 20.01)",
  基础层: "地球纹理 (radius: 19.98)"
};

程序化几何体的工程化

1. 模块化设计

javascript 复制代码
// 几何体生成器的模块化设计
class GeometryGenerator {
  //  六边形生成器
  static createHexagon(center, size, normal) {
    return HexagonGenerator.generate(center, size, normal);
  }
  
  //  球面网格生成器
  static createSphericalGrid(radius, divisions) {
    return SphericalGridGenerator.generate(radius, divisions);
  }
  
  //  自适应密度生成器
  static createAdaptiveMesh(config) {
    return AdaptiveMeshGenerator.generate(config);
  }
}

2. 配置驱动设计

javascript 复制代码
// 完全配置化的蜂巢网格
const honeycombConfig = {
  //  几何参数
  geometry: {
    hexagonSize: 0.12,
    latitudeDivisions: 192,
    longitudeDivisions: 384,
    radius: EARTH_RADIUS * 1.001
  },
  
  //  扫光参数
  sweep: {
    enabled: true,
    speed: 0.42,
    width: 0.34,
    intensity: 0.33,
    rotation: 66,
    decay: 4.8
  },
  
  //  视觉参数
  visual: {
    color: "#00ccff",
    baseOpacity: 0.01,
    blending: THREE.AdditiveBlending
  }
};

本篇总结

** 核心知识点**

  1. 程序化几何体

    • 球面参数化数学
    • 六边形密铺算法
    • 自适应密度控制
    • 局部坐标系建立
  2. 高级着色器

    • 复杂的数学运算
    • 旋转矩阵变换
    • 指数衰减函数
    • 多效果合成
  3. 视觉效果设计

    • 科幻感营造
    • 动态扫光算法
    • 强度衰减控制
    • 拖尾效果实现
  4. 工程化实践

    • 模块化设计
    • 配置驱动开发
    • 性能优化策略
    • 资源管理
相关推荐
GISer_Jing2 小时前
Taro打造电商项目实战
前端·javascript·人工智能·aigc·taro
KLW752 小时前
vue watch监听
前端·javascript·vue.js
晴殇i2 小时前
🎉 TRAE 一年使用的过程体验 🎉
前端
GDAL2 小时前
Tailwind CSS Flex 布局深入全面教程
前端·css·tailwindcss
qq. 28040339842 小时前
react --> redux
前端·react.js·前端框架
前端不太难2 小时前
用 RN 的渲染模型,反推 Vue 列表的正确拆分方式
前端·javascript·vue.js
JS_GGbond2 小时前
防抖与节流:前端性能优化“双剑客”
前端
KLW752 小时前
vue v-if和v-show比较
前端·css·css3
梵尔纳多3 小时前
使用 Electron 实现一个简单的文本编辑器
前端·javascript·electron