学习threejs,结合anime.js打造炫酷文字粒子星空秀

👨‍⚕️ 主页: gis分享者

👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!

👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • [1.1 ☘️anime.js 动画库](#1.1 ☘️anime.js 动画库)
      • [1.1.1 ☘️核心特性](#1.1.1 ☘️核心特性)
      • [1.1.2 ☘️安装与引入](#1.1.2 ☘️安装与引入)
      • [1.1.3 ☘️使用示例](#1.1.3 ☘️使用示例)
  • 二、🍀结合anime.js打造炫酷文字粒子星空秀
    • [1. ☘️实现思路](#1. ☘️实现思路)
    • [2. ☘️代码样例](#2. ☘️代码样例)

一、🍀前言

本文详细介绍如何基于threejs在三维场景中结合anime.js打造炫酷文字粒子星空秀。亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️anime.js 动画库

Anime.js 是一个轻量级(核心文件仅约10KB)的 JavaScript 动画库,专注于为网页元素提供高性能、灵活的动画效果。

anime 官网

1.1.1 ☘️核心特性

广泛兼容性

支持 CSS 属性、DOM 属性、SVG 属性及 JavaScript 对象属性的动画,覆盖从简单位移到复杂路径变形(如 SVG 路径动画)的多种场景。

简洁 API

通过 anime() 函数快速创建动画,参数配置直观,适合快速开发。

高级功能

提供时间轴(Timeline)、交错动画(Stagger)、拖拽交互(Draggable)、响应式动画(Scope API)等工具,满足复杂动画需求。

性能优化

优先使用 transform 和 opacity 等不触发重排的属性,确保动画流畅性。

1.1.2 ☘️安装与引入

CDN 引入:在 HTML 文件中直接添加脚本标签:

javascript 复制代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>

通过 npm 安装 animejs:

javascript 复制代码
npm install animejs

随后在代码中导入:

javascript 复制代码
import anime from 'animejs';

1.1.3 ☘️使用示例

元素平移与旋转:

javascript 复制代码
<div class="box" style="width: 100px; height: 100px; background-color: blue;"></div>
<script>
  anime({
    targets: '.box', // 目标元素选择器
    translateX: 250, // X轴平移距离
    translateY: 250, // Y轴平移距离
    rotate: '1turn', // 旋转一周
    duration: 1000, // 动画持续时间(毫秒)
    easing: 'easeInOutQuad' // 缓动函数
  });
</script>

效果:蓝色方块从左上角平滑移动到右下角,同时旋转一周。

颜色渐变与缩放:

javascript 复制代码
<div class="circle" style="width: 100px; height: 100px; background-color: red; border-radius: 50%; opacity: 0;"></div>
<script>
  anime({
    targets: '.circle',
    opacity: [0, 1], // 透明度从0到1
    scale: [0.5, 1], // 缩放比例从0.5到1
    duration: 1000,
    loop: true, // 循环播放
    direction: 'alternate' // 动画方向交替(正放→倒放→正放...)
  });
</script>

效果:红色圆形淡入并放大至原始大小,循环执行且方向交替。

时间轴(Timeline):编排多个动画按顺序执行。

javascript 复制代码
const timeline = anime.timeline({ loop: true });
timeline
  .add({ targets: '.box1', scale: 1.5, duration: 500 })
  .add({ targets: '.box2', rotate: '1turn', duration: 500 }, '-200'); // 延迟200ms启动

效果:.box1 放大后,.box2 延迟200ms开始旋转,整体循环播放。

交错动画(Stagger):为一组元素添加延迟效果。

javascript 复制代码
anime({
  targets: '.items', // 假设有多个.items元素
  translateX: 100,
  delay: anime.stagger(100) // 每个元素间隔100ms启动
});

效果:多个元素依次向右移动,形成序列感。

SVG 路径动画:

javascript 复制代码
// 假设存在SVG路径元素 <path class="my-path" d="M0,0 L100,0" />
anime.setDashoffset(document.querySelector('.my-path')); // 初始化路径偏移
anime({
  targets: '.my-path',
  strokeDashoffset: [anime.setDashoffset, 0], // 从当前偏移量动画到0
  duration: 1500
});

效果:SVG线条从无到有逐渐绘制完成。

生命周期回调:

javascript 复制代码
anime({
  targets: '.box',
  translateX: 100,
  begin: () => console.log('动画开始'), // 动画启动时触发
  update: (anim) => console.log(`进度: ${anim.progress}%`), // 动画进行时触发
  complete: () => console.log('动画完成') // 动画结束时触发
});

动画控制:

javascript 复制代码
const myAnimation = anime({ /* 参数配置 */ });
myAnimation.pause(); // 暂停动画
myAnimation.play(); // 继续播放
myAnimation.reverse(); // 倒放动画
myAnimation.restart(); // 重新开始

二、🍀结合anime.js打造炫酷文字粒子星空秀

1. ☘️实现思路

集成了 Three.js(渲染)、anime.js (动画库)打造炫酷文字粒子星空秀。具体代码参考代码样例。可以直接运行。

2. ☘️代码样例

html 复制代码
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>打造炫酷文字粒子星空秀</title>
    <style>
        :root {
            --red: hsl(4, 70%, 50%);
        }
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            position: absolute;
            overflow: hidden;
            width: 100%;
            height: 100%;
            background: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: "Arial Black", sans-serif;
        }
        .text-container {
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 15px;
        }
        .letter {
            position: relative;
            width: 100px;
            height: 120px;
        }
        .letter.dot {
            width: 30px;
        }
        .particle {
            position: absolute;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: radial-gradient(
                    circle at 35% 35%,
                    rgba(255, 255, 255, 0.7),
                    rgba(255, 120, 180, 0.8) 20%,
                    rgba(255, 80, 120, 0.7) 35%,
                    rgba(200, 40, 80, 0.6) 50%,
                    rgba(150, 20, 60, 0.4) 70%,
                    transparent
            );
            mix-blend-mode: screen;
            will-change: transform;
            opacity: 0.95;
            box-shadow: 0 0 15px rgba(255, 100, 150, 0.6), 0 0 30px rgba(255, 80, 120, 0.4), 0 0 45px rgba(255, 60, 100, 0.2),
            inset -2px -2px 4px rgba(100, 0, 50, 0.3), inset 2px 2px 4px rgba(255, 255, 255, 0.5);
            filter: contrast(1.1) brightness(1);
        }
        .particle::before {
            content: "";
            position: absolute;
            top: 15%;
            left: 15%;
            width: 25%;
            height: 25%;
            background: radial-gradient(circle, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.3) 40%, transparent 70%);
            border-radius: 50%;
            filter: blur(0.3px);
        }
        .particle::after {
            content: "";
            position: absolute;
            bottom: 15%;
            right: 15%;
            width: 15%;
            height: 15%;
            background: radial-gradient(circle, rgba(255, 200, 255, 0.3), rgba(200, 150, 255, 0.15) 50%, transparent 70%);
            border-radius: 50%;
            filter: blur(0.5px);
        }
    </style>
</head>
<body>
<div class="text-container" id="textContainer"></div>
<!-- Three.js 用于3D行星 -->
<script src="https://unpkg.com/three@0.155.0/build/three.min.js"></script>
<!-- 行星背景容器 -->
<canvas id="planetCanvas" style="position: fixed; top: 0; left: 0; z-index: -1; pointer-events: none"></canvas>
<script type="module">
  import { animate, createTimeline, createTimer, stagger, utils } from "https://esm.sh/animejs";
  const textContainer = document.getElementById("textContainer");
  const viewport = { w: window.innerWidth * 0.5, h: window.innerHeight * 0.5 };
  const cursor = { x: 0, y: 0 };
  let allParticles = [];
  let originalPositions = [];
  // 各字符的粒子坐标(改进版)
  const letterPatterns = {
    a: [
      // 顶点
      [50, 35],
      // 左斜线
      [45, 45],
      [40, 55],
      [35, 65],
      [30, 75],
      [25, 85],
      [20, 90],
      // 右斜线
      [55, 45],
      [60, 55],
      [65, 65],
      [70, 75],
      [75, 85],
      [80, 90],
      // 横线
      [30, 70],
      [40, 70],
      [50, 70],
      [60, 70],
      [70, 70],
    ],
    n: [
      // 左竖线
      [20, 37],
      [20, 45],
      [20, 55],
      [20, 65],
      [20, 75],
      [20, 85],
      [20, 90],
      // 右竖线
      [70, 37],
      [70, 45],
      [70, 55],
      [70, 65],
      [70, 75],
      [70, 85],
      [70, 90],
      // 上曲线
      [20, 37],
      [30, 35],
      [40, 34],
      [50, 34],
      [60, 35],
      [70, 37],
    ],
    i: [
      // 上点
      [45, 20],
      [50, 20],
      [55, 20],
      // 竖线
      [50, 37],
      [50, 45],
      [50, 55],
      [50, 65],
      [50, 75],
      [50, 85],
      [50, 90],
    ],
    m: [
      // 左竖线
      [15, 37],
      [15, 45],
      [15, 55],
      [15, 65],
      [15, 75],
      [15, 85],
      [15, 90],
      // 中竖线
      [45, 37],
      [45, 45],
      [45, 55],
      [45, 65],
      [45, 75],
      [45, 85],
      [45, 90],
      // 右竖线
      [75, 37],
      [75, 45],
      [75, 55],
      [75, 65],
      [75, 75],
      [75, 85],
      [75, 90],
      // 左山(连接部分)
      [15, 37],
      [22, 35],
      [30, 34],
      [38, 35],
      [45, 37],
      // 右山(连接部分)
      [45, 37],
      [52, 35],
      [60, 34],
      [68, 35],
      [75, 37],
    ],
    e: [
      // 中央横线(最重要)
      [20, 60],
      [28, 60],
      [36, 60],
      [44, 60],
      [52, 60],
      [60, 60],
      [68, 60],
      // 上部打开的C形
      [68, 45],
      [60, 40],
      [50, 37],
      [40, 35],
      [30, 37],
      [22, 40],
      [20, 45],
      // 右上连接线(e的特征)
      [68, 45],
      [70, 50],
      [70, 55],
      [68, 60],
      // 左竖线(上半部分)
      [20, 45],
      [20, 50],
      [20, 55],
      [20, 60],
      // 左竖线(下半部分)
      [20, 60],
      [20, 65],
      [20, 70],
      [20, 75],
      [20, 80],
      [20, 85],
      // 下部打开的C形(更低)
      [20, 85],
      [22, 88],
      [30, 90],
      [40, 91],
      [50, 91],
      [60, 90],
      [68, 88],
      [72, 85],
    ],
    ".": [
      // 点(小)
      [12, 85],
      [15, 88],
      [12, 91],
    ],
    j: [
      // 上点
      [45, 20],
      [50, 20],
      [55, 20],
      // 竖线
      [50, 37],
      [50, 45],
      [50, 55],
      [50, 65],
      [50, 75],
      [50, 85],
      [50, 90],
      // 下曲线(钩)
      [45, 95],
      [35, 97],
      [25, 95],
      [20, 90],
      [18, 80],
    ],
    s: [
      // 上曲线(右开)- 与下方空白一致
      [65, 37],
      [55, 35],
      [45, 34],
      [35, 34],
      [25, 35],
      [20, 37],
      [18, 42],
      // 左侧竖线(上半部分)
      [18, 47],
      [18, 52],
      // 中央横线(右向)
      [20, 57],
      [30, 58],
      [40, 59],
      [50, 60],
      [60, 61],
      [65, 63],
      // 右侧竖线(下半部分)
      [70, 66],
      [70, 71],
      [70, 76],
      // 下曲线(左开)- 延伸更低
      [68, 81],
      [60, 86],
      [50, 89],
      [40, 90],
      [30, 90],
      [25, 89],
      [20, 87],
      [18, 84],
      // 最底部对齐其他字
      [22, 90],
      [30, 91],
      [40, 91],
      [50, 91],
      [60, 91],
      [65, 90],
    ],
  };
  const text = "anime.js";
  let currentX = 0;
  // 创建字符
  text.split("").forEach((char, charIndex) => {
    const letterEl = document.createElement("div");
    letterEl.classList.add("letter");
    if (char === ".") {
      letterEl.classList.add("dot");
    }
    const pattern = letterPatterns[char];
    if (!pattern) return;
    pattern.forEach((pos, i) => {
      const particle = document.createElement("div");
      particle.classList.add("particle");
      // 彩虹渐变
      const totalIndex = allParticles.length;
      const hue = (totalIndex / 150) * 360; // 按粒子总数分配彩虹
      const saturation = 100;
      const lightness = 50 + Math.random() * 15;
      // 立体彩虹渐变背景
      particle.style.background = `
          radial-gradient(circle at 35% 35%,
            hsla(${hue}, 100%, 90%, 0.7),
            hsla(${hue}, ${saturation}%, 70%, 0.8) 15%,
            hsla(${hue}, ${saturation}%, ${lightness}%, 0.85) 30%,
            hsla(${hue}, ${saturation}%, ${lightness - 10}%, 0.7) 50%,
            hsla(${hue}, ${saturation - 10}%, ${lightness - 20}%, 0.5) 70%,
            hsla(${hue}, ${saturation - 20}%, ${lightness - 30}%, 0.3) 85%,
            transparent
          )
        `;
      // 彩虹可见的阴影和高光
      particle.style.boxShadow = `
          0 0 15px hsla(${hue}, 100%, ${lightness}%, 0.7),
          0 0 25px hsla(${hue}, 100%, ${lightness - 5}%, 0.5),
          0 0 35px hsla(${hue}, 90%, ${lightness - 10}%, 0.3),
          inset -1px -1px 3px hsla(${hue + 180}, 60%, 25%, 0.4),
          inset 2px 2px 4px hsla(${hue}, 70%, 85%, 0.6)
        `;
      particle.style.filter = `contrast(1.1) brightness(1.05) saturate(1.3)`;
      letterEl.appendChild(particle);
      allParticles.push(particle);
      // 保存原始位置
      originalPositions.push({ x: 0, y: 0, letterIndex: charIndex });
      // 设置初始位置
      utils.set(particle, {
        left: pos[0] + "px",
        top: pos[1] + "px",
        translateX: "-50%",
        translateY: "-50%",
        scale: 0.8 + Math.random() * 0.4,
        opacity: 0.8 + Math.random() * 0.2,
        x: 0,
        y: 0,
      });
    });
    textContainer.appendChild(letterEl);
  });
  console.log(`生成 ${allParticles.length} 个 anime.js 粒子`);
  // 脉冲动画
  const pulse = () => {
    animate(allParticles, {
      scale: [1, 1.5, 1],
      opacity: [0.8, 1, 0.8],
      duration: 1000,
      delay: stagger(10),
      ease: "inOutQuad",
    });
  };
  // 主循环 - 按字母顺序排列
  const mainLoop = createTimer({
    frameRate: 15,
    onUpdate: () => {
      animate(allParticles, {
        x: cursor.x,
        y: cursor.y,
        delay: stagger(30, { from: "first" }), // 从第一个字母开始
        duration: 600, // 固定时间移动
        ease: "outQuart", // 平滑移动
        composition: "blend",
      });
    },
  });
  // 自动移动动画
  const autoMove = createTimeline()
    .add(
      cursor,
      {
        x: [-viewport.w * 0.45, viewport.w * 0.45],
        modifier: (x) => x + Math.sin(mainLoop.currentTime * 0.0007) * viewport.w * 0.5,
        duration: 3000,
        ease: "inOutExpo",
        alternate: true,
        loop: true,
        onBegin: pulse,
        onLoop: pulse,
      },
      0
    )
    .add(
      cursor,
      {
        y: [-viewport.h * 0.45, viewport.h * 0.45],
        modifier: (y) => y + Math.cos(mainLoop.currentTime * 0.00012) * viewport.h * 0.5,
        duration: 1000,
        ease: "inOutQuad",
        alternate: true,
        loop: true,
      },
      0
    );
  // 手动操作定时器 - 暂停动画
  const manualMovementTimeout = createTimer({
    duration: 2000,
    onComplete: () => {
      // 在光标位置停止
    },
  });
  // 鼠标跟随
  const followPointer = (e) => {
    const event = e.type === "touchmove" ? e.touches[0] : e;
    cursor.x = event.pageX - window.innerWidth / 2;
    cursor.y = event.pageY - window.innerHeight / 2;
    autoMove.pause();
    manualMovementTimeout.restart();
  };
  document.addEventListener("mousemove", followPointer);
  document.addEventListener("touchmove", followPointer);
  // 初始动画
  animate(allParticles, {
    opacity: [0, 0.9],
    scale: [0, 1],
    translateX: [stagger([-100, 100]), 0],
    translateY: [stagger([-100, 100]), 0],
    duration: 2000,
    delay: stagger(8, { from: "first" }),
    ease: "outElastic(1, 0.6)",
    complete: () => {
      pulse();
    },
  });
  // 持续闪烁动画
  allParticles.forEach((particle, i) => {
    animate(particle, {
      opacity: [0.9, 1, 0.9],
      scale: [1, 1.1, 1],
      duration: 2500 + Math.random() * 2000,
      delay: Math.random() * 2000,
      loop: true,
      ease: "inOutSine",
    });
  });
  // Three.js 初始化行星背景
  const canvas = document.getElementById("planetCanvas");
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    alpha: true,
    antialias: true,
    powerPreference: "high-performance",
  });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.0;
  // 地球几何体和材质(着色器)
  const earthGeometry = new THREE.SphereGeometry(3, 256, 256);
  // NASA 高分辨率地球纹理加载器
  const textureLoader = new THREE.TextureLoader();
  // 高分辨率地球纹理(8K) - 带备用纹理
  const earthDayTexture = textureLoader.load("https://unpkg.com/three-globe@2.24.3/example/img/earth-blue-marble.jpg", undefined, undefined, () =>
    textureLoader.load("https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73909/world.topo.bathy.200412.3x5400x2700.jpg")
  );
  const earthNightTexture = textureLoader.load("https://unpkg.com/three-globe@2.24.3/example/img/earth-night.jpg", undefined, undefined, () =>
    textureLoader.load("https://eoimages.gsfc.nasa.gov/images/imagerecords/144000/144898/BlackMarble_2016_3km.jpg")
  );
  const earthCloudsTexture = textureLoader.load("https://unpkg.com/three-globe@2.24.3/example/img/earth-water.png", undefined, undefined, () =>
    textureLoader.load("https://eoimages.gsfc.nasa.gov/images/imagerecords/57000/57747/cloud_combined_2048.jpg")
  );
  const earthBumpTexture = textureLoader.load("https://unpkg.com/three-globe@2.24.3/example/img/earth-topology.png", undefined, undefined, () =>
    textureLoader.load("https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73934/gebco_08_rev_elev_A2_grey_geo.tif")
  );
  // 设置纹理
  [earthDayTexture, earthNightTexture, earthCloudsTexture, earthBumpTexture].forEach((texture) => {
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.anisotropy = 16; // 高质量过滤
    texture.generateMipmaps = true;
    texture.minFilter = THREE.LinearMipmapLinearFilter;
    texture.magFilter = THREE.LinearFilter;
  });
  // 新建简单稳定地球材质
  const earthMaterial = new THREE.MeshPhongMaterial({
    map: earthDayTexture,
    bumpMap: earthBumpTexture,
    bumpScale: 0.1,
    shininess: 0,
  });
  // 海洋蓝色附加层
  const oceanMaterial = new THREE.ShaderMaterial({
    uniforms: {
      uDayTexture: { value: earthDayTexture },
    },
    vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
    fragmentShader: `
        uniform sampler2D uDayTexture;
        varying vec2 vUv;

        void main() {
          vec3 texColor = texture2D(uDayTexture, vUv).rgb;

          // 海洋判断: 蓝色多,绿红少
          float isOcean = step(0.5, texColor.b) * step(texColor.r, 0.3) * step(texColor.g, 0.4);

          // 海洋部分替换为深蓝
          vec3 oceanColor = vec3(0.1, 0.3, 0.7);
          vec3 finalColor = mix(texColor, oceanColor, isOcean);

          gl_FragColor = vec4(finalColor, 1.0);
        }
      `,
    transparent: false,
  });
  // 月球着色器材质(高分辨率)
  const moonGeometry = new THREE.SphereGeometry(0.8, 128, 128);
  // NASA 月球高分辨率纹理 - 带备用
  const moonTexture = textureLoader.load("https://unpkg.com/three-globe@2.24.3/example/img/moon.jpg", undefined, undefined, () =>
    textureLoader.load("https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_poles_1k.jpg")
  );
  const moonBumpTexture = textureLoader.load("https://unpkg.com/three-globe@2.24.3/example/img/moon.jpg", undefined, undefined, () =>
    textureLoader.load("https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_poles_1k.jpg")
  );
  [moonTexture, moonBumpTexture].forEach((texture) => {
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.anisotropy = 16;
    texture.generateMipmaps = true;
    texture.minFilter = THREE.LinearMipmapLinearFilter;
    texture.magFilter = THREE.LinearFilter;
  });
  const moonMaterial = new THREE.ShaderMaterial({
    uniforms: {
      time: { value: 0.0 },
      sunDirection: { value: new THREE.Vector3(-1, 0.5, 1).normalize() },
      uMoonTexture: { value: moonTexture },
      uMoonBump: { value: moonBumpTexture },
    },
    vertexShader: `
        varying vec3 vNormal;
        varying vec3 vPosition;
        varying vec2 vUv;
        varying vec3 vViewPosition;

        void main() {
          vNormal = normalize(normalMatrix * normal);
          vPosition = position;
          vUv = uv;

          vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
          vViewPosition = -mvPosition.xyz;
          gl_Position = projectionMatrix * mvPosition;
        }
      `,
    fragmentShader: `
        uniform float time;
        uniform vec3 sunDirection;
        uniform sampler2D uMoonTexture;
        uniform sampler2D uMoonBump;
        varying vec3 vNormal;
        varying vec3 vPosition;
        varying vec2 vUv;
        varying vec3 vViewPosition;

        void main() {
          vec3 normal = normalize(vNormal);
          vec3 lightDir = normalize(sunDirection);

          // 使用NASA月球纹理
          vec3 moonColor = texture2D(uMoonTexture, vUv).rgb;

          // 凹凸贴图显示月球地形
          float bumpValue = texture2D(uMoonBump, vUv).r;
          vec3 bumpNormal = normal + (bumpValue - 0.5) * 0.3;
          bumpNormal = normalize(bumpNormal);

          // 阳光照明计算
          float lightIntensity = dot(bumpNormal, lightDir);
          float NdotL = max(0.0, lightIntensity);

          // 阴阳分界线
          float terminator = smoothstep(-0.1, 0.1, lightIntensity);

          // 月相
          float phaseAngle = dot(normalize(vPosition), lightDir);
          float phase = smoothstep(-0.4, 0.4, phaseAngle);

          // 月球真实反照率
          float albedo = 0.12; // 月球平均反照率

          // 最终照明
          float ambientLight = 0.02; // 太空环境光
          float diffuseLight = NdotL * albedo;

          // 调整月球颜色
          moonColor *= vec3(1.1, 1.0, 0.9);

          vec3 finalColor = moonColor * (ambientLight + diffuseLight * terminator * phase);

          // 地球反射光
          float earthShine = (1.0 - terminator) * 0.08;
          finalColor += moonColor * earthShine * vec3(0.3, 0.4, 0.6);

          // 伽马校正
          finalColor = pow(finalColor, vec3(1.0/2.2));

          gl_FragColor = vec4(finalColor, 1.0);
        }
      `,
  });
  // 创建地球和月球网格(使用海洋材质)
  const earth = new THREE.Mesh(earthGeometry, oceanMaterial);
  const moon = new THREE.Mesh(moonGeometry, moonMaterial);
  // 地球位置(右下)
  earth.position.set(5, -3, -15);
  // 月球位置(左上)
  moon.position.set(-6, 4, -12);
  // 星空背景
  const starsGeometry = new THREE.BufferGeometry();
  const starsMaterial = new THREE.PointsMaterial({
    color: 0xffffff,
    size: 2,
    sizeAttenuation: false,
    transparent: true,
    opacity: 0.8,
  });
  const starsVertices = [];
  for (let i = 0; i < 2000; i++) {
    const x = (Math.random() - 0.5) * 2000;
    const y = (Math.random() - 0.5) * 2000;
    const z = (Math.random() - 0.5) * 2000;
    starsVertices.push(x, y, z);
  }
  starsGeometry.setAttribute("position", new THREE.Float32BufferAttribute(starsVertices, 3));
  const stars = new THREE.Points(starsGeometry, starsMaterial);
  // 地球大气光(辉光效果)
  const atmosphereGeometry = new THREE.SphereGeometry(3.1, 64, 64);
  const atmosphereMaterial = new THREE.ShaderMaterial({
    uniforms: {
      viewVector: { value: camera.position },
    },
    vertexShader: `
        uniform vec3 viewVector;
        varying float intensity;
        void main() {
          vec3 vNormal = normalize(normalMatrix * normal);
          vec3 vNormel = normalize(normalMatrix * viewVector);
          intensity = pow(0.6 - dot(vNormal, vNormel), 2.0);
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
    fragmentShader: `
        varying float intensity;
        void main() {
          vec3 glow = vec3(0.3, 0.6, 1.0) * intensity;
          gl_FragColor = vec4(glow, intensity * 0.8);
        }
      `,
    side: THREE.BackSide,
    blending: THREE.AdditiveBlending,
    transparent: true,
  });
  const earthAtmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
  earthAtmosphere.position.copy(earth.position);
  // 更真实的光照
  const ambientLight = new THREE.AmbientLight(0x404040, 0.1);
  const sunLight = new THREE.DirectionalLight(0xffffff, 1.0);
  sunLight.position.set(-10, 5, 8);
  sunLight.castShadow = true;
  // 添加到场景
  scene.add(earth);
  scene.add(earthAtmosphere);
  scene.add(moon);
  scene.add(stars);
  scene.add(ambientLight);
  scene.add(sunLight);
  // 摄像机位置
  camera.position.z = 10;
  // 动画循环
  let planetTime = 0;
  function animatePlanets() {
    planetTime += 0.01;
    // 地球自转
    earth.rotation.y += 0.01;
    // 月球自转和公转
    moon.rotation.y += 0.02;
    moon.position.x = -6 + Math.cos(planetTime * 0.5) * 2;
    moon.position.y = 4 + Math.sin(planetTime * 0.5) * 1;
    // 更新着色器uniform
    moonMaterial.uniforms.time.value = planetTime;
    // 星空微旋转
    stars.rotation.y += 0.0005;
    stars.rotation.x += 0.0002;
    renderer.render(scene, camera);
    requestAnimationFrame(animatePlanets);
  }
  animatePlanets();
  // 窗口大小变化响应
  window.addEventListener("resize", () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });
</script>
</body>
</html>

效果如下

相关推荐
gis分享者3 天前
学习threejs,生成复杂3D迷宫游戏
学习·游戏·3d·threejs·cannon·迷宫·cannon-es
沟通QQ8762239655 天前
含分布式电源配电网潮流计算及相关实践
动画
by__csdn8 天前
大前端:定义、演进与实践全景解析
前端·javascript·vue.js·react.js·typescript·ecmascript·动画
韩曙亮8 天前
【Web APIs】JavaScript 动画 ② ( 缓动动画 | 步长计算取整 )
前端·javascript·动画·web apis·缓动动画·匀速动画
lrh30259 天前
Custom SRP - 15 Particles
unity·渲染管线·粒子·srp·扰动效果
Irene199115 天前
CSS Animation 详解
css·动画
gis分享者15 天前
学习threejs,使用自定义GLSL 着色器,实现抽象艺术特效
threejs·着色器·glsl·shadermaterial·抽象艺术
智算菩萨16 天前
【3D建模】人体投弹动作的3D建模与实时动作演示系统
数学建模·3d·动画
码界奇点18 天前
Java大数据在智能教育个性化学习资源推荐中的冷启动解决方案
java·大数据·学习·动画·推荐算法