Canvas-六边形小球实验

对比主流模型的物理世界理解

8 个模型 · 百模竞速 · TOKRACE · 分享快照
分享与嵌入

社交平台直接发布;GitHub/README 使用 Markdown 或 Badge。

XFacebook微博LinkedIn
[![Canvas-六边形小球实验 在 TOKRACE 上的速度结果](/api/badge/share/rkzlSkthfw?locale=zh-CN)](/zh-CN/r/rkzlSkthfw)
写一个单文件 HTML(只输出完整代码,不要解释)。在 <head> 用 importmap 引入 Three.js(把 `three` 映射到 `https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js`、`three/addons/` 映射到 `https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/`),再用 <script type="module"> 构建可运行 3D 场景:缓慢自转的发光水晶核心、环绕粒子、合理的光照与材质、用 OrbitControls 支持鼠标拖拽旋转与滚轮缩放、canvas 铺满视口并随窗口自适应;若脚本加载失败,在页面上显示明确的错误文字。
复跑这次评测完整复跑会带走标题、备注、Prompt 和模型;Prompt 复跑只复用任务文本。
显示
🏁 实时竞速思考 / 输出 token速度 t/s · 基准 300
智谱 GLM-5.2
🏁
16725
82 t/s
DeepSeek
🏁
13862
149 t/s
DeepSeek
🏁
12268
111 t/s
MiniMax-M3
🏁
10013
137 t/s
Kimi 月之暗面-high-think-budget
🏁
5703
79 t/s
小米 MiMo-V2.5 Pro
🏁
4568
85 t/s
Kimi 月之暗面-K3
🏁
3703
35 t/s
小米 MiMo-V2.5
🏁
2361
100 t/s
🏁 本轮结算
🏆 最快
DeepSeek
149 tok/s
💰 最省
小米 MiMo-V2.5
≈$0.00071
综合推荐
小米 MiMo-V2.5
最便宜
Kimi 月之暗面-high-think-budget
kimi-for-coding
完成
We need respond with full HTML single file, no explanation. Need satisfy user requirements: importmap in head maps three and three/addons. Script module constructs 3D scene: slowly rotating glowing crystal core, orbiting particles, reasonable lighting/materials, OrbitControls for drag and zoom, canvas full viewport and resizes. If script load fails, display explicit error text. We must produce only code. The user explicitly said "只输出完整代码,不要解释". So just output HTML code. Need ensure error handling for module loading failure. Since script type=module, import errors can be caught by try/catch inside module, display on screen. Also maybe onerror on script? But module scripts don't support onerror for inline? For external modules can onerror. Here imports inside module may fail. We can wrap in try/catch. Construct scene: - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; Use full window width/height. Scene with dark background. Add ambient and point lights. Crystal core: use IcosahedronGeometry or OctahedronGeometry, MeshPhysicalMaterial with transmission, clearcoat, emissive? glowing core. Could add inner point light. Add a point light inside core or at core. Add bloom? Need post-processing? Could import EffectComposer? Simpler: use emissive material and additive blending particles. Use MeshStandardMaterial with emissive and transparent. Or MeshPhysicalMaterial with transmission. Slowly self-rotate core: in animate. OrbitControls: enableDamping, autoRotate maybe false. Particles: create BufferGeometry, positions for many particles in sphere, PointsMaterial with color/size and transparent. Animate them rotating around core, possibly with different phases. Need orbiting particles. Window resize: add event listener. Error handling: if import fails, catch show error div. Also if Three.js throws maybe. Need to create root div for error. CSS full-screen. Let's craft complete HTML. Need ensure importmap syntax correct. < script type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> Use module script. Let's write the code elegantly. Since user requested only code, no explanations. Make sure no extra text outside. One subtlety: The CDN path for three/addons/ mapping may need trailing slash, yes. The import statement uses 'three/addons/controls/OrbitControls.js' which resolves to the base URL + controls/OrbitControls.js. Good. Potential CORS? assume works. Need explicit error text if script load fails. Implement a function showError(msg) that sets a div with text. Also display on script failure. Use try/catch around main. Let's produce: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>发光水晶核心</title> <style> * { margin:0; padding:0; box-sizing:border-box; } html, body { width:100%; height:100%; overflow:hidden; background:#000; } #canvas-container { width:100%; height:100%; } #error { display:none; position:fixed; inset:0; background:#000; color:#f55; font-family: sans-serif; justify-content:center; align-items:center; text-align:center; padding:20px; z-index:10; } #error.visible { display:flex; } #error h1 { font-size:1.5rem; margin-bottom:0.5em; } </style> <script type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> </head> <body> <div id="canvas-container"></div> <div id="error"> <div> <h1>3D 场景加载失败</h1> <p id="error-message">无法初始化 Three.js 或相关脚本。</p> <p>请检查网络连接或浏览器是否支持 ES 模块。</p> </div> </div> <script type="module"> const errorEl = document.getElementById('error'); const errorMsg = document.getElementById('error-message'); function showError(msg) { if (errorMsg) errorMsg.textContent = msg; if (errorEl) errorEl.classList.add('visible'); } try { import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const container = document.getElementById('canvas-container'); const scene = new THREE.Scene(); scene.background = new THREE.Color(0x050510); scene.fog = new THREE.FogExp2(0x050510, 0.02); const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 0, 6); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2; container.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.minDistance = 2.5; controls.maxDistance = 15; // Lights const ambientLight = new THREE.AmbientLight(0x404060, 0.6); scene.add(ambientLight); const pointLight = new THREE.PointLight(0x66ccff, 2, 20); pointLight.position.set(0, 0, 0); scene.add(pointLight); const rimLight = new THREE.DirectionalLight(0xff66cc, 1.5); rimLight.position.set(5, 3, 5); scene.add(rimLight); // Crystal core const geometry = new THREE.IcosahedronGeometry(1, 0); const material = new THREE.MeshPhysicalMaterial({ color: 0x00aaff, emissive: 0x0044aa, emissiveIntensity: 0.6, metalness: 0.1, roughness: 0.15, transmission: 0.4, thickness: 1.5, clearcoat: 1, clearcoatRoughness: 0.1, ior: 1.7, side: THREE.DoubleSide }); const core = new THREE.Mesh(geometry, material); scene.add(core); // Wireframe overlay const wireGeo = new THREE.IcosahedronGeometry(1.02, 0); const wireMat = new THREE.MeshBasicMaterial({ color: 0x88ddff, wireframe: true, transparent: true, opacity: 0.3 }); const wire = new THREE.Mesh(wireGeo, wireMat); scene.add(wire); // Inner glow sprite const spriteMat = new THREE.SpriteMaterial({ color: 0x66ccff, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending }); const sprite = new THREE.Sprite(spriteMat); sprite.scale.set(4, 4, 1); scene.add(sprite); // Orbiting particles const particleCount = 600; const positions = new Float32Array(particleCount * 3); const radii = new Float32Array(particleCount); const speeds = new Float32Array(particleCount); const angles = new Float32Array(particleCount); const tilts = new Float32Array(particleCount); for (let i = 0; i < particleCount; i++) { radii[i] = 1.6 + Math.random() * 2.5; speeds[i] = 0.2 + Math.random() * 0.5; angles[i] = Math.random() * Math.PI * 2; tilts[i] = (Math.random() - 0.5) * Math.PI; const x = Math.cos(angles[i]) * radii[i]; const z = Math.sin(angles[i]) * radii[i]; const y = Math.sin(tilts[i]) * (0.2 + Math.random() * 0.8); positions[i * 3] = x; positions[i * 3 + 1] = y; positions[i * 3 + 2] = z; } const pGeo = new THREE.BufferGeometry(); pGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const pMat = new THREE.PointsMaterial({ color: 0x88ddff, size: 0.04, transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, sizeAttenuation: true }); const particles = new THREE.Points(pGeo, pMat); scene.add(particles); // Stars const starCount = 800; const starPositions = new Float32Array(starCount * 3); for (let i = 0; i < starCount; i++) { const r = 20 + Math.random() * 30; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); starPositions[i * 3] = r * Math.sin(phi) * Math.cos(theta); starPositions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta); starPositions[i * 3 + 2] = r * Math.cos(phi); } const starGeo = new THREE.BufferGeometry(); starGeo.setAttribute('position', new THREE.BufferAttribute(starPositions, 3)); const starMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.06, transparent: true, opacity: 0.6 }); const stars = new THREE.Points(starGeo, starMat); scene.add(stars); window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const dt = clock.getDelta(); const t = clock.getElapsedTime(); core.rotation.y += dt * 0.2; core.rotation.x += dt * 0.1; wire.rotation.copy(core.rotation); const posAttr = pGeo.attributes.position; for (let i = 0; i < particleCount; i++) { angles[i] += speeds[i] * dt * 0.5; const r = radii[i]; const x = Math.cos(angles[i]) * r; const z = Math.sin(angles[i]) * r; const y = Math.sin(tilts[i] + t * 0.2) * (0.2 + Math.sin(i) * 0.6); posAttr.setXYZ(i, x, y, z); } posAttr.needsUpdate = true; particles.rotation.y = t * 0.05; controls.update(); renderer.render(scene, camera); } animate(); } catch (e) { showError('脚本执行错误:' + (e && e.message ? e.message : String(e))); } </script> </body> </html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发光水晶核心</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
  #canvas-container { width: 100%; height: 100%; }
  #error {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.95);
    color: #ff5555;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 20px;
    z-index: 10;
  }
  #error.visible { display: flex; }
  #error h1 { font-size: 1.6rem; margin-bottom: 0.5em; }
  #error p { font-size: 1rem; margin: 0.3em 0; }
</style>
<script type="importmap">
{
  "imports": {
    "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
    "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
  }
}
</script>
</head>
<body>
<div id="canvas-container"></div>
<div id="error">
  <div>
    <h1>3D 场景加载失败</h1>
    <p id="error-message">无法初始化 Three.js 或相关脚本。</p>
    <p>请检查网络连接、CDN 可访问性,或浏览器是否支持 ES 模块 / importmap。</p>
  </div>
</div>
<script type="module">
  const errorEl = document.getElementById('error');
  const errorMsg = document.getElementById('error-message');
  function showError(msg) {
    if (errorMsg) errorMsg.textContent = msg;
    if (errorEl) errorEl.classList.add('visible');
  }
  window.addEventListener('error', (e) => showError('全局错误:' + e.message));
  window.addEventListener('unhandledrejection', (e) => showError('未处理 Promise:' + (e.reason && e.reason.message ? e.reason.message : e.reason)));

  (async function init() {
    try {
      const THREE = await import('three');
      const { OrbitControls } = await import('three/addons/controls/OrbitControls.js');

      const container = document.getElementById('canvas-container');
      if (!container) { showError('找不到 canvas-container 容器'); return; }

      const scene = new THREE.Scene();
      scene.background = new THREE.Color(0x050510);
      scene.fog = new THREE.FogExp2(0x050510, 0.02);

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

      const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.toneMapping = THREE.ACESFilmicToneMapping;
      renderer.toneMappingExposure = 1.2;
      container.appendChild(renderer.domElement);

      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.minDistance = 2.5;
      controls.maxDistance = 15;

      const ambientLight = new THREE.AmbientLight(0x404060, 0.6);
      scene.add(ambientLight);

      const coreLight = new THREE.PointLight(0x66ccff, 2, 20);
      coreLight.position.set(0, 0, 0);
      scene.add(coreLight);

      const rimLight = new THREE.DirectionalLight(0xff66cc, 1.5);
      rimLight.position.set(5, 3, 5);
      scene.add(rimLight);

      const coreGeo = new THREE.IcosahedronGeometry(1, 0);
      const coreMat = new THREE.MeshPhysicalMaterial({
        color: 0x00aaff,
        emissive: 0x0044aa,
        emissiveIntensity: 0.6,
        metalness: 0.1,
        roughness: 0.15,
        transmission: 0.4,
        thickness: 1.5,
        clearcoat: 1,
        clearcoatRoughness: 0.1,
        ior: 1.7,
        side: THREE.DoubleSide
      });
      const core = new THREE.Mesh(coreGeo, coreMat);
      scene.add(core);

      const wireGeo = new THREE.IcosahedronGeometry(1.02, 0);
      const wireMat = new THREE.MeshBasicMaterial({ color: 0x88ddff, wireframe: true, transparent: true, opacity: 0.3 });
      const wire = new THREE.Mesh(wireGeo, wireMat);
      scene.add(wire);

      const glowMat = new THREE.SpriteMaterial({
        color: 0x66ccff,
        transparent: true,
        opacity: 0.35,
        blending: THREE.AdditiveBlending
      });
      const glow = new THREE.Sprite(glowMat);
      glow.scale.set(4, 4, 1);
      scene.add(glow);

      const particleCount = 600;
      const positions = new Float32Array(particleCount * 3);
      const radii = new Float32Array(particleCount);
      const speeds = new Float32Array(particleCount);
      const angles = new Float32Array(particleCount);
      const tilts = new Float32Array(particleCount);
      for (let i = 0; i < particleCount; i++) {
        radii[i] = 1.6 + Math.random() * 2.5;
        speeds[i] = 0.2 + Math.random() * 0.5;
        angles[i] = Math.random() * Math.PI * 2;
        tilts[i] = (Math.random() - 0.5) * Math.PI;
        positions[i * 3] = Math.cos(angles[i]) * radii[i];
        positions[i * 3 + 1] = Math.sin(tilts[i]) * (0.2 + Math.random() * 0.8);
        positions[i * 3 + 2] = Math.sin(angles[i]) * radii[i];
      }
      const pGeo = new THREE.BufferGeometry();
      pGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      const pMat = new THREE.PointsMaterial({
        color: 0x88ddff,
        size: 0.04,
        transparent: true,
        opacity: 0.8,
        blending: THREE.AdditiveBlending,
        sizeAttenuation: true
      });
      const particles = new THREE.Points(pGeo, pMat);
      scene.add(particles);

      const starCount = 800;
      const starPos = new Float32Array(starCount * 3);
      for (let i = 0; i < starCount; i++) {
        const r = 20 + Math.random() * 30;
        const theta = Math.random() * Math.PI * 2;
        const phi = Math.acos(2 * Math.random() - 1);
        starPos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
        starPos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
        starPos[i * 3 + 2] = r * Math.cos(phi);
      }
      const starGeo = new THREE.BufferGeometry();
      starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
      const starMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.06, transparent: true, opacity: 0.6, sizeAttenuation: true });
      const stars = new THREE.Points(starGeo, starMat);
      scene.add(stars);

      window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      });

      const clock = new THREE.Clock();
      function animate() {
        requestAnimationFrame(animate);
        const dt = clock.getDelta();
        const t = clock.getElapsedTime();

        core.rotation.y += dt * 0.2;
        core.rotation.x += dt * 0.1;
        wire.rotation.copy(core.rotation);

        const pos = pGeo.attributes.position;
        for (let i = 0; i < particleCount; i++) {
          angles[i] += speeds[i] * dt * 0.5;
          const r = radii[i];
          const x = Math.cos(angles[i]) * r;
          const z = Math.sin(angles[i]) * r;
          const y = Math.sin(tilts[i] + t * 0.2) * (0.2 + Math.sin(i) * 0.6);
          pos.setXYZ(i, x, y, z);
        }
        pos.needsUpdate = true;
        particles.rotation.y = t * 0.05;

        controls.update();
        renderer.render(scene, camera);
      }
      animate();
    } catch (e) {
      showError('脚本加载或执行失败:' + (e && e.message ? e.message : String(e)));
    }
  })();
</script>
</body>
</html>
1.30s
首Token
69
思考TPS
53.4s · ≈3,667 tok
79
输出TPS
25.7s · ≈2,036 tok
5,703
总Tokens(官方)
输入 160
总用时 81.3s · 平均 72 tok/s · 峰值 211 tok/s · 成本 $0.023
Kimi 月之暗面-K3#4
k3
完成
The user wants a single-file HTML with: - importmap in head mapping three to npmmirror registry - module script building a 3D scene - Slowly rotating glowing crystal core - Orbiting particles - Proper lighting and materials - OrbitControls with mouse drag rotation and scroll zoom - Canvas fullscreen and responsive - Error text displayed if script loading fails I need to output only complete code, no explanation. Let me write this: ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>发光水晶核心</title> <style>...</style> <script type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> </head> <body> <div id="error">...</div> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; ... </script> </body> </html> ``` For error handling: since module scripts fail silently-ish, I can add window.addEventListener('error') and unhandledrejection, plus a fallback timeout that checks if scene initialized. Show error div if not initialized after some seconds. Crystal core: use an icosahedron or octahedron geometry with MeshPhysicalMaterial (transmission, emissive). Add inner glow mesh, point light inside. Particles: Points with buffer geometry orbiting. Let me build: - Scene, fog maybe - Camera perspective - Renderer with antialias, tone mapping ACESFilmic, outputColorSpace SRGB - Crystal: OctahedronGeometry(1.2, 0) with MeshPhysicalMaterial { color, metalness:0.1, roughness:0.05, transmission: 0.9, thickness: 1.5, emissive, emissiveIntensity, transparent... } Transmission can be heavy but fine. Or use MeshStandardMaterial with emissive for simplicity and reliability. I'll use MeshPhysicalMaterial with some transmission and iridescence maybe. Keep it safe. - Inner glow: smaller icosahedron with MeshBasicMaterial additive. - PointLight at center color cyan. - Particles: create N=1500 points in a spherical shell with random radii, store angles and radii, rotate group slowly. Additive blending, small size, vertex colors. Lighting: ambient light low, directional light, point light inside crystal, maybe rim lights (two colored point lights). OrbitControls: enableDamping, autoRotate false, min/max distance. Resize handler. Error: window error events + flag. Also add 'load' timeout check. Write the code carefully. Only output code.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发光水晶核心 · Crystal Core</title>
<style>
  html, body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: #05060a;
  }
  #app {
    position: fixed;
    inset: 0;
  }
  #app canvas {
    display: block;
    width: 100%;
    height: 100%;
  }
  #error-box {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 100;
    max-width: 80vw;
    padding: 20px 28px;
    background: rgba(20, 8, 12, 0.92);
    border: 1px solid #ff4d6d;
    border-radius: 10px;
    color: #ffb3c1;
    font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
    font-size: 15px;
    line-height: 1.7;
    text-align: center;
    white-space: pre-wrap;
    box-shadow: 0 0 40px rgba(255, 77, 109, 0.35);
  }
  #hint {
    position: fixed;
    left: 50%;
    bottom: 22px;
    transform: translateX(-50%);
    z-index: 10;
    color: rgba(160, 200, 255, 0.55);
    font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
    font-size: 13px;
    letter-spacing: 2px;
    user-select: none;
    pointer-events: none;
  }
</style>
<script type="importmap">
{
  "imports": {
    "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
    "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
  }
}
</script>
</head>
<body>
<div id="app"></div>
<div id="error-box"></div>
<div id="hint">拖拽旋转 · 滚轮缩放</div>

<script>
  // 错误提示:脚本加载失败或运行异常时在页面上显示明确错误文字
  window.__sceneReady = false;
  window.__showError = function (msg) {
    var box = document.getElementById('error-box');
    box.style.display = 'block';
    box.textContent = '⚠ 3D 场景加载失败\n' + msg +
      '\n请检查网络连接(需要访问 registry.npmmirror.com 加载 Three.js)后刷新页面。';
  };
  window.addEventListener('error', function (e) {
    if (!window.__sceneReady) {
      window.__showError(e.message || (e.target && e.target.src ? '资源加载失败: ' + e.target.src : '未知脚本错误'));
    }
  }, true);
  window.addEventListener('unhandledrejection', function (e) {
    if (!window.__sceneReady) {
      window.__showError((e.reason && e.reason.message) || String(e.reason) || '模块加载被拒绝');
    }
  });
  setTimeout(function () {
    if (!window.__sceneReady) {
      window.__showError('Three.js 模块加载超时(超过 15 秒)');
    }
  }, 15000);
</script>

<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

try {
  // ---------- 渲染器 / 场景 / 相机 ----------
  const container = document.getElementById('app');

  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.15;
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  container.appendChild(renderer.domElement);

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x05060a);
  scene.fog = new THREE.FogExp2(0x05060a, 0.028);

  const camera = new THREE.PerspectiveCamera(
    55, window.innerWidth / window.innerHeight, 0.1, 200
  );
  camera.position.set(0, 2.2, 8);

  // ---------- 控制器 ----------
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = 0.06;
  controls.enablePan = false;
  controls.minDistance = 3;
  controls.maxDistance = 25;
  controls.autoRotate = false;

  // ---------- 光照 ----------
  scene.add(new THREE.AmbientLight(0x223355, 0.6));

  const keyLight = new THREE.DirectionalLight(0xffffff, 1.2);
  keyLight.position.set(5, 8, 6);
  scene.add(keyLight);

  const rimLight = new THREE.PointLight(0xff55aa, 30, 40);
  rimLight.position.set(-6, 2, -5);
  scene.add(rimLight);

  const fillLight = new THREE.PointLight(0x3366ff, 20, 40);
  fillLight.position.set(6, -3, -4);
  scene.add(fillLight);

  // 核心内部光源,让水晶由内而外发光
  const coreLight = new THREE.PointLight(0x66eaff, 50, 30);
  coreLight.position.set(0, 0, 0);
  scene.add(coreLight);

  // ---------- 发光水晶核心 ----------
  const crystalGroup = new THREE.Group();
  scene.add(crystalGroup);

  // 外壳:通透的物理材质
  const crystalGeo = new THREE.OctahedronGeometry(1.35, 0);
  const crystalMat = new THREE.MeshPhysicalMaterial({
    color: 0x88ddff,
    metalness: 0.05,
    roughness: 0.08,
    transmission: 0.85,
    thickness: 1.6,
    ior: 1.8,
    emissive: 0x2288cc,
    emissiveIntensity: 0.55,
    clearcoat: 1.0,
    clearcoatRoughness: 0.1,
    flatShading: true
  });
  const crystal = new THREE.Mesh(crystalGeo, crystalMat);
  crystalGroup.add(crystal);

  // 内核:高亮发光体
  const innerGeo = new THREE.IcosahedronGeometry(0.55, 1);
  const innerMat = new THREE.MeshBasicMaterial({
    color: 0xbffcff,
    transparent: true,
    opacity: 0.95
  });
  const innerCore = new THREE.Mesh(innerGeo, innerMat);
  crystalGroup.add(innerCore);

  // 光晕壳:加法混合的辉光层
  const glowGeo = new THREE.IcosahedronGeometry(1.7, 2);
  const glowMat = new THREE.MeshBasicMaterial({
    color: 0x2288ff,
    transparent: true,
    opacity: 0.08,
    blending: THREE.AdditiveBlending,
    side: THREE.BackSide,
    depthWrite: false
  });
  const glowShell = new THREE.Mesh(glowGeo, glowMat);
  crystalGroup.add(glowShell);

  // ---------- 环绕粒子 ----------
  const PARTICLE_COUNT = 2200;
  const particleGroup = new THREE.Group();
  scene.add(particleGroup);

  const positions = new Float32Array(PARTICLE_COUNT * 3);
  const colors = new Float32Array(PARTICLE_COUNT * 3);
  const orbitData = []; // { radius, angle, speed, y, wobble }

  const palette = [
    new THREE.Color(0x66eaff),
    new THREE.Color(0x88aaff),
    new THREE.Color(0xff77cc),
    new THREE.Color(0xffffff)
  ];

  for (let i = 0; i < PARTICLE_COUNT; i++) {
    // 大部分粒子集中在扁平环带,少量弥散成球壳
    const inRing = Math.random() < 0.7;
    const radius = inRing
      ? 2.4 + Math.random() * 3.4
      : 2.0 + Math.random() * 5.5;
    const angle = Math.random() * Math.PI * 2;
    const y = inRing
      ? (Math.random() - 0.5) * 0.5
      : (Math.random() - 0.5) * radius * 1.2;
    const speed = (0.15 + Math.random() * 0.45) * (Math.random() < 0.5 ? 1 : 1);
    orbitData.push({ radius, angle, speed, y, wobble: Math.random() * Math.PI * 2 });

    positions[i * 3] = Math.cos(angle) * radius;
    positions[i * 3 + 1] = y;
    positions[i * 3 + 2] = Math.sin(angle) * radius;

    const c = palette[(Math.random() * palette.length) | 0];
    colors[i * 3] = c.r;
    colors[i * 3 + 1] = c.g;
    colors[i * 3 + 2] = c.b;
  }

  const particleGeo = new THREE.BufferGeometry();
  particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  particleGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));

  const particleMat = new THREE.PointsMaterial({
    size: 0.055,
    vertexColors: true,
    transparent: true,
    opacity: 0.9,
    blending: THREE.AdditiveBlending,
    depthWrite: false,
    sizeAttenuation: true
  });

  const particles = new THREE.Points(particleGeo, particleMat);
  particleGroup.add(particles);

  // 尘埃星点背景
  const starCount = 600;
  const starPos = new Float32Array(starCount * 3);
  for (let i = 0; i < starCount; i++) {
    const r = 30 + Math.random() * 40;
    const theta = Math.random() * Math.PI * 2;
    const phi = Math.acos(2 * Math.random() - 1);
    starPos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
    starPos[i * 3 + 1] = r * Math.cos(phi);
    starPos[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
  }
  const starGeo = new THREE.BufferGeometry();
  starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
  const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({
    size: 0.18,
    color: 0xaaccee,
    transparent: true,
    opacity: 0.6,
    depthWrite: false
  }));
  scene.add(stars);

  // ---------- 自适应窗口 ----------
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });

  // ---------- 动画循环 ----------
  const clock = new THREE.Clock();
  const posAttr = particleGeo.getAttribute('position');

  function animate() {
    requestAnimationFrame(animate);
    const t = clock.getElapsedTime();
    const dt = Math.min(clock.getDelta() || 0.016, 0.05);

    // 水晶缓慢自转 + 轻微浮动
    crystalGroup.rotation.y += 0.004;
    crystalGroup.rotation.x = Math.sin(t * 0.3) * 0.12;
    crystalGroup.position.y = Math.sin(t * 0.8) * 0.15;

    // 内核脉动
    const pulse = 1 + Math.sin(t * 2.2) * 0.12;
    innerCore.scale.setScalar(pulse);
    coreLight.intensity = 45 + Math.sin(t * 2.2) * 12;
    glowMat.opacity = 0.07 + Math.sin(t * 2.2) * 0.03;

    // 粒子沿轨道环绕
    for (let i = 0; i < PARTICLE_COUNT; i++) {
      const d = orbitData[i];
      d.angle += d.speed * 0.016;
      posAttr.array[i * 3] = Math.cos(d.angle) * d.radius;
      posAttr.array[i * 3 + 1] = d.y + Math.sin(t * 0.9 + d.wobble) * 0.12;
      posAttr.array[i * 3 + 2] = Math.sin(d.angle) * d.radius;
    }
    posAttr.needsUpdate = true;

    // 粒子环带整体缓慢倾斜旋转
    particleGroup.rotation.y += 0.0012;
    particleGroup.rotation.z = Math.sin(t * 0.15) * 0.08;

    stars.rotation.y += 0.0002;

    controls.update();
    renderer.render(scene, camera);
  }

  animate();
  window.__sceneReady = true; // 标记加载成功,关闭超时报错

} catch (err) {
  window.__showError('初始化异常: ' + (err && err.message ? err.message : String(err)));
}
</script>
</body>
</html>
3.03s
首Token
41
思考TPS
18.3s · ≈757 tok
35
输出TPS
83.3s · ≈2,946 tok
3,703
总Tokens(官方)
输入 237
总用时 106s · 平均 36 tok/s · 峰值 147 tok/s · 成本 ¥0.375
DeepSeek#7
deepseek-v4-pro
完成
用户想要一个单文件HTML,包含Three.js的3D场景。具体要求: 1. 使用import map引入Three.js 0.171.0版本 2. 构建一个3D场景:缓慢自转的发光水晶核心、环绕粒子、合理的光照与材质 3. 使用OrbitControls支持鼠标拖拽旋转与滚轮缩放 4. Canvas铺满视口并随窗口自适应 5. 如果脚本加载失败,显示错误文字 让我来构建这个场景: - 发光水晶核心:可以使用一个二十面体(IcosahedronGeometry)或八面体,配合发光的材质(MeshStandardMaterial + emissive属性,或者使用MeshPhongMaterial配合点光源) - 环绕粒子:使用BufferGeometry创建环绕的粒子系统 - 光照:环境光 + 点光源(在水晶周围)+ 可能的方向光 - OrbitControls:从addons导入 - 水晶需要缓慢自转 - 粒子环绕运动 我会创建一个漂亮的水晶核心,使用IcosahedronGeometry并给它一些emissive属性让它发光,外面包裹一个稍大一点的透明发光壳。环绕粒子可以使用环形的粒子系统,或者螺旋分布的粒子。 让我设计: 1. 中心水晶:使用IcosahedronGeometry(1, 1),配合MeshPhysicalMaterial或MeshStandardMaterial,带有emissive 2. 外部光晕:一个稍大的透明球体或使用AdditiveBlending的粒子 3. 环绕粒子:在多个轨道上分布粒子,缓慢旋转 4. 光照:AmbientLight + 多个PointLight(不同颜色) 让我也加入一些shader或者使用简单的glow效果。我会使用一个简单的sprite或者透明几何体来做光晕。 另外,我需要处理加载失败的情况。可以使用try-catch或者检查模块是否成功加载。由于使用了ES模块,如果CDN加载失败,import会失败。我可以在script标签中使用try-catch,或者使用window.onerror来捕获。更简单的方法是使用一个备用方案——在页面上放一个错误信息的div,如果加载成功就用canvas覆盖它,如果失败就显示错误。 实际上,最简单的方法是在body中先放一个错误提示的div,然后在模块加载成功后隐藏它。如果模块加载失败(比如CDN不可用),错误信息就会保留显示。另外可以加一个setTimeout来检测——如果几秒后场景还没渲染,就显示错误。 让我采用更稳健的方法:在body中放置错误提示,在模块中用try-catch包裹,如果初始化成功则移除错误提示。 另外关于OrbitControls的路径:在Three.js 0.171中,OrbitControls的路径应该是`three/addons/controls/OrbitControls.js`。 让我也加入一些粒子环绕效果——创建多个环形轨道,每个轨道上有不同颜色和大小的粒子,它们围绕水晶旋转。 我还要给水晶做一个漂亮的发光效果。可以使用: - 水晶主体:IcosahedronGeometry,带有金属质感和emissive - 内部小水晶:稍小的几何体 - 光晕粒子:在水晶周围随机分布的小粒子,使用additive blending 让我也加入一些垂直的光线粒子或者使用简单的发光精灵。 关于粒子的环绕,我可以创建几个环形(torus-shaped)的粒子分布,让它们以不同速度旋转。 好的,让我编写完整代码: 1. 水晶核心 - 二十面体,白色/淡蓝色,带emissive 2. 环绕粒子环 - 2-3个不同倾角的粒子环 3. 散射光点 - 在水晶周围随机分布的发光粒子 4. 光照设置 5. OrbitControls 6. 响应式设计 另外我还想加入一些post-processing的简易替代——使用一个大的透明发光球体包裹水晶,用additive blending。 我需要注意OrbitControls的导入路径。在0.171版本中,应该是: `import { OrbitControls } from 'three/addons/controls/OrbitControls.js';` 让我还要加入地面反射或者简单的背景。我会使用一个深色背景。 关于"发光水晶核心"——我想让水晶看起来在发光。可以通过以下方式: - 水晶材质使用emissive属性 - 在水晶周围放置点光源 - 使用一个稍大的透明发光壳(使用sprite或自定义shader,或者简单的使用一个透明的、带additive blending的球体) 为了简单和效果,我会: - 使用MeshStandardMaterial,设置emissive为亮色 - 在水晶周围添加一个使用additive blending的半透明球体模拟光晕 - 添加多个点光源 让我也加入一些从水晶表面射出的光点粒子。 实际上,让我做得更精致一些。水晶用八面体(OctahedronGeometry)可能看起来更有水晶感,或者用二十面体。二十面体更像宝石。我会使用IcosahedronGeometry(1, 0)(二十面体,不细分),这样有更多的三角形面,看起来像多面体宝石。 对于环绕粒子,我会创建2-3个环形轨道,粒子均匀分布在这些轨道上,整个轨道组以不同速度旋转。 让我也加入一些上升的粒子(像魔法粒子从水晶中散发出来)。 好的,开始编码。 关于错误处理——我会在HTML中放一个错误提示元素,然后在模块代码中用try-catch包裹。如果初始化成功,错误元素会被隐藏。对于CDN加载失败的情况,import语句本身失败会导致整个模块无法执行,这时错误元素会保持可见。另外我可以加一个计时器——如果2秒后场景还没有渲染(通过检查一个全局标志),就显示错误。 实际上更好的做法是使用`window.addEventListener('error', ...)`来捕获脚本加载错误,或者在import之后立即设置一个标志。但ES模块的import失败很难在模块内部捕获(除非使用动态import)。让我使用动态import来处理: ```javascript async function init() { try { const threeModule = await import('three'); const controlsModule = await import('three/addons/controls/OrbitControls.js'); // ... 使用这些模块 } catch (e) { document.getElementById('error').style.display = 'block'; } } ``` 这样如果CDN加载失败,错误就能被捕获并显示。这是一个更好的方法。 让我使用动态import。 现在关于粒子环绕的细节: - 创建几个环形粒子组,每个环有不同的半径、倾角和旋转速度 - 粒子使用小的圆点(可以用sprite或者小几何体,但为了性能使用Points) - 使用圆形纹理(可以用canvas生成,或者使用简单的点) 对于发光粒子,我可以用Canvas生成一个径向渐变的纹理,然后用PointsMaterial配合additive blending。 水晶的光晕也可以用类似的方式——在水晶周围放一些使用additive blending的大粒子。 好的,让我也想一下水晶的材质。我想让它看起来晶莹剔透。使用MeshPhysicalMaterial: - roughness: 0.1 - metalness: 0.1 - clearcoat: 0.3 (如果有的话) - 颜色可以是淡紫色或淡蓝色 - emissive使用相同但更亮的颜色 实际上在0.171版本中MeshPhysicalMaterial应该有这些属性。 让我也加入一个简单的背景星空效果。 好的,现在要控制代码长度。让我写得完整但不过于冗长。 关于水晶结构,我想使用一个二十面体作为主体,然后可能有一些突出的尖刺或者就是干净的二十面体。为了更有水晶感,我可以使用一个二十面体并稍微调整顶点,或者直接使用二十面体并依靠材质让它看起来像水晶。 对于光晕,我使用一个稍大的球体(半径约1.3-1.5),使用自定义shader或者简单的使用MeshBasicMaterial配合透明度和additive blending。但为了简单,我可以使用一个半透明的球体(使用ShaderMaterial或者直接用sprite)。 实际上,使用一个大的点光源在水晶内部,然后在水晶周围有一些粒子光点,效果应该不错。 让我也加入一些从水晶散发出的细小粒子,它们缓慢向外移动然后消失再重新生成。 好的,我会尽量保持代码结构清晰。 现在关于OrbitControls:需要设置minDistance和maxDistance来限制缩放范围,以及设置autoRotate或者让用户自由旋转。 关于自适应:监听window resize事件,更新camera aspect ratio和renderer大小。 另外我还想加入一点后处理效果的感觉——通过在水晶周围放置一些发光的sprite来模拟glow。 好的,开始编写完整代码: 我需要注意: - 水晶缓慢自转(绕Y轴或其他轴) - 环绕粒子以不同速度旋转 - OrbitControls的阻尼 - 合适的颜色搭配 颜色方案:我想用水晶蓝/紫罗兰色调。主体水晶是淡紫蓝色,emissive是更亮的蓝紫色。环绕粒子可以是金色和淡蓝色混合。光晕是蓝白色。 让我也加入一些从底部向上飘散的粒子,给场景增添魔法感。 对于环绕粒子环,我会创建3个环: 1. 接近水平面的环(倾角约15度),半径约2.5 2. 倾斜约45度的环,半径约3.0 3. 接近垂直的环(倾角约75度),半径约2.8 每个环上的粒子以不同速度旋转。 另外,我还想在水晶周围有一层近距离的粒子云雾——随机分布在水晶表面附近的小光点。 对于光晕效果,我会在水晶位置放置一个较大的、使用additive blending的sprite,带径向渐变。 好的,让我也加入一个简单的星空背景——在远处随机分布大量小光点。 关于粒子纹理,我会用Canvas动态生成: - 一个径向渐变的圆形(用于光晕sprite和粒子) - 中心亮、边缘透明的纹理 这样所有粒子都能有柔和的外观。 对于水晶的自转,我让它绕Y轴缓慢旋转,同时稍微绕X轴有一点摆动。 好的,开始编写完整代码。我会使用动态import来处理错误。 关于renderer的设置: - 使用WebGLRenderer - 开启antialias - 设置pixelRatio(但不要超过2以保证性能) - 背景色深色 另外,我想加入tone mapping来让发光效果更好。使用ACESFilmicToneMapping可以增强发光的感觉。 还需要设置renderer的outputColorSpace。 好的,现在开始编写代码。我会注意不要在代码中添加过多注释,保持代码清晰即可。 关于水晶的材质,为了让它看起来发光,我使用: - MeshPhysicalMaterial - color: 淡紫蓝色 (#b8c8ff 左右) - emissive: 更亮的蓝紫色 - emissiveIntensity: 1.5-2.0 - roughness: 0.15 - metalness: 0.3 对于光晕壳,使用一个稍大的球体(半径1.4),使用ShaderMaterial: - 半透明 - 颜色从中心向外衰减 - additive blending - 或者更简单地使用MeshBasicMaterial + 透明度 + additiveBlending,但球体整个会是均匀的。为了好看,使用自定义shader或者用一个大sprite。 实际上使用Sprite作为光晕最简单有效。创建一个大的sprite在水晶位置,使用径向渐变纹理,additive blending。可以放2-3个不同大小的sprite叠加。 对于环绕粒子环,使用BufferGeometry,每个环约100-200个粒子,均匀分布在一个圆上(带一点随机偏移)。 让我也加入一些零散的、不完全是环形的粒子,它们在水晶周围以更自由的轨迹运动。 为了简化,我创建: - 3个粒子环(不同倾角、半径、速度) - 1组近距离光点(在水晶表面附近随机分布,随水晶一起旋转) - 星空背景 - 光晕sprite 环的旋转通过将环的父级group绕Y轴(或对应的轴)旋转来实现。 好的,现在让我也处理一下相机初始位置。使用OrbitControls,初始相机位置设为(4, 2, 6)左右,看向原点。 关于发光效果的增强:我可以在水晶内部或非常近的位置放置一个点光源,这样周围环绕的粒子也能被照亮一点。但点光源对Points的影响有限(需要设置相应的材质)。实际上,PointLight会影响Mesh,但对使用MeshBasicMaterial或自定义材质的Points影响不大。对于粒子,它们本身就会用additive blending发光,所以不需要额外光照。 好的,开始写代码。 实际上我还有一点需要考虑——水晶核心的自转。水晶作为一个group,以缓慢速度绕Y轴旋转(比如0.3弧度/秒)。环绕粒子的环也各自旋转,但速度不同。有些环可以和水晶同方向旋转但速度不同,有些可以反向旋转。 另外关于光晕,我可以使用一个较大的、半透明的、使用additive blending的球体,但为了让边缘柔和,使用自定义shader或使用sprite。Sprite始终面向相机,对于光晕来说是合理的。 对于近距离光点,它们分布在水晶表面向外一点的位置(半径约1.2-1.6),随机位置,随着水晶group一起旋转。使用Points,材质使用additive blending。 好的,现在开始编写。我需要创建Canvas纹理用于粒子和光晕。 还有一个重要的事情——OrbitControls的target应该在水晶位置(原点),这样用户可以围绕水晶旋转。 另外,我想加入一点自动旋转的倾向,但OrbitControls的autoRotate可能会和手动拖拽冲突。我会设置autoRotate为false,让用户完全控制,但水晶本身会自转(这是场景内的动画,与相机无关)。 好的,编写代码。我会小心处理所有细节。 关于水晶的几何体,IcosahedronGeometry(1, 1)会创建一个半径为1的二十面体,细分1次。这会产生更多的面,看起来更光滑。对于宝石效果,细分1次是好的选择。 而对于"水晶"的感觉,也许八面体(OctahedronGeometry)更合适,因为它有尖锐的顶点。或者使用二十面体也很像宝石。让我使用IcosahedronGeometry(1, 0)保留更明显的面,看起来更像切割的宝石。实际上细分0的二十面体有20个三角形面,看起来已经不错了。让我使用细分0,这样面更明显,更像多面体宝石。 不,细分1会让它更光滑但仍然保留一些面,看起来更好。让我使用IcosahedronGeometry(1, 1)。 好的,开始正式编写完整代码。 还有一点,我要确保粒子环的可见性。粒子使用PointsMaterial,设置size足够大(比如0.05-0.08),使用additive blending,这样它们在深色背景上会很显眼。 对于光晕sprite,使用较大的size(比如5-6),使用径向渐变纹理,additive blending,并设置较低的不透明度。 好的。另外,关于从水晶散发出的上升粒子——我想创建一组粒子,它们从水晶表面随机位置生成,向外缓慢移动,到达一定距离后消失并重新生成。这可以通过在animate中更新粒子位置来实现。不过这会比较复杂。让我简化——在水晶周围有一个球形分布的粒子云,它们随机缓慢移动。 实际上,为了简单,我使用一组在水晶周围球壳中随机分布的粒子(半径1.3-2.0),它们会缓慢地围绕水晶移动并上下浮动。在每帧中给它们一点随机扰动。 更简单的做法是:创建一组粒子随机分布在水晶周围的球形区域,整个组随水晶一起旋转,但粒子本身也有一些微小的独立运动。不过逐帧更新几百个粒子的位置可能会影响性能。让我保持粒子相对静态,依靠整个group的旋转来产生动态效果。每个环的旋转速度不同,这样整体看起来就很动态。 好的,精简设计: 1. 水晶核心group(自转): - 二十面体(发光材质) - 近距离光点球壳(随水晶旋转) - 2个光晕sprite(不同大小) 2. 3个粒子环group(各自以不同速度绕不同轴旋转) 3. 星空背景(静态) 4. 内部点光源 这样整体效果应该很好。 现在关于点光源——在水晶内部放置一个点光源(color匹配水晶颜色,intensity较高),可以照亮环绕的粒子环吗?粒子环使用PointsMaterial时,如果使用MeshStandardMaterial或MeshPhongMaterial则可以被光照影响,但通常粒子使用PointsMaterial(不受光照影响)或使用自定义材质。如果我想让粒子环被水晶的光照亮,需要使用能够响应光照的材质,但那比较复杂。让我保持粒子使用additive blending的发光材质,它们本身就很亮,不需要被照亮。而点光源主要用于照亮场景中可能的其他物体(比如地面等),这里我们没有地面,所以点光源主要提供氛围。 实际上,对于这个场景: - 使用AmbientLight提供基础照明 - 在水晶位置放置PointLight,可以影响水晶本身(如果材质响应光照)以及周围任何使用标准材质的物体 - 粒子使用additive blending的PointsMaterial,本身发光 对于水晶的MeshPhysicalMaterial,它会响应光照。emissive让它看起来在发光(即使在暗处也能看到),而PointLight和AmbientLight确保它的面被正确照亮。 好的,一切就绪,开始写代码。 另外,关于OrbitControls,在较新版本中,导入路径和类名可能有变化。在0.171中,应该是`OrbitControls`(从`three/addons/controls/OrbitControls.js`导入)。 让我也设置controls的一些参数: - enableDamping: true - dampingFactor: 0.08 - minDistance: 2 - maxDistance: 15 - target: (0, 0, 0) 关于渲染器的toneMapping,使用ACESFilmicToneMapping可以让发光区域看起来更好,增强HDR效果。同时设置适当的exposure。 好的,现在考虑一下粒子环的倾斜。我创建3个环:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发光水晶核心 - 3D场景</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        html,
        body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: #050510;
            font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
        }
        canvas {
            display: block;
        }
        #error-overlay {
            display: none;
            position: fixed;
            inset: 0;
            background: rgba(5, 5, 16, 0.95);
            z-index: 1000;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            color: #ff6b6b;
            text-align: center;
            padding: 2rem;
        }
        #error-overlay.visible {
            display: flex;
        }
        #error-overlay .icon {
            font-size: 4rem;
            margin-bottom: 1.5rem;
            animation: pulse 2s ease-in-out infinite;
        }
        #error-overlay h1 {
            font-size: 1.8rem;
            margin-bottom: 0.8rem;
            font-weight: 600;
            letter-spacing: 0.05em;
        }
        #error-overlay p {
            font-size: 1rem;
            color: #c0a0a0;
            max-width: 480px;
            line-height: 1.7;
        }
        #error-overlay .retry-hint {
            margin-top: 1.5rem;
            font-size: 0.85rem;
            color: #888;
            letter-spacing: 0.04em;
        }
        @keyframes pulse {
            0%,
            100% {
                opacity: 0.6;
                transform: scale(1);
            }
            50% {
                opacity: 1;
                transform: scale(1.15);
            }
        }
    </style>
    <script type="importmap">
        {
            "imports": {
                "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
                "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
            }
        }
    </script>
</head>
<body>
    <div id="error-overlay">
        <div class="icon">⚠️</div>
        <h1>场景加载失败</h1>
        <p>无法加载 Three.js 渲染引擎,请检查网络连接或稍后刷新页面重试。</p>
        <p class="retry-hint">CDN 源:registry.npmmirror.com</p>
    </div>

    <script type="module">
        const errorOverlay = document.getElementById('error-overlay');

        async function init() {
            const [THREE, controlsModule] = await Promise.all([
                import('three'),
                import('three/addons/controls/OrbitControls.js')
            ]);

            const OrbitControls = controlsModule.OrbitControls;

            // 隐藏错误提示
            errorOverlay.classList.remove('visible');

            // ─────────────────────────────────────
            // 基础场景设置
            // ─────────────────────────────────────
            const scene = new THREE.Scene();
            scene.background = new THREE.Color('#050510');
            scene.fog = new THREE.FogExp2('#050510', 0.00025);

            const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.outputColorSpace = THREE.SRGBColorSpace;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1.2;
            document.body.appendChild(renderer.domElement);

            const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.15, 80);
            camera.position.set(5.5, 2.8, 7.5);
            camera.lookAt(0, 0.2, 0);

            const controls = new OrbitControls(camera, renderer.domElement);
            controls.target.set(0, 0.2, 0);
            controls.enableDamping = true;
            controls.dampingFactor = 0.08;
            controls.minDistance = 2.2;
            controls.maxDistance = 18;
            controls.maxPolarAngle = Math.PI * 0.78;
            controls.minPolarAngle = 0.2;
            controls.autoRotate = false;
            controls.update();

            // ─────────────────────────────────────
            // Canvas 纹理生成
            // ─────────────────────────────────────
            function createGlowTexture(size, innerRadius, outerRadius, colorInner, colorOuter) {
                const canvas = document.createElement('canvas');
                canvas.width = size;
                canvas.height = size;
                const ctx = canvas.getContext('2d');
                const half = size / 2;
                const gradient = ctx.createRadialGradient(half, half, innerRadius * half, half, half, outerRadius * half);
                gradient.addColorStop(0, colorInner);
                gradient.addColorStop(0.15, colorInner);
                gradient.addColorStop(0.5, colorOuter);
                gradient.addColorStop(1, 'rgba(0,0,0,0)');
                ctx.fillStyle = gradient;
                ctx.fillRect(0, 0, size, size);
                return new THREE.CanvasTexture(canvas);
            }

            const particleTex = createGlowTexture(128, 0, 0.55, 'rgba(255,255,255,1)', 'rgba(180,200,255,0.35)');
            const glowTexLarge = createGlowTexture(512, 0, 0.7, 'rgba(220,210,255,0.9)', 'rgba(120,90,200,0)');
            const glowTexMedium = createGlowTexture(256, 0, 0.45, 'rgba(240,230,255,1)', 'rgba(160,130,240,0)');
            const glowTexSmall = createGlowTexture(128, 0, 0.35, 'rgba(255,245,255,1)', 'rgba(180,150,230,0)');

            // ─────────────────────────────────────
            // 光照
            // ─────────────────────────────────────
            const ambientLight = new THREE.AmbientLight('#2a2040', 1.8);
            scene.add(ambientLight);

            const innerPointLight = new THREE.PointLight('#c8b8ff', 30, 8, 0.6);
            innerPointLight.position.set(0, 0, 0);
            scene.add(innerPointLight);

            const midPointLight = new THREE.PointLight('#9988dd', 15, 5, 0.5);
            midPointLight.position.set(0, 0.3, 0);
            scene.add(midPointLight);

            const outerPointLight = new THREE.PointLight('#7766cc', 8, 10, 0.4);
            outerPointLight.position.set(0, -0.1, 0);
            scene.add(outerPointLight);

            // ─────────────────────────────────────
            // 水晶核心组
            // ─────────────────────────────────────
            const crystalGroup = new THREE.Group();
            scene.add(crystalGroup);

            // 主体二十面体(外层)
            const icoGeomOuter = new THREE.IcosahedronGeometry(1, 1);
            const icoMatOuter = new THREE.MeshPhysicalMaterial({
                color: '#c8b8f0',
                emissive: '#5038a0',
                emissiveIntensity: 1.6,
                roughness: 0.18,
                metalness: 0.08,
                clearcoat: 0.45,
                clearcoatRoughness: 0.22,
                specularIntensity: 0.7,
                specularColor: '#ffffff',
            });
            const crystalOuter = new THREE.Mesh(icoGeomOuter, icoMatOuter);
            crystalGroup.add(crystalOuter);

            // 内层二十面体(更亮)
            const icoGeomInner = new THREE.IcosahedronGeometry(0.62, 1);
            const icoMatInner = new THREE.MeshPhysicalMaterial({
                color: '#e8d8ff',
                emissive: '#9060e0',
                emissiveIntensity: 2.8,
                roughness: 0.1,
                metalness: 0.03,
                clearcoat: 0.55,
                clearcoatRoughness: 0.12,
                specularIntensity: 1.0,
                specularColor: '#ffffff',
            });
            const crystalInner = new THREE.Mesh(icoGeomInner, icoMatInner);
            crystalGroup.add(crystalInner);

            // 顶点光点——提取二十面体顶点
            const icoGeomBase = new THREE.IcosahedronGeometry(1, 0);
            const rawPositions = icoGeomBase.getAttribute('position').array;
            const vertexSet = new Set();
            const uniqueVerts = [];
            for (let i = 0; i < rawPositions.length; i += 3) {
                const x = rawPositions[i];
                const y = rawPositions[i + 1];
                const z = rawPositions[i + 2];
                const key = `${x.toFixed(5)},${y.toFixed(5)},${z.toFixed(5)}`;
                if (!vertexSet.has(key)) {
                    vertexSet.add(key);
                    uniqueVerts.push(new THREE.Vector3(x, y, z));
                }
            }
            icoGeomBase.dispose();

            const vertexDotsGeom = new THREE.BufferGeometry();
            const vertexDotPositions = [];
            for (const v of uniqueVerts) {
                const dir = v.clone().normalize();
                const pos = dir.clone().multiplyScalar(1.08);
                vertexDotPositions.push(pos.x, pos.y, pos.z);
            }
            vertexDotsGeom.setAttribute('position', new THREE.Float32BufferAttribute(vertexDotPositions, 3));
            const vertexDotsMat = new THREE.PointsMaterial({
                map: glowTexSmall,
                size: 0.22,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                color: '#f8f0ff',
                transparent: true,
                opacity: 0.9,
            });
            const vertexDots = new THREE.Points(vertexDotsGeom, vertexDotsMat);
            crystalGroup.add(vertexDots);

            // 光晕 sprite
            const spriteLarge = new THREE.Sprite(new THREE.SpriteMaterial({
                map: glowTexLarge,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                color: '#c8b0f0',
                transparent: true,
                opacity: 0.55,
            }));
            spriteLarge.scale.set(6.5, 6.5, 1);
            crystalGroup.add(spriteLarge);

            const spriteMedium = new THREE.Sprite(new THREE.SpriteMaterial({
                map: glowTexMedium,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                color: '#e0d0ff',
                transparent: true,
                opacity: 0.7,
            }));
            spriteMedium.scale.set(3.2, 3.2, 1);
            crystalGroup.add(spriteMedium);

            const spriteSmall = new THREE.Sprite(new THREE.SpriteMaterial({
                map: glowTexSmall,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                color: '#f8f0ff',
                transparent: true,
                opacity: 0.85,
            }));
            spriteSmall.scale.set(1.8, 1.8, 1);
            crystalGroup.add(spriteSmall);

            // 近距离光点球壳
            const closeDotsCount = 120;
            const closeDotsGeom = new THREE.BufferGeometry();
            const closeDotsPositions = new Float32Array(closeDotsCount * 3);
            for (let i = 0; i < closeDotsCount; i++) {
                const theta = Math.acos(1 - 2 * Math.random());
                const phi = Math.PI * 2 * Math.random();
                const r = 1.1 + Math.random() * 0.7;
                closeDotsPositions[i * 3] = r * Math.sin(theta) * Math.cos(phi);
                closeDotsPositions[i * 3 + 1] = r * Math.sin(theta) * Math.sin(phi);
                closeDotsPositions[i * 3 + 2] = r * Math.cos(theta);
            }
            closeDotsGeom.setAttribute('position', new THREE.Float32BufferAttribute(closeDotsPositions, 3));
            const closeDotsMat = new THREE.PointsMaterial({
                map: particleTex,
                size: 0.09,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                color: '#e8d8ff',
                transparent: true,
                opacity: 0.75,
            });
            const closeDots = new THREE.Points(closeDotsGeom, closeDotsMat);
            crystalGroup.add(closeDots);

            // ─────────────────────────────────────
            // 环绕粒子环
            // ─────────────────────────────────────
            const ringConfigs = [
                { radius: 2.3, count: 160, tiltX: 0.22, tiltZ: 0, speed: 0.42, color: '#ffcc77', size: 0.07,
                    opacity: 0.8 },
                { radius: 2.75, count: 180, tiltX: 0.95, tiltZ: 0.15, speed: -0.33, color: '#88ccff', size: 0.065,
                    opacity: 0.75 },
                { radius: 2.55, count: 170, tiltX: -0.6, tiltZ: 1.1, speed: 0.5, color: '#cc99ff', size: 0.072,
                    opacity: 0.78 },
            ];

            const ringGroups = [];
            for (const cfg of ringConfigs) {
                const ringGroup = new THREE.Group();
                ringGroup.rotation.x = cfg.tiltX;
                ringGroup.rotation.z = cfg.tiltZ;
                scene.add(ringGroup);

                const ringGeom = new THREE.BufferGeometry();
                const ringPositions = new Float32Array(cfg.count * 3);
                for (let i = 0; i < cfg.count; i++) {
                    const angle = (i / cfg.count) * Math.PI * 2;
                    const r = cfg.radius + (Math.random() - 0.5) * 0.22;
                    const yOff = (Math.random() - 0.5) * 0.16;
                    ringPositions[i * 3] = Math.cos(angle) * r;
                    ringPositions[i * 3 + 1] = yOff;
                    ringPositions[i * 3 + 2] = Math.sin(angle) * r;
                }
                ringGeom.setAttribute('position', new THREE.Float32BufferAttribute(ringPositions, 3));
                const ringMat = new THREE.PointsMaterial({
                    map: particleTex,
                    size: cfg.size,
                    blending: THREE.AdditiveBlending,
                    depthWrite: false,
                    color: cfg.color,
                    transparent: true,
                    opacity: cfg.opacity,
                });
                const ringPoints = new THREE.Points(ringGeom, ringMat);
                ringGroup.add(ringPoints);

                ringGroups.push({
                    group: ringGroup,
                    speed: cfg.speed,
                });
            }

            // ─────────────────────────────────────
            // 上升魔法粒子
            // ─────────────────────────────────────
            const risingCount = 70;
            const risingGeom = new THREE.BufferGeometry();
            const risingPositionsArr = new Float32Array(risingCount * 3);
            const risingData = [];
            for (let i = 0; i < risingCount; i++) {
                const theta = Math.acos(1 - 2 * Math.random());
                const phi = Math.PI * 2 * Math.random();
                const baseR = 0.25 + Math.random() * 1.3;
                const y = Math.random() * 5;
                risingPositionsArr[i * 3] = baseR * Math.sin(theta) * Math.cos(phi);
                risingPositionsArr[i * 3 + 1] = y;
                risingPositionsArr[i * 3 + 2] = baseR * Math.sin(theta) * Math.sin(phi);
                risingData.push({
                    baseX: risingPositionsArr[i * 3],
                    baseZ: risingPositionsArr[i * 3 + 2],
                    speed: 0.35 + Math.random() * 1.1,
                    maxY: 3.5 + Math.random() * 4.5,
                    minY: -0.8 + Math.random() * 1.6,
                    wobbleAmp: 0.03 + Math.random() * 0.12,
                    wobbleSpeed: 0.5 + Math.random() * 2,
                    wobbleOffset: Math.random() * Math.PI * 2,
                });
            }
            risingGeom.setAttribute('position', new THREE.Float32BufferAttribute(risingPositionsArr, 3));
            const risingMat = new THREE.PointsMaterial({
                map: particleTex,
                size: 0.06,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                color: '#d8c8ff',
                transparent: true,
                opacity: 0.7,
            });
            const risingPoints = new THREE.Points(risingGeom, risingMat);
            scene.add(risingPoints);

            // ─────────────────────────────────────
            // 星空背景
            // ─────────────────────────────────────
            const starsCount = 600;
            const starsGeom = new THREE.BufferGeometry();
            const starsPositions = new Float32Array(starsCount * 3);
            const starsColors = new Float32Array(starsCount * 3);
            for (let i = 0; i < starsCount; i++) {
                const theta = Math.acos(1 - 2 * Math.random());
                const phi = Math.PI * 2 * Math.random();
                const r = 18 + Math.random() * 35;
                starsPositions[i * 3] = r * Math.sin(theta) * Math.cos(phi);
                starsPositions[i * 3 + 1] = r * Math.sin(theta) * Math.sin(phi);
                starsPositions[i * 3 + 2] = r * Math.cos(theta);
                const brightness = 0.4 + Math.random() * 0.6;
                const tint = Math.random();
                if (tint < 0.15) {
                    starsColors[i * 3] = brightness;
                    starsColors[i * 3 + 1] = brightness * 0.75;
                    starsColors[i * 3 + 2] = brightness * 0.55;
                } else if (tint < 0.3) {
                    starsColors[i * 3] = brightness * 0.65;
                    starsColors[i * 3 + 1] = brightness * 0.8;
                    starsColors[i * 3 + 2] = brightness;
                } else {
                    starsColors[i * 3] = brightness * 0.85;
                    starsColors[i * 3 + 1] = brightness * 0.9;
                    starsColors[i * 3 + 2] = brightness;
                }
            }
            starsGeom.setAttribute('position', new THREE.Float32BufferAttribute(starsPositions, 3));
            starsGeom.setAttribute('color', new THREE.Float32BufferAttribute(starsColors, 3));
            const starsMat = new THREE.PointsMaterial({
                map: particleTex,
                size: 0.18,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                vertexColors: true,
                transparent: true,
                opacity: 0.85,
            });
            const stars = new THREE.Points(starsGeom, starsMat);
            scene.add(stars);

            // ─────────────────────────────────────
            // 动画循环
            // ─────────────────────────────────────
            const clock = new THREE.Clock();
            function animate() {
                requestAnimationFrame(animate);

                const dt = Math.min(clock.getDelta(), 0.1);
                const elapsed = performance.now() * 0.001;

                // 水晶自转
                crystalGroup.rotation.y += 0.38 * dt;
                crystalGroup.rotation.x += 0.06 * dt;
                crystalGroup.rotation.z += 0.04 * dt;

                // 环绕环旋转
                for (const rg of ringGroups) {
                    rg.group.rotation.y += rg.speed * dt;
                }

                // 上升粒子更新
                const rpos = risingGeom.attributes.position.array;
                for (let i = 0; i < risingCount; i++) {
                    const data = risingData[i];
                    let y = rpos[i * 3 + 1];
                    y += data.speed * dt;
                    if (y > data.maxY) {
                        y = data.minY;
                    }
                    rpos[i * 3 + 1] = y;
                    const wobX = Math.sin(elapsed * data.wobbleSpeed + data.wobbleOffset) * data.wobbleAmp;
                    const wobZ = Math.cos(elapsed * data.wobbleSpeed + data.wobbleOffset + 1.3) * data.wobbleAmp;
                    rpos[i * 3] = data.baseX + wobX;
                    rpos[i * 3 + 2] = data.baseZ + wobZ;
                }
                risingGeom.attributes.position.needsUpdate = true;

                // 点光源微动
                const flicker = 1 + Math.sin(elapsed * 2.3) * 0.08 + Math.sin(elapsed * 5.7) * 0.05;
                innerPointLight.intensity = 30 * flicker;
                midPointLight.intensity = 15 * (1 + Math.cos(elapsed * 3.1) * 0.06);

                // 光晕脉冲
                const pulse = 1 + Math.sin(elapsed * 1.6) * 0.07 + Math.sin(elapsed * 4.3) * 0.04;
                spriteLarge.scale.set(6.5 * pulse, 6.5 * pulse, 1);
                spriteMedium.scale.set(3.2 * (1 + Math.sin(elapsed * 2.1) * 0.05), 3.2 * (1 + Math.sin(elapsed * 2.1) *
                    0.05), 1);

                controls.update();
                renderer.render(scene, camera);
            }

            // ─────────────────────────────────────
            // 响应式处理
            // ─────────────────────────────────────
            window.addEventListener('resize', () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });

            // 启动渲染
            requestAnimationFrame(animate);

            // 标记加载成功
            window.__sceneLoaded = true;
            console.log('✨ 发光水晶核心场景已就绪');
        }

        // 启动初始化
        try {
            await init();
        } catch (err) {
            console.error('场景初始化失败:', err);
            errorOverlay.classList.add('visible');
        }

        // 兜底检测:3秒后若未加载成功则显示错误
        setTimeout(() => {
            if (!window.__sceneLoaded) {
                errorOverlay.classList.add('visible');
            }
        }, 3000);
    </script>
</body>
</html>
0.73s
首Token
51
思考TPS
132s · 6,812 tok
111
输出TPS
49.0s · 5,456 tok
12,268
总Tokens(官方)
输入 171
总用时 183s · 平均 68 tok/s · 峰值 252 tok/s · 成本 $0.011
DeepSeek#6
deepseek-v4-flash
完成
用户让我写一个单文件HTML,使用Three.js构建一个3D场景,包含: 1. 使用importmap引入Three.js 2. 缓慢自转的发光水晶核心 3. 环绕粒子 4. 合理的光照与材质 5. OrbitControls支持鼠标交互 6. canvas铺满视口并自适应 7. 脚本加载失败时显示错误信息 我将创建一个完整的单文件HTML,包含所有必要的代码。 我需要正确使用importmap映射,然后创建场景。 让我仔细想想水晶核心的实现: - 使用IcosahedronGeometry或OctahedronGeometry来制作水晶形状 - 使用发光材质,结合Emissive颜色 - 缓慢自转 环绕粒子: - 粒子系统围绕核心旋转 - 可以使用Points或者小几何体 光照: - 环境光 - 点光源或平行光 - 可能添加一些光晕效果 OrbitControls: - 从three/addons/导入 错误处理: - 使用window.onerror或try-catch捕获模块加载错误 让我写出完整的代码: 实际上,对于importmap,我需要这样设置: ```html <script type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> ``` 然后使用type="module"的script标签导入。 对于错误处理,我可以使用window.addEventListener('error')来捕获脚本加载错误,并在页面上显示。 让我构思一下水晶核心的实现: 水晶核心可以用IcosahedronGeometry,然后使用MeshPhysicalMaterial或者MeshStandardMaterial,设置emissive属性,加上一些透明度或者光泽。 或者我可以使用多个几何体组合成一个更复杂的水晶结构。 我会做一个五光十色的水晶核心,使用多个不同大小的八面体或二十面体堆叠,或者使用一个主要几何体加上一些边缘线条。 让我用一个简单但视觉效果好的方案: - 主核心:一个二十面体,使用半透明且发光的材质 - 内部还有一个更小的核心,发出更强的光 - 外部有一些闪烁的粒子环绕 对于环绕粒子,我会创建大量小点围绕核心旋转,形成动态的光环。 让我开始写代码: 其实我可以让水晶核心更精致一些,比如使用ShaderMaterial或者自定义几何体。但为了保持代码清晰且运行良好,我会使用标准Three.js组件。 我来设计一个层次丰富的水晶核心: 1. 外层:一个较大的二十面体,半透明,带有emissive发光 2. 中层:一个较小的八面体,颜色不同,也发光 3. 内层:一个很小的球体,亮度最高,模拟核心能量 环绕粒子系统: - 使用Points,粒子数量500-1000 - 分布在环形轨道上 - 缓慢旋转 - 粒子大小和颜色变化 光照: - 环境光提供基础照明 - 两个点光源从不同方向照射,产生立体感 - 可能会添加一些体积光效果(用简单的Mesh代替) 让我开始编码吧。 实际上,我需要考虑一下"水晶"的视觉效果。水晶通常具有: - 半透明性 - 高折射率(看起来闪烁) - 棱角分明 - 内部有光线反射 在Three.js中,我可以使用MeshPhysicalMaterial的transmission、roughness、metalness等属性来模拟水晶效果。 好的,让我开始写代码: 我会使用一个二十面体作为基础,然后添加一些边缘线框来增强水晶的几何感。 对于粒子,我会创建两种粒子: 1. 环绕粒子:分布在核心周围,形成光环 2. 闪烁粒子:随机分布在空间中的小点 实际上,让我简化一下,创建一个美观且性能良好的场景。 我这里的设计方案: 核心组成: - 主要晶体:IcosahedronGeometry,使用MeshPhysicalMaterial,设置transmission、emissive、roughness等 - 内核心:较小的IcosahedronGeometry,高亮度emissive - 边缘线条:使用EdgesGeometry和LineSegments 粒子系统: - 大量粒子分布在环形轨道上 - 使用PointsMaterial,带有纹理(圆形渐变) - 粒子颜色渐变 轨道控制:OrbitControls 自适应:窗口resize事件 错误处理:捕获加载错误并显示 好的,我准备开始写了。 等等,对于粒子纹理,我需要生成一个圆形渐变纹理,而不是加载外部图片。我可以用Canvas生成。 好的,开始写完整代码: 我注意到,为了确保水晶看起来发光且漂亮,材质的设置很关键。对于MeshPhysicalMaterial,我会设置: - color: 淡蓝色或紫色 - emissive: 明亮的蓝色或紫色 - emissiveIntensity: 较高 - metalness: 0.1 - roughness: 0.2 - transparent: true - opacity: 0.8 - envMap: 可以添加环境映射,但需要加载HDR,有点复杂,先不加了 或者更简单一点,使用MeshStandardMaterial配合emissive。 实际上,要做出真正漂亮的发光水晶效果,可以使用自定义ShaderMaterial,但为了代码简洁且易懂,我用标准材质加上一些技巧。 让我用MeshPhysicalMaterial并设置transmission来实现玻璃般的水晶效果,再加上emissive发光。 好的,开始编码: 让我再想一下粒子系统的具体实现: 粒子分布在多个环形轨道上,每个轨道半径不同,颜色也不同。 粒子围绕Y轴旋转,同时每个粒子有轻微的上下浮动。 这样看起来就像星系环绕一样。 我会创建大约2000个粒子,分布在3-4个轨道环上。 粒子颜色从蓝色到紫色渐变,与核心颜色协调。 好的,开始写了。 实际上我需要考虑,为了让粒子看起来更亮更闪烁,可以使用PointsMaterial的size attenuation和自定义纹理。 让我用canvas生成一个光晕纹理。 好的,开始写完整的代码了。我需要确保所有导入正确,并且场景美观。 让我再确认一下版本:three@0.171.0 好的,开始写完整代码: ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>发光水晶核心 3D 场景</title> ... </head> <body> ... </body> </html> ``` 让我完整地写出来。 对于错误处理,我会在页面顶部添加一个错误显示区域,然后监听error事件或使用try-catch。 由于使用的是module脚本,错误处理要特别注意。我可以在window上监听'unhandledrejection'事件,因为模块加载失败会触发Promise rejection。 同时,我也可以使用window.onerror来捕获其他错误。 好的,开始写完整代码了。 让我想一个更好的水晶核心实现方案,让视觉效果更震撼: 1. 使用一个IcosahedronGeometry(半径2, 细节2)作为主晶体 2. 材质使用MeshPhysicalMaterial,设置: - color: #4a7aff (亮蓝色) - emissive: #44aaff - emissiveIntensity: 2 - metalness: 0.3 - roughness: 0.1 - transparent: true - opacity: 0.85 - clearcoat: 0.5 - clearcoatRoughness: 0.3 - envMapIntensity: 1 3. 内部再添加一个较小的二十面体,颜色为紫色,发光更强 4. 添加边缘线框,使用白色或淡蓝色线条 这样应该能做出很漂亮的水晶效果。 对于粒子,我会有三种粒子环: 1. 内环:半径3.5,粒子密集,颜色偏蓝 2. 中环:半径5.5,粒子适中,颜色偏紫 3. 外环:半径8,粒子稀疏,颜色偏粉 每个环的粒子数量不同,旋转速度也不同。 这样整体效果会非常丰富。 好了开始写吧。 实际编写时,我还需要添加一些辅助效果: - 核心周围的光晕(可以用一个Sprite或者一个半透明的球体) - 或者使用点光源产生的辉光 我决定在核心位置添加一个点光源,产生真实的照明效果,同时核心材质自身的emissive会发光。 可能还需要添加一个背景,使用深色渐变或者星空背景。 为了简洁,我会使用深色纯色背景,配合一些微小的闪烁星星(作为背景粒子)。 好的,计划已经足够,开始写完整代码。 让我注意一下,使用importmap时,three/addons/的映射需要以斜杠结尾。 我查了一下,正确的映射格式是: ``` "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" ``` 这样导入 OrbitControls 时就可以写: ``` import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; ``` 好的,开始写完整代码了。 我还要注意,three@0.171.0 的版本号在URL中是否正确。格式应该是: `https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js` 这个URL看起来没问题。 现在开始写完整的HTML文件。 让我再想想有没有什么遗漏的: 1. ✅ 自转的水晶核心 2. ✅ 环绕粒子 3. ✅ 光照与材质 4. ✅ OrbitControls 5. ✅ canvas铺满视口并自适应 6. ✅ 错误处理显示文字 7. ✅ importmap引入Three.js 好的,开始写了。 关于错误处理,我要确保在模块加载失败时显示错误。因为模块是异步加载的,所以需要使用unhandledrejection或者直接在模块内部try-catch。 我可以在页面中添加一个div用于显示错误,然后监听error和unhandledrejection事件。 同时,在模块脚本内部,也可以使用try-catch来捕获运行时错误。 让我写一个完善的错误处理方案。 实际上,模块加载失败会触发window上的'unhandledrejection'事件,因为import()返回一个Promise。 但type="module"的script标签如果src加载失败,也会触发error事件。 让我在module脚本中使用try-catch来捕获所有可能的错误。 不过,如果模块本身因为依赖加载失败而无法执行,那么模块内的try-catch也无法捕获到依赖加载失败的错误。 所以,最可靠的方案是监听window的'unhandledrejection'和'error'事件。 让我在外层(非模块脚本)中设置错误监听,然后在模块脚本中执行主要逻辑。 好的,我打算这样组织代码: 1. 在head中设置importmap 2. 在body中放置一个错误显示div 3. 使用一个外层的script(非module)来设置错误监听 4. 使用type="module"的script来构建场景 或者,我可以在module脚本中直接使用try-catch,并在catch中更新UI显示错误。 但是为了捕获import map加载失败的情况,我需要在module脚本外部监听错误。 更合理的方式: 在body开头放一个错误显示区域,然后在module脚本中用try-catch包裹所有逻辑,如果出错就在错误区域显示错误信息。 同时,在window上添加'unhandledrejection'和'error'监听,作为备用。 好,就这么办。 实际上,我可以在一个单独的脚本(非module)中设置错误监听,然后在module脚本中执行主要逻辑。这样即使module脚本加载失败,错误监听也能生效。 但问题是,如果module脚本因为import映射失败而无法加载,那么module脚本中的代码永远不会执行,也就无法显示自定义错误信息。 所以,我需要在非module脚本中设置全局的错误监听,并在错误发生时,在页面上显示错误信息。 让我这样安排: ```html <script> // 全局错误监听,在模块加载失败时显示错误 window.addEventListener('unhandledrejection', (event) => { showError('模块加载失败: ' + event.reason); }); window.addEventListener('error', (event) => { if (event.target && event.target.tagName === 'SCRIPT') { showError('脚本加载失败: ' + (event.target.src || '未知错误')); } }); function showError(msg) { const el = document.getElementById('error-display'); if (el) { el.textContent = msg; el.style.display = 'block'; } } </script> ``` 然后在module脚本中: ```javascript import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; try { // 构建场景 } catch (e) { document.getElementById('error-display').textContent = '场景构建失败: ' + e.message; document.getElementById('error-display').style.display = 'block'; } ``` 这样双重保障。 好,开始写完整代码吧。 让我再想想水晶核心的视觉效果,我决定采用多层结构: 外层晶体:大二十面体,半透明,淡蓝色,发光 中层晶体:中等八面体,半透明,紫色,强发光 内层核心:小球体,白色,极强发光(模拟能量源) 粒子系统:三个环,不同颜色和速度 这样层次感丰富,视觉效果好。 好的,开始写完整代码。 实际上,我可能还需要考虑一下性能问题。粒子数量、几何体细节等要适中。 对于粒子,我使用BufferGeometry,手动设置位置和颜色。 粒子数量:内环800,中环600,外环400,总共1800个粒子。 每个粒子使用圆形纹理,大小根据环不同有所变化。 好了,开始写了。 我修正一下我的方案,为了让水晶更"水晶",我会使用: 1. 主要几何体:IcosahedronGeometry,细节1或2 2. 材质使用MeshPhysicalMaterial,设置transmission和ior来模拟玻璃质感 3. 同时设置emissive来模拟发光 这样既有玻璃的透明折射感,又有发光效果。 但是,由于我们没有环境贴图,transmission效果可能不太明显。所以,我可以在场景中添加一些环境光或者使用CubeTextureLoader加载环境贴图。 为了简化,我可能会使用一个简单的环境贴图(使用Canvas生成一个渐变纹理作为环境贴图),或者干脆不使用transmission,而是使用emissive配合透明度。 让我选择一种更简单但视觉效果依然很好的方案: 使用MeshStandardMaterial,设置: - color: 亮蓝色 - emissive: 蓝色/紫色 - emissiveIntensity: 1.5 - metalness: 0.2 - roughness: 0.3 - transparent: true - opacity: 0.9 这样看起来就是一块发光的晶体,虽然不是玻璃质感,但发光效果很好。 同时,我可以在晶体表面添加一些线框,突出几何形状。 好的,就这么办。 我还会在核心位置添加一个点光源,照亮周围的粒子。 好了,开始写完整代码了。这次真的开始写了。 让我写出一个完整的、漂亮的3D场景。 我还想到,为了增强发光效果,可以在核心周围添加一个光晕(使用Sprite或者一个始终面向相机的平面),但这样可能会遮挡视线,所以谨慎使用。 或者,我可以使用一个半透明的球体包裹核心,产生辉光效果。 这个可以作为一个额外的视觉元素。 好吧,让我不要过度设计,先写出一个干净利落的版本。 最终我决定的核心结构: 1. 主晶体:IcosahedronGeometry(1.8, 2),蓝色发光材质 2. 副晶体:OctahedronGeometry(1.2, 0),紫色发光材质,嵌套在主晶体内部 3. 内核心:SphereGeometry(0.6, 32, 32),白色高亮发光 4. 边缘线框:从主晶体提取EdgesGeometry,使用LineSegments 粒子系统: 1. 光环1:半径3-4,蓝色粒子,顺时针旋转 2. 光环2:半径5-6,紫色粒子,逆时针旋转 3. 光环3:半径7-9,粉色粒子,顺时针旋转,速度较慢 光照: 1. 环境光:柔和的蓝色调 2. 主光源:点光源,从右上角照射 3. 辅助光源:点光源,从左下角照射,颜色偏暖 4. 核心点光源:位于核心中心,发出蓝白色光 背景:深蓝色到黑色的渐变(可以使用CSS或场景背景色) Ok,现在开始写代码。 不对,使用场景背景色的话,我可以用THREE.Color设置一个深色渐变,但
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>发光水晶核心 3D</title>
    <script type="importmap">
        {
            "imports": {
                "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
                "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
            }
        }
    </script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            overflow: hidden;
            background: #0a0e1a;
            font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }

        #error-display {
            position: fixed;
            top: 24px;
            left: 50%;
            transform: translateX(-50%);
            max-width: 90vw;
            background: rgba(180, 30, 30, 0.92);
            color: #fff;
            padding: 18px 32px;
            border-radius: 14px;
            font-size: 15px;
            font-weight: 500;
            letter-spacing: 0.3px;
            text-align: center;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
            border: 1px solid rgba(255, 120, 120, 0.3);
            backdrop-filter: blur(6px);
            z-index: 999;
            display: none;
            line-height: 1.6;
            pointer-events: none;
            word-break: break-word;
        }
        #error-display.show {
            display: block;
            pointer-events: auto;
        }
        #error-display .error-icon {
            font-size: 20px;
            margin-right: 10px;
        }
        #error-display .error-detail {
            font-size: 13px;
            opacity: 0.85;
            margin-top: 6px;
            font-weight: 400;
        }

        #loading-tip {
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            color: rgba(255, 255, 255, 0.3);
            font-size: 13px;
            font-weight: 300;
            letter-spacing: 1px;
            z-index: 1;
            transition: opacity 0.8s;
            pointer-events: none;
            background: rgba(0, 0, 0, 0.3);
            padding: 8px 20px;
            border-radius: 20px;
            backdrop-filter: blur(4px);
        }
        #loading-tip.hide {
            opacity: 0;
        }
    </style>
</head>
<body>

    <div id="error-display">
        <span class="error-icon">⚠️</span> 场景加载遇到问题
        <div class="error-detail" id="error-detail-text"></div>
    </div>

    <div id="loading-tip">✦ 加载中 ...</div>

    <script>
        // ============================================================
        // 全局错误捕获(在模块加载之前生效)
        // ============================================================
        (function() {
            const errorEl = document.getElementById('error-display');
            const detailEl = document.getElementById('error-detail-text');

            function showError(msg, detail) {
                const text = msg || '未知错误';
                errorEl.querySelector('.error-icon').textContent = '⚠️';
                errorEl.querySelector('.error-icon').style.display = 'inline';
                if (detail) {
                    detailEl.textContent = detail;
                    detailEl.style.display = 'block';
                } else {
                    detailEl.style.display = 'none';
                }
                // 在错误文本中也显示详情
                const mainSpan = errorEl.childNodes[2] || errorEl.firstChild;
                if (detail) {
                    errorEl.innerHTML =
                        '<span class="error-icon">⚠️</span> ' +
                        text +
                        '<div class="error-detail">' + detail + '</div>';
                } else {
                    errorEl.innerHTML =
                        '<span class="error-icon">⚠️</span> ' + text;
                }
                errorEl.classList.add('show');
                document.getElementById('loading-tip')?.classList.add('hide');
            }

            // 捕获未处理的 Promise 拒绝(模块加载失败等)
            window.addEventListener('unhandledrejection', function(e) {
                const reason = e.reason;
                let msg = '模块加载失败或网络错误';
                let detail = '';
                if (reason instanceof Error) {
                    detail = reason.message || reason.stack || '';
                } else if (typeof reason === 'string') {
                    detail = reason;
                } else if (reason && reason.toString) {
                    detail = reason.toString();
                }
                // 限制长度
                if (detail.length > 300) detail = detail.slice(0, 300) + '…';
                showError(msg, detail || '请检查网络连接或刷新重试');
                e.preventDefault();
            });

            // 捕获常规脚本错误
            window.addEventListener('error', function(e) {
                // 如果是资源加载错误(如 script 标签)
                if (e.target && e.target.tagName) {
                    const tag = e.target.tagName.toLowerCase();
                    if (tag === 'script' || tag === 'link' || tag === 'img') {
                        const src = e.target.src || e.target.href || '';
                        showError('资源加载失败', '来源: ' + (src.slice(0, 200) || '未知'));
                        e.preventDefault();
                        return;
                    }
                }
                // 运行时错误
                const msg = e.message || '运行时错误';
                const detail = (e.filename ? e.filename.slice(-100) : '') +
                    (e.lineno ? ':' + e.lineno : '');
                showError(msg, detail || '未知位置');
                e.preventDefault();
            });

            // 也捕获 console.error 风格的错误(兜底)
            const origOnError = window.onerror;
            window.onerror = function(msg, url, line, col, err) {
                const detail = (url ? url.split('/').pop() : '') +
                    (line ? ':' + line : '') +
                    (col ? ':' + col : '');
                showError(msg || '脚本错误', detail || (err ? err.message : ''));
                if (origOnError) return origOnError.apply(this, arguments);
                return true;
            };

            // 暴露给模块使用
            window.__showSceneError = showError;
        })();
    </script>

    <script type="module">
        // ============================================================
        // 模块入口:构建 3D 场景
        // ============================================================
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        const showError = window.__showSceneError || function() {};

        try {

            // ---------- 基础场景 ----------
            const scene = new THREE.Scene();

            // 用 Canvas 生成径向渐变背景纹理
            const bgCanvas = document.createElement('canvas');
            bgCanvas.width = 1024;
            bgCanvas.height = 1024;
            const bgCtx = bgCanvas.getContext('2d');
            const bgGradient = bgCtx.createRadialGradient(512, 512, 0, 512, 512, 512);
            bgGradient.addColorStop(0, '#10182e');
            bgGradient.addColorStop(0.4, '#0c1220');
            bgGradient.addColorStop(1, '#04070e');
            bgCtx.fillStyle = bgGradient;
            bgCtx.fillRect(0, 0, 1024, 1024);
            const bgTexture = new THREE.CanvasTexture(bgCanvas);
            scene.background = bgTexture;

            // ---------- 相机 ----------
            const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
            camera.position.set(6, 3.5, 10);
            camera.lookAt(0, 0, 0);

            // ---------- 渲染器 ----------
            const renderer = new THREE.WebGLRenderer({
                antialias: true,
                alpha: false,
            });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1.2;
            document.body.prepend(renderer.domElement);

            // ---------- 控制器 ----------
            const controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.06;
            controls.minDistance = 2.5;
            controls.maxDistance = 25;
            controls.autoRotate = false;
            controls.rotateSpeed = 0.8;
            controls.zoomSpeed = 1.2;
            controls.target.set(0, 0, 0);
            controls.update();

            // ---------- 光照 ----------
            // 环境光 - 柔和蓝紫
            const ambient = new THREE.AmbientLight(0x4466aa, 0.5);
            scene.add(ambient);

            // 主光源 - 暖色,从右上方
            const mainLight = new THREE.DirectionalLight(0xffd8a0, 2.0);
            mainLight.position.set(8, 12, 6);
            mainLight.castShadow = true;
            mainLight.shadow.mapSize.width = 1024;
            mainLight.shadow.mapSize.height = 1024;
            scene.add(mainLight);

            // 辅助光源 - 冷色,从左下方
            const fillLight = new THREE.DirectionalLight(0x4488ff, 1.2);
            fillLight.position.set(-6, -2, 8);
            scene.add(fillLight);

            // 背光 - 紫色
            const rimLight = new THREE.DirectionalLight(0xaa66ff, 0.8);
            rimLight.position.set(-4, 6, -10);
            scene.add(rimLight);

            // 核心点光源 - 从内部照亮粒子
            const coreLight = new THREE.PointLight(0x66aaff, 3.0, 12);
            coreLight.position.set(0, 0, 0);
            scene.add(coreLight);

            // 额外氛围光
            const ambient2 = new THREE.HemisphereLight(0x4488ff, 0x442266, 0.4);
            scene.add(ambient2);

            // ---------- 水晶核心 ----------
            // 主晶体:二十面体
            const crystalGeo = new THREE.IcosahedronGeometry(1.8, 2);
            const crystalMat = new THREE.MeshPhysicalMaterial({
                color: 0x4a8aff,
                emissive: 0x3366ff,
                emissiveIntensity: 1.8,
                metalness: 0.15,
                roughness: 0.25,
                transparent: true,
                opacity: 0.88,
                clearcoat: 0.4,
                clearcoatRoughness: 0.3,
                envMapIntensity: 0.6,
                side: THREE.DoubleSide,
                wireframe: false,
            });
            const crystal = new THREE.Mesh(crystalGeo, crystalMat);
            crystal.castShadow = true;
            crystal.receiveShadow = true;
            scene.add(crystal);

            // 内部晶体:八面体(旋转错位,增加层次)
            const innerGeo = new THREE.OctahedronGeometry(1.2, 0);
            const innerMat = new THREE.MeshPhysicalMaterial({
                color: 0xaa77ff,
                emissive: 0x8844ff,
                emissiveIntensity: 2.5,
                metalness: 0.2,
                roughness: 0.15,
                transparent: true,
                opacity: 0.75,
                clearcoat: 0.6,
                clearcoatRoughness: 0.2,
                side: THREE.DoubleSide,
            });
            const innerCrystal = new THREE.Mesh(innerGeo, innerMat);
            innerCrystal.castShadow = true;
            innerCrystal.receiveShadow = true;
            scene.add(innerCrystal);

            // 内核:高亮能量球
            const coreGeo = new THREE.SphereGeometry(0.55, 32, 32);
            const coreMat = new THREE.MeshPhysicalMaterial({
                color: 0xffffff,
                emissive: 0x88ccff,
                emissiveIntensity: 4.0,
                metalness: 0.0,
                roughness: 0.05,
                transparent: true,
                opacity: 0.95,
            });
            const coreSphere = new THREE.Mesh(coreGeo, coreMat);
            coreSphere.position.set(0, 0, 0);
            scene.add(coreSphere);

            // 边缘线框 - 增强水晶几何感
            const edgesGeo = new THREE.EdgesGeometry(new THREE.IcosahedronGeometry(1.82, 2));
            const edgesMat = new THREE.LineBasicMaterial({
                color: 0x88ddff,
                transparent: true,
                opacity: 0.35,
            });
            const edgesLine = new THREE.LineSegments(edgesGeo, edgesMat);
            scene.add(edgesLine);

            // 第二层线框 - 内部晶体
            const edgesGeo2 = new THREE.EdgesGeometry(new THREE.OctahedronGeometry(1.22, 0));
            const edgesMat2 = new THREE.LineBasicMaterial({
                color: 0xcc88ff,
                transparent: true,
                opacity: 0.25,
            });
            const edgesLine2 = new THREE.LineSegments(edgesGeo2, edgesMat2);
            scene.add(edgesLine2);

            // 核心光晕:半透明发光球体
            const glowGeo = new THREE.SphereGeometry(2.4, 32, 32);
            const glowMat = new THREE.MeshBasicMaterial({
                color: 0x4488ff,
                transparent: true,
                opacity: 0.08,
                side: THREE.BackSide,
            });
            const glowSphere = new THREE.Mesh(glowGeo, glowMat);
            scene.add(glowSphere);

            // 第二层光晕
            const glowGeo2 = new THREE.SphereGeometry(3.2, 32, 32);
            const glowMat2 = new THREE.MeshBasicMaterial({
                color: 0x6644ff,
                transparent: true,
                opacity: 0.04,
                side: THREE.BackSide,
            });
            const glowSphere2 = new THREE.Mesh(glowGeo2, glowMat2);
            scene.add(glowSphere2);

            // ---------- 粒子纹理 ----------
            function createParticleTexture() {
                const canvas = document.createElement('canvas');
                canvas.width = 64;
                canvas.height = 64;
                const ctx = canvas.getContext('2d');
                const center = 32;
                const radius = 30;
                const gradient = ctx.createRadialGradient(center, center, 0, center, center, radius);
                gradient.addColorStop(0, 'rgba(255,255,255,1)');
                gradient.addColorStop(0.15, 'rgba(255,255,255,0.95)');
                gradient.addColorStop(0.5, 'rgba(200,220,255,0.6)');
                gradient.addColorStop(1, 'rgba(255,255,255,0)');
                ctx.fillStyle = gradient;
                ctx.fillRect(0, 0, 64, 64);
                return new THREE.CanvasTexture(canvas);
            }
            const particleTexture = createParticleTexture();

            // ---------- 环绕粒子系统 ----------
            // 三个光环,不同颜色、半径、速度
            const rings = [];

            function createRing(params) {
                const {
                    count,
                    radiusMin,
                    radiusMax,
                    ySpread,
                    color1,
                    color2,
                    size,
                    speed,
                    opacity,
                } = params;

                const positions = new Float32Array(count * 3);
                const colors = new Float32Array(count * 3);
                const sizes = new Float32Array(count);
                const phases = new Float32Array(count);

                const c1 = new THREE.Color(color1);
                const c2 = new THREE.Color(color2);

                for (let i = 0; i < count; i++) {
                    const angle = Math.random() * Math.PI * 2;
                    const radius = radiusMin + Math.random() * (radiusMax - radiusMin);
                    const x = Math.cos(angle) * radius;
                    const z = Math.sin(angle) * radius;
                    const y = (Math.random() - 0.5) * ySpread;

                    positions[i * 3] = x;
                    positions[i * 3 + 1] = y;
                    positions[i * 3 + 2] = z;

                    const mix = Math.random();
                    const col = c1.clone().lerp(c2, mix);
                    colors[i * 3] = col.r;
                    colors[i * 3 + 1] = col.g;
                    colors[i * 3 + 2] = col.b;

                    sizes[i] = size * (0.6 + Math.random() * 0.8);
                    phases[i] = Math.random() * Math.PI * 2;
                }

                const geo = new THREE.BufferGeometry();
                geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
                geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
                geo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
                geo.userData = { phases };

                const mat = new THREE.PointsMaterial({
                    size: 0.22,
                    map: particleTexture,
                    blending: THREE.AdditiveBlending,
                    depthWrite: false,
                    transparent: true,
                    opacity: opacity || 0.9,
                    sizeAttenuation: true,
                    vertexColors: true,
                });

                const points = new THREE.Points(geo, mat);
                points.userData = {
                    speed: speed || 0.3,
                    radiusMin,
                    radiusMax,
                    ySpread,
                    offset: Math.random() * Math.PI * 2,
                };
                return points;
            }

            // 内环:蓝-青
            const ring1 = createRing({
                count: 900,
                radiusMin: 2.8,
                radiusMax: 4.0,
                ySpread: 0.8,
                color1: '#44aaff',
                color2: '#88ddff',
                size: 0.18,
                speed: 0.45,
                opacity: 0.95,
            });
            scene.add(ring1);
            rings.push(ring1);

            // 中环:紫-粉
            const ring2 = createRing({
                count: 700,
                radiusMin: 4.6,
                radiusMax: 6.2,
                ySpread: 1.2,
                color1: '#aa66ff',
                color2: '#ff88cc',
                size: 0.20,
                speed: -0.28,
                opacity: 0.85,
            });
            scene.add(ring2);
            rings.push(ring2);

            // 外环:蓝紫-淡粉
            const ring3 = createRing({
                count: 500,
                radiusMin: 7.0,
                radiusMax: 9.5,
                ySpread: 1.8,
                color1: '#6688ff',
                color2: '#cc88ff',
                size: 0.25,
                speed: 0.15,
                opacity: 0.7,
            });
            scene.add(ring3);
            rings.push(ring3);

            // 额外:极稀疏大环
            const ring4 = createRing({
                count: 200,
                radiusMin: 10.5,
                radiusMax: 13.0,
                ySpread: 2.5,
                color1: '#ff88aa',
                color2: '#ffaa88',
                size: 0.30,
                speed: -0.08,
                opacity: 0.4,
            });
            scene.add(ring4);
            rings.push(ring4);

            // ---------- 背景星星 ----------
            const starCount = 1200;
            const starPos = new Float32Array(starCount * 3);
            const starSizes = new Float32Array(starCount);
            for (let i = 0; i < starCount; i++) {
                const r = 30 + Math.random() * 70;
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos(2 * Math.random() - 1);
                starPos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
                starPos[i * 3 + 1] = r * Math.cos(phi) * (0.4 + 0.6 * Math.random());
                starPos[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
                starSizes[i] = 0.08 + Math.random() * 0.25;
            }
            const starGeo = new THREE.BufferGeometry();
            starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
            starGeo.setAttribute('size', new THREE.BufferAttribute(starSizes, 1));
            const starMat = new THREE.PointsMaterial({
                color: 0xffffff,
                size: 0.12,
                map: particleTexture,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                transparent: true,
                opacity: 0.6,
                sizeAttenuation: true,
            });
            const stars = new THREE.Points(starGeo, starMat);
            scene.add(stars);

            // ---------- 微尘粒子(近景漂浮) ----------
            const dustCount = 400;
            const dustPos = new Float32Array(dustCount * 3);
            for (let i = 0; i < dustCount; i++) {
                const r = 2.5 + Math.random() * 8;
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos(2 * Math.random() - 1);
                dustPos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
                dustPos[i * 3 + 1] = r * Math.cos(phi) * 0.6;
                dustPos[i * 3 + 2] = r * Math.sin(phi) * Math.sin(theta);
            }
            const dustGeo = new THREE.BufferGeometry();
            dustGeo.setAttribute('position', new THREE.BufferAttribute(dustPos, 3));
            const dustMat = new THREE.PointsMaterial({
                color: 0x88ccff,
                size: 0.04,
                map: particleTexture,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                transparent: true,
                opacity: 0.25,
                sizeAttenuation: true,
            });
            const dust = new THREE.Points(dustGeo, dustMat);
            scene.add(dust);

            // ---------- 辅助地面微光(视觉引导) ----------
            const glowRingGeo = new THREE.RingGeometry(2.5, 3.8, 64);
            const glowRingMat = new THREE.MeshBasicMaterial({
                color: 0x4488ff,
                transparent: true,
                opacity: 0.06,
                side: THREE.DoubleSide,
                depthWrite: false,
            });
            const glowRing = new THREE.Mesh(glowRingGeo, glowRingMat);
            glowRing.rotation.x = -Math.PI / 2;
            glowRing.position.y = -2.2;
            scene.add(glowRing);

            const glowRingGeo2 = new THREE.RingGeometry(5.0, 6.8, 64);
            const glowRingMat2 = new THREE.MeshBasicMaterial({
                color: 0x9966ff,
                transparent: true,
                opacity: 0.04,
                side: THREE.DoubleSide,
                depthWrite: false,
            });
            const glowRing2 = new THREE.Mesh(glowRingGeo2, glowRingMat2);
            glowRing2.rotation.x = -Math.PI / 2;
            glowRing2.position.y = -2.0;
            scene.add(glowRing2);

            // ---------- 隐藏 loading 提示 ----------
            document.getElementById('loading-tip')?.classList.add('hide');

            // ---------- 动画变量 ----------
            let clock = new THREE.Clock();

            // ---------- 窗口自适应 ----------
            function onResize() {
                const w = window.innerWidth;
                const h = window.innerHeight;
                camera.aspect = w / h;
                camera.updateProjectionMatrix();
                renderer.setSize(w, h);
            }
            window.addEventListener('resize', onResize);

            // ---------- 动画循环 ----------
            function animate() {
                const delta = clock.getDelta();
                const elapsed = clock.getElapsedTime();

                // 水晶核心自转
                crystal.rotation.x += delta * 0.20;
                crystal.rotation.y += delta * 0.35;
                crystal.rotation.z += delta * 0.10;

                innerCrystal.rotation.x += delta * 0.30;
                innerCrystal.rotation.y -= delta * 0.45;
                innerCrystal.rotation.z += delta * 0.15;

                coreSphere.rotation.x += delta * 0.10;
                coreSphere.rotation.y += delta * 0.20;

                // 线框跟随
                edgesLine.rotation.copy(crystal.rotation);
                edgesLine2.rotation.copy(innerCrystal.rotation);

                // 光晕呼吸
                const breath = 0.06 + 0.03 * Math.sin(elapsed * 0.5);
                glowSphere.material.opacity = breath;
                glowSphere2.material.opacity = breath * 0.6;

                // 内核发光脉动
                const pulse = 3.5 + 1.2 * Math.sin(elapsed * 0.7);
                coreSphere.material.emissiveIntensity = pulse;

                // 核心光强脉动
                coreLight.intensity = 2.5 + 0.8 * Math.sin(elapsed * 0.6);

                // 环绕粒子旋转 (使用 group 旋转更高效,但这里直接旋转 points)
                rings.forEach((ring, idx) => {
                    const speed = ring.userData.speed;
                    const offset = ring.userData.offset;
                    // 绕 Y 轴旋转
                    ring.rotation.y += delta * speed * 0.6;

                    // 粒子上下浮动(通过更新几何体位置)
                    const positions = ring.geometry.attributes.position.array;
                    const phases = ring.geometry.userData.phases;
                    const ySpread = ring.userData.ySpread;
                    const count = positions.length / 3;

                    // 每帧更新所有粒子的 y 会消耗性能,这里只做少量浮动
                    // 为了性能,只对部分粒子做浮动?或者直接用 shader?
                    // 这里采用轻量方案:每帧只更新少量粒子?不好。
                    // 改用整体轻微摆动 + 不更新单粒子位置,保持性能
                    // 实际上 points 整体旋转已经有效果,加上 Y 轴波浪用 sin(角度) 实现?
                    // 更简单:让 points 整体在 Y 轴上下移动 + 旋转
                });

                // 对 rings 整体加微小浮动
                rings.forEach((ring, i) => {
                    const baseY = 0;
                    const wave = Math.sin(elapsed * 0.3 + i * 2.0) * 0.08;
                    ring.position.y = wave;
                });

                // 背景星星缓慢旋转
                stars.rotation.y += delta * 0.008;
                stars.rotation.x += delta * 0.002;

                // 微尘缓慢飘移
                dust.rotation.y += delta * 0.02;
                dust.rotation.x += delta * 0.005;

                // 地面光环淡入淡出
                glowRing.material.opacity = 0.04 + 0.03 * Math.sin(elapsed * 0.4);
                glowRing2.material.opacity = 0.03 + 0.02 * Math.sin(elapsed * 0.5 + 1.2);

                // 更新控制器
                controls.update();

                // 渲染
                renderer.render(scene, camera);
                requestAnimationFrame(animate);
            }

            animate();

            // ---------- 额外:键盘快捷键提示 ----------
            console.log('🌟 水晶核心场景已加载 | 拖拽旋转 / 滚轮缩放');

        } catch (err) {
            // ---------- 模块内错误捕获 ----------
            const msg = err.message || '场景构建失败';
            const stack = err.stack || '';
            const detail = stack ? stack.split('\n').slice(0, 3).join(' ').slice(0, 250) : msg;
            if (typeof showError === 'function') {
                showError('场景初始化失败', detail);
            } else {
                const el = document.getElementById('error-display');
                if (el) {
                    el.innerHTML = '<span class="error-icon">⚠️</span> 场景初始化失败' +
                        '<div class="error-detail">' + detail + '</div>';
                    el.classList.add('show');
                }
            }
            document.getElementById('loading-tip')?.classList.add('hide');
            console.error('Three.js 场景错误:', err);
        }
    </script>
</body>
</html>
0.74s
首Token
85
思考TPS
83.0s · 7,076 tok
149
输出TPS
45.7s · 6,786 tok
13,862
总Tokens(官方)
输入 171
总用时 132s · 平均 108 tok/s · 峰值 638 tok/s · 成本 $0.0039
小米 MiMo-V2.5 Pro🥈
mimo-v2.5-pro
完成
用户要求我写一个单文件 HTML,包含: 1. 在 `<head>` 中用 `importmap` 引入 Three.js 2. 映射 `three` 到指定 URL 3. 映射 `three/addons/` 到指定 URL 4. 用 `<script type="module">` 构建 3D 场景 5. 场景内容: - 缓慢自转的发光水晶核心 - 环绕粒子 - 合理的光照与材质 - OrbitControls 支持鼠标拖拽旋转与滚轮缩放 - canvas 铺满视口并随窗口自适应 6. 脚本加载失败时显示错误文字 让我来写这个完整的 HTML 文件。 ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>发光水晶核心</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; } canvas { display: block; } #error { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #ff4444; font-size: 18px; font-family: sans-serif; text-align: center; background: rgba(0,0,0,0.8); padding: 20px 30px; border-radius: 8px; border: 1px solid #ff4444; } </style> 我需要添加 importmap 配置来加载 Three.js 及其插件,然后在模块脚本中初始化3D 场景、创建水晶核心几何体和发光效果,添加轨道控制器以支持交互旋转,并设置响应式布局和错误处理机制。 type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // Initialize const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.5; document.body.appendChild(renderer.domElement); // Orbit Controls const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.autoRotate = false; controls.enablePan = true; controls.enableZoom = true; // Camera position camera.position.set(0, 2, 8); controls.update(); // Scene background - dark space scene.background = new THREE.Color(0x000011); scene.fog = new THREE.FogExp2(0x000011, 0.05); // Crystal Core const crystalGeometry = new THREE.IcosahedronGeometry(1.5, 1); const crystalMaterial = new THREE.MeshPhysicalMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 0.5, metalness: 0.3, roughness: 0.1, transmission: 0.9, thickness: 1.5, ior: 2.33, clearcoat: 1.0, clearcoatRoughness: 0.1, envMapIntensity: 1.0, transparent: true, opacity: 0.8, side: THREE.DoubleSide }); const crystal = new THREE.Mesh(crystalGeometry, crystalMaterial); scene.add(crystal); // Inner glow core const innerGlowGeo = new THREE.IcosahedronGeometry(1.0, 0); const innerGlowMat = new THREE.MeshBasicMaterial({ color: 0x00ffff, transparent: true, opacity: 0.4, side: THREE.DoubleSide }); const innerGlow = new THREE.Mesh(innerGlowGeo, innerGlowMat); scene.add(innerGlow); // Point light for glow effect const coreLight = new THREE.PointLight(0x00ffff, 5, 20); coreLight.position.set(0, 0, 0); scene.add(coreLight); // Orbiting particles const particleCount = 2000; const particleGeometry = new THREE.BufferGeometry(); const positions = new Float32Array(particleCount * 3); const sizes = new Float32Array(particleCount); for (let i = 0; i < particleCount; i++) { const radius = 3 + Math.random() * 5; const theta = Math.random() * Math.PI * 2; const phi = Math.random() * Math.PI; positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta); positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta); positions[i * 3 + 2] = radius * Math.cos(phi); sizes[i] = Math.random() * 3 + 1; } particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); particleGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); const particleMaterial = new THREE.PointsMaterial({ color: 0x00aaff, size: 0.05, transparent: true, opacity: 0.6, blending: THREE.AdditiveBlending, depthWrite: false }); const particles = new THREE.Points(particleGeometry, particleMaterial); scene.add(particles); // Additional ambient light const ambientLight = new THREE.AmbientLight(0x111133, 0.5); scene.add(ambientLight); // Directional light const dirLight = new THREE.DirectionalLight(0xffffff, 0.3); dirLight.position.set(5, 5, 5); scene.add(dirLight); // Animation loop const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const elapsed = clock.getElapsedTime(); crystal.rotation.x = elapsed * 0.3; crystal.rotation.y = elapsed * 0.5; innerGlow.rotation.x = -elapsed * 0.2; innerGlow.rotation.y = -elapsed * 0.4; coreLight.intensity = 5 + Math.sin(elapsed * 3) * 2; particles.rotation.y = elapsed * 0.1; particles.rotation.x = elapsed * 0.05; controls.update(); renderer.render(scene, camera); } animate(); // Handle resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> The animation loop updates crystal and inner glow rotations over time, modulates the core light intensity with a sine wave, and gradually rotates the particle system while continuously rendering the scene. I'm setting up a window resize handler to maintain proper camera aspect ratio and renderer dimensions, then initializing the scene with tone mapping and fog settings to create depth. The crystal material uses physical rendering with transmission, metalness, and clearcoat properties for a realistic translucent appearance, while the inner glow provides a simpler emissive effect. I'm creating orbiting particles distributed in a spherical pattern with varying sizes, rendered as additive-blended points to enhance the ethereal atmosphere. The animation loop continuously rotates the crystal and inner glow in opposite directions, modulates the core light intensity with a sine wave for pulsing, and slowly rotates the particle system. A resize handler maintains proper aspect ratio when the window dimensions change. The error handling displays a message if module imports fail, showing the specific error and reloading instructions. The implementation uses ES modules with import maps for Three.js, so compatibility with modern browsers supporting these features is necessary.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发光水晶核心</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
        canvas { display: block; }
        #error-box {
            display: none;
            position: fixed;
            inset: 0;
            z-index: 999;
            background: rgba(0,0,0,0.92);
            color: #ff6b6b;
            font: 18px/1.6 "Segoe UI", system-ui, sans-serif;
            text-align: center;
            padding: 2em;
            flex-direction: column;
            justify-content: center;
            align-items: center;
        }
    </style>

    <script type="importmap">
    {
        "imports": {
            "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
            "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
        }
    }
    </script>

    <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
</script>
</head>
<body>

<div id="error-box">
    <p style="font-size:2em;margin-bottom:0.5em">⚠️ 脚本加载失败</p>
    <p id="error-msg"></p>
    <p style="margin-top:1em;color:#aaa">请检查网络连接或使用支持 importmap 的现代浏览器。</p>
</div>

<script type="module">
/* 全局错误捕获 */
window.addEventListener('error', e => {
    document.getElementById('error-box').style.display = 'flex';
    document.getElementById('error-msg').textContent = e.message || String(e);
});
window.addEventListener('unhandledrejection', e => {
    document.getElementById('error-box').style.display = 'flex';
    document.getElementById('error-msg').textContent = e.reason?.message || String(e.reason);
});

try {
    /* ========== 引入 ========== */
    const THREE = await import('three');
    const { OrbitControls } = await import('three/addons/controls/OrbitControls.js');

    /* ========== 渲染器 ========== */
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.4;
    document.body.appendChild(renderer.domElement);

    /* ========== 场景 / 相机 ========== */
    const scene    = new THREE.Scene();
    scene.background = new THREE.Color(0x020810);
    scene.fog = new THREE.FogExp2(0x020810, 0.035);

    const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 500);
    camera.position.set(0, 2, 8);

    /* ========== 控制器 ========== */
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.06;
    controls.minDistance   = 3;
    controls.maxDistance   = 30;

    /* ========== 灯光 ========== */
    scene.add(new THREE.AmbientLight(0x1a1a3e, 0.4));

    const coreLight = new THREE.PointLight(0x00e5ff, 8, 30, 1.5);
    scene.add(coreLight);

    const rimLight = new THREE.PointLight(0x8000ff, 4, 25, 1.8);
    rimLight.position.set(-4, 3, -2);
    scene.add(rimLight);

    const topLight = new THREE.DirectionalLight(0xffffff, 0.25);
    topLight.position.set(0, 10, 5);
    scene.add(topLight);

    /* ========== 水晶核心 ========== */
    const crystalGeo = new THREE.IcosahedronGeometry(1.3, 1);
    const crystalMat = new THREE.MeshPhysicalMaterial({
        color:             0x00e5ff,
        emissive:          0x00e5ff,
        emissiveIntensity: 1.8,
        metalness:         0.1,
        roughness:         0.05,
        transmission:      0.92,
        thickness:         2.0,
        ior:               2.4,
        clearcoat:         1.0,
        clearcoatRoughness: 0.05,
        transparent:       true,
        opacity:           0.85,
        side:              THREE.DoubleSide,
        envMapIntensity:   1.0,
    });
    const crystal = new THREE.Mesh(crystalGeo, crystalMat);
    scene.add(crystal);

    /* 内核光球 */
    const innerGeo = new THREE.IcosahedronGeometry(0.55, 0);
    const innerMat = new THREE.MeshBasicMaterial({
        color: 0x80ffff,
        transparent: true,
        opacity: 0.55,
    });
    const innerCore = new THREE.Mesh(innerGeo, innerMat);
    scene.add(innerCore);

    /* 外层光晕 */
    const glowSprite = (() => {
        const canvas  = document.createElement('canvas');
        canvas.width = canvas.height = 256;
        const ctx = canvas.getContext('2d');
        const gradient = ctx.createRadialGradient(128, 128, 0, 128, 128, 128);
        gradient.addColorStop(0,   'rgba(0,229,255,0.55)');
        gradient.addColorStop(0.4, 'rgba(0,180,255,0.18)');
        gradient.addColorStop(1,   'rgba(0,100,200,0)');
        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, 256, 256);
        const tex = new THREE.CanvasTexture(canvas);
        const mat = new THREE.SpriteMaterial({ map: tex, blending: THREE.AdditiveBlending, transparent: true, depthWrite: false });
        const sprite = new THREE.Sprite(mat);
        sprite.scale.set(6, 6, 1);
        return sprite;
    })();
    scene.add(glowSprite);

    /* ========== 环绕粒子 ========== */
    const PARTICLE_COUNT = 3000;
    const particleGeo = new THREE.BufferGeometry();
    const pPos   = new Float32Array(PARTICLE_COUNT * 3);
    const pColor = new Float32Array(PARTICLE_COUNT * 3);

    for (let i = 0; i < PARTICLE_COUNT; i++) {
        const r = 2.5 + Math.random() * 6;
        const theta = Math.random() * Math.PI * 2;
        const phi   = Math.acos(2 * Math.random() - 1);
        pPos[i * 3]     = r * Math.sin(phi) * Math.cos(theta);
        pPos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
        pPos[i * 3 + 2] = r * Math.cos(phi);

        const c = new THREE.Color().setHSL(0.52 + Math.random() * 0.12, 0.9, 0.5 + Math.random() * 0.4);
        pColor[i * 3]     = c.r;
        pColor[i * 3 + 1] = c.g;
        pColor[i * 3 + 2] = c.b;
    }
    particleGeo.setAttribute('position', new THREE.BufferAttribute(pPos, 3));
    particleGeo.setAttribute('color',    new THREE.BufferAttribute(pColor, 3));

    const particleMat = new THREE.PointsMaterial({
        size:            0.045,
        vertexColors:    true,
        transparent:     true,
        opacity:         0.75,
        blending:        THREE.AdditiveBlending,
        depthWrite:      false,
        sizeAttenuation: true,
    });
    const particles = new THREE.Points(particleGeo, particleMat);
    scene.add(particles);

    /* 轨道环线 */
    const orbitCount = 3;
    const orbitGroup = new THREE.Group();
    for (let i = 0; i < orbitCount; i++) {
        const curve = new THREE.EllipseCurve(0, 0, 3 + i * 1.6, 3 + i * 1.6, 0, Math.PI * 2, false, 0);
        const pts = curve.getPoints(128);
        const geo = new THREE.BufferGeometry().setFromPoints(pts.map(p => new THREE.Vector3(p.x, 0, p.y)));
        const mat = new THREE.LineBasicMaterial({ color: 0x00e5ff, transparent: true, opacity: 0.12 });
        const line = new THREE.Line(geo, mat);
        line.rotation.x = Math.PI * 0.5 * (i + 1) / orbitCount;
        line.rotation.z = 0.3 * i;
        orbitGroup.add(line);
    }
    scene.add(orbitGroup);

    /* ========== 动画 ========== */
    const clock = new THREE.Clock();

    function animate() {
        requestAnimationFrame(animate);
        const t = clock.getElapsedTime();

        /* 水晶自转 */
        crystal.rotation.x = t * 0.25;
        crystal.rotation.y = t * 0.4;
        innerCore.rotation.x = -t * 0.5;
        innerCore.rotation.y = -t * 0.35;

        /* 内核脉动 */
        const pulse = 1 + Math.sin(t * 2.8) * 0.12;
        crystal.scale.setScalar(pulse);
        innerCore.scale.setScalar(pulse * 1.1);
        coreLight.intensity = 8 + Math.sin(t * 2.8) * 3;

        /* 粒子环绕 */
        particles.rotation.y = t * 0.08;
        particles.rotation.x = Math.sin(t * 0.15) * 0.15;

        /* 轨道环缓慢旋转 */
        orbitGroup.rotation.y = t * 0.06;

        /* 光晕呼吸 */
        glowSprite.material.opacity = 0.55 + Math.sin(t * 2.8) * 0.15;

        controls.update();
        renderer.render(scene, camera);
    }
    animate();

    /* ========== 窗口自适应 ========== */
    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });

} catch (err) {
    document.getElementById('error-box').style.display = 'flex';
    document.getElementById('error-msg').textContent = err.message || String(err);
    throw err;
}
</script>

</body>
</html>
0.63s
首Token
88
思考TPS
24.3s · ≈2,126 tok
85
输出TPS
28.6s · ≈2,442 tok
4,568
总Tokens(官方)
输入 429
总用时 58.1s · 平均 86 tok/s · 峰值 225 tok/s · 成本 ¥0.0096
小米 MiMo-V2.5🥇
mimo-v2.5
完成
用户需要一个完整的单文件HTML,核心要求是使用Three.js0.171.0版本创建包含水晶核心、粒子系统的3D场景,并具备交互控制和错误处理功能。需要确保importmap配置正确,所有功能模块都通过标准ES模块方式导入。 首先确认技术要点:必须使用指定的镜像源版本,水晶核心需要发光效果和自转动画,粒子系统要环绕核心运动,光照需配合水晶材质,OrbitControls用于交互,还要处理窗口resize和脚本加载失败的情况。 考虑到单文件限制,所有代码都要内联。错误处理可以放在script标签的onerror事件里,用try-catch包装主要逻辑更可靠。水晶材质可以用MeshPhongMaterial配合自发光,粒子系统用Points和自定义着色器或基础材质。 整体结构就按标准的Three.js项目模板来,从场景初始化到动画循环,最后确保canvas自适应窗口。错误提示用固定定位的div元素,初始隐藏只在捕获错误时显示。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D 水晶核心场景</title>
    <script type="importmap">
        {
            "imports": {
                "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
                "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
            }
        }
    </script>
    <style>
        body { margin: 0; overflow: hidden; background: #000; }
        canvas { display: block; }
        #error {
            display: none;
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: #1a1a1a; color: #ff4444; font-family: sans-serif;
            font-size: 1.2rem; text-align: center; padding-top: 40vh;
            z-index: 1000;
        }
    </style>
</head>
<body>
    <div id="error">脚本加载失败。请检查网络连接或尝试刷新页面。</div>

    <script type="module" onerror="document.getElementById('error').style.display='block';">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        try {
            // 场景、相机、渲染器
            const scene = new THREE.Scene();
            scene.background = new THREE.Color(0x050510);
            scene.fog = new THREE.FogExp2(0x050510, 0.03);

            const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
            camera.position.set(0, 2, 8);

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

            // 轨道控制器
            const controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.minDistance = 4;
            controls.maxDistance = 20;
            controls.autoRotate = true;
            controls.autoRotateSpeed = 0.5;

            // 光源
            const ambientLight = new THREE.AmbientLight(0x222233, 0.5);
            scene.add(ambientLight);

            const pointLight1 = new THREE.PointLight(0x4488ff, 30, 20);
            pointLight1.position.set(5, 5, 5);
            scene.add(pointLight1);

            const pointLight2 = new THREE.PointLight(0x8844ff, 25, 20);
            pointLight2.position.set(-5, 3, -5);
            scene.add(pointLight2);

            const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
            directionalLight.position.set(0, 10, 10);
            scene.add(directionalLight);

            // 水晶核心
            const crystalGeometry = new THREE.IcosahedronGeometry(1, 2);
            const crystalMaterial = new THREE.MeshPhongMaterial({
                color: 0x66aaff,
                emissive: 0x3366ff,
                emissiveIntensity: 0.6,
                shininess: 100,
                transparent: true,
                opacity: 0.8,
                wireframe: false,
            });
            const crystal = new THREE.Mesh(crystalGeometry, crystalMaterial);
            scene.add(crystal);

            // 水晶外框
            const frameGeometry = new THREE.IcosahedronGeometry(1.2, 1);
            const frameMaterial = new THREE.MeshPhongMaterial({
                color: 0x99ccff,
                emissive: 0x2255ff,
                emissiveIntensity: 0.3,
                wireframe: true,
                transparent: true,
                opacity: 0.5,
            });
            const frame = new THREE.Mesh(frameGeometry, frameMaterial);
            scene.add(frame);

            // 环绕粒子
            const particleCount = 2000;
            const particleGeometry = new THREE.BufferGeometry();
            const positions = new Float32Array(particleCount * 3);
            const colors = new Float32Array(particleCount * 3);
            const sizes = new Float32Array(particleCount);

            for (let i = 0; i < particleCount; i++) {
                const radius = 2 + Math.random() * 5;
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos(2 * Math.random() - 1);

                positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
                positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
                positions[i * 3 + 2] = radius * Math.cos(phi);

                const color = new THREE.Color();
                color.setHSL(0.6 + Math.random() * 0.2, 0.8, 0.7 + Math.random() * 0.3);
                colors[i * 3] = color.r;
                colors[i * 3 + 1] = color.g;
                colors[i * 3 + 2] = color.b;

                sizes[i] = 0.03 + Math.random() * 0.06;
            }

            particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
            particleGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

            const particleMaterial = new THREE.PointsMaterial({
                size: 0.1,
                vertexColors: true,
                transparent: true,
                opacity: 0.9,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
            });

            const particles = new THREE.Points(particleGeometry, particleMaterial);
            scene.add(particles);

            // 粒子运动向量
            const particleVelocities = [];
            for (let i = 0; i < particleCount; i++) {
                particleVelocities.push({
                    speed: 0.001 + Math.random() * 0.003,
                    orbitSpeed: 0.002 + Math.random() * 0.004,
                    wobble: Math.random() * Math.PI * 2,
                });
            }

            // 动画循环
            const clock = new THREE.Clock();

            function animate() {
                requestAnimationFrame(animate);

                const elapsed = clock.getElapsedTime();

                // 核心自转
                crystal.rotation.y += 0.003;
                crystal.rotation.x += 0.001;
                frame.rotation.y -= 0.002;
                frame.rotation.z += 0.001;

                // 核心脉动
                const pulse = 1 + Math.sin(elapsed * 2) * 0.05;
                crystal.scale.set(pulse, pulse, pulse);
                frame.scale.set(pulse * 1.1, pulse * 1.1, pulse * 1.1);

                // 粒子环绕运动
                const posArray = particles.geometry.attributes.position.array;
                for (let i = 0; i < particleCount; i++) {
                    const i3 = i * 3;
                    const x = posArray[i3];
                    const y = posArray[i3 + 1];
                    const z = posArray[i3 + 2];

                    const angle = particleVelocities[i].orbitSpeed;
                    const cosA = Math.cos(angle);
                    const sinA = Math.sin(angle);

                    posArray[i3] = x * cosA - z * sinA;
                    posArray[i3 + 2] = x * sinA + z * cosA;
                    posArray[i3 + 1] += Math.sin(elapsed * particleVelocities[i].speed * 10 + particleVelocities[i].wobble) * 0.002;
                }
                particles.geometry.attributes.position.needsUpdate = true;

                // 粒子整体轻微旋转
                particles.rotation.y += 0.0005;

                // 光源动画
                pointLight1.position.x = Math.sin(elapsed * 0.5) * 5;
                pointLight1.position.z = Math.cos(elapsed * 0.5) * 5;
                pointLight2.position.x = Math.cos(elapsed * 0.3) * 5;
                pointLight2.position.z = Math.sin(elapsed * 0.3) * 5;

                controls.update();
                renderer.render(scene, camera);
            }

            // 窗口自适应
            window.addEventListener('resize', () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            });

            animate();
        } catch (e) {
            console.error(e);
            document.getElementById('error').style.display = 'block';
            document.getElementById('error').textContent = `场景初始化失败: ${e.message}`;
        }
    </script>
</body>
</html>
1.38s
首Token
67
思考TPS
3.25s · 217 tok
100
输出TPS
21.5s · 2,144 tok
2,361
总Tokens(官方)
输入 425
总用时 27.9s · 平均 95 tok/s · 峰值 221 tok/s · 成本 ¥0.0051
智谱 GLM-5.2#8
glm-5.2
完成
用户要求一个单文件 HTML,用 Three.js 构建可运行的 3D 场景。需求要点: 1. importmap 映射 three 和 three/addons/ 2. 缓慢自转的发光水晶核心 3. 环绕粒子 4. 合理的光照与材质 5. OrbitControls 支持拖拽旋转和滚轮缩放 6. canvas 铺满视口,窗口自适应 7. 脚本加载失败时显示明确错误文字 我需要让这个作品在视觉上非常有冲击力,不能只是基础 demo。让我设计一个有质感、有氛围的水晶核心场景: **视觉设计方向:** - 深色背景,但不是纯黑——用径向渐变营造氛围 - 水晶核心:用八面体/二十面体几何体,配合 MeshPhysicalMaterial(transmission、roughness、ior、thickness、clearcoat 等参数),让它真的像水晶 - 核心内部发光:用一个小的发光球体或多面体放在中心,发出强光 - 环绕粒子:用 Points + 自定义 shader 或者发光粒子,不要用方块,要用圆形带辉光的粒子 - 后处理:bloom 效果会让发光水晶真的"发光" - 多光源:环境光 + 点光源(核心位置)+ 一些彩色辅助光 **颜色方案:** 不要默认的蓝紫色。我想用青蓝-粉品红的对比,或者青绿-橙红。让我选择: - 主色调:深青色/蓝绿色 #0a1628 背景 - 强调色:青色 #00d9ff + 品红 #ff2e8a 或者 翡翠绿 #00ffaa + 珊瑚橙 #ff6b4a 我决定用:背景深空黑蓝,核心是青色水晶,发光是青色+品红对比,粒子是渐变色。 **技术实现:** 1. Three.js 场景 2. PerspectiveCamera 3. WebGLRenderer with antialias, alpha 4. EffectComposer + UnrealBloomPass 让水晶发光 5. MeshPhysicalMaterial with transmission 让水晶透明 6. OrbitControls 7. 多面体核心(IcosahedronGeometry) 8. 粒子系统(自定义 shader 让粒子是发光圆形) 9. 添加光晕 sprite 或者多层 mesh 增强发光感 10. 窗口 resize 监听 11. importmap 加载失败的 fallback **粒子细节:** - 不要用方块 - 用 shader 让每个粒子是发光圆形 - 粒子数量要够(2000-5000) - 粒子要有大小变化、颜色变化 - 粒子要缓慢围绕核心旋转 **水晶细节:** - 多层结构:外层透明水晶(physical material with transmission)+ 内层发光核心(发光 shader 或者 emissive material)+ 光晕 sprite - 缓慢自转 - 可能加一点细微的呼吸效果(scale 微变化) **加载失败提示:** importmap 加载失败比较难捕获,但可以在 script type="module" 中检测 typeof Three === 'undefined'。更可靠的方式是检查 window Three 是否加载完成,或者用 import 的 catch。 实际上 importmap 加载失败的话,import 会抛错,可以用 try/catch 包裹 import 语句。但 import 是静态的,不能用 try/catch 直接包裹。可以用动态 import: ```js try { const Three = await import('three'); // ... } catch (e) { showError(e); } ``` 这样比较优雅。 **OrbitControls 配置:** - enableDamping: true - minDistance / maxDistance 限制 - autoRotate 关闭(用户要求手动拖拽) 让我开始写代码。 ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>水晶核心 · Crystal Core</title> <style> /* CSS 样式 */ :root { --bg-deep: #050912; --accent-cyan: #00e5ff; --accent-magenta: #ff2e8a; --fg: #e8f4ff; } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { width: 100%; height: 100%; overflow: hidden; background: radial-gradient(ellipse at 30% 20%, rgba(0, 90, 130, 0.25) 0%, transparent 50%), radial-gradient(ellipse at 70% 80%, rgba(120, 20, 70, 0.2) 0%, transparent 50%), radial-gradient(circle at center, #0a1428 0%, #050912 70%, #02050a 100%); font-family: 'Inter', system-ui, sans-serif; color: var(--fg); } #canvas-container { position: fixed; inset: 0; width: 100%; height: 100%; } canvas { display: block; width: 100%; height: 100%; } /* UI 层 */ .ui-overlay { position: fixed; inset: 0; pointer-events: none; z-index: 10; } .brand { position: absolute; top: 28px; left: 32px; pointer-events: auto; } .brand .label { font-size: 10px; letter-spacing: 0.4em; text-transform: uppercase; color: rgba(232, 244, 255, 0.5); margin-bottom: 6px; font-weight: 500; } .brand .title { font-family: 'Space Grotesk', sans-serif; font-size: 28px; font-weight: 700; letter-spacing: 0.05em; color: #fff; } .brand .title span { color: var(--accent-cyan); } .info-panel { position: absolute; bottom: 28px; left: 32px; pointer-events: auto; font-size: 12px; letter-spacing: 0.05em; color: rgba(232, 244, 255, 0.6); line-height: 1.8; } .info-panel .row { display: flex; gap: 12px; align-items: center; } .info-panel .key { display: inline-block; padding: 2px 8px; border: 1px solid rgba(232, 244, 255, 0.2); border-radius: 4px; font-size: 10px; color: var(--fg); } .stats { position: absolute; top: 28px; right: 32px; text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: rgba(232, 244, 255, 0.5); letter-spacing: 0.1em; } .stats .value { color: var(--accent-cyan); } .vignette { position: fixed; inset: 0; pointer-events: none; background: radial-gradient(circle at center, transparent 50%, rgba(0,0,0,0.6) 100%); z-index: 5; } /* 错误提示 */ #error-message { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(20, 5, 15, 0.95); border: 1px solid rgba(255, 46, 138, 0.5); box-shadow: 0 0 60px rgba(255, 46, 138, 0.3); color: #ffd0e0; padding: 32px 40px; border-radius: 8px; max-width: 540px; text-align: center; font-size: 14px; line-height: 1.7; z-index: 100; display: none; } #error-message h2 { color: #ff5a9a; font-size: 18px; margin-bottom: 12px; letter-spacing: 0.1em; } #error-message.show { display: block; animation: shake 0.5s ease-out; } @keyframes shake { 0%, 100% { transform: translate(-50%, -50%); } 25% { transform: translate(-52%, -50%); } 75% { transform: translate(-48%, -50%); } } .loader { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 50; text-align: center; color: rgba(232, 244, 255, 0.6); font-size: 11px; letter-spacing: 0.4em; text-transform: uppercase; } .loader.hidden { display: none; } .loader .dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--accent-cyan); margin: 0 3px; animation: bounce 1.4s infinite ease-in-out; } .loader .dot:nth-child(2) { animation-delay: 0.2s; } .loader .dot:nth-child(3) { animation-delay: 0.4s; } @keyframes bounce { 0%, 80%, 100% { transform: scale(0.5); opacity: 0.3; } 40% { transform: scale(1.2); opacity: 1; } } </style> </head> <body> <div id="canvas-container"></div> <div class="vignette"></div> <div class="ui-overlay"> <div class="brand"> <div class="label">Reactant // Σ-07</div> <div class="title">CRYSTAL <span>CORE</span></div> </div> <div class="stats"> <div>FPS · <span class="value" id="fps">--</span></div> <div>PARTICLES · <span class="value" id="particles">--</span></div> </div> <div class="info-panel"> <div class="row"><span class="key">DRAG</span> rotate viewpoint</div> <div class="row"><span class="key">SCROLL</span> zoom in / out</div> </div> </div> <div class="loader" id="loader"> <div> <span class="dot"></span><span class="dot"></span><span class="dot"></span> </div> <div style="margin-top: 14px;">Initializing Reactant</div> </div> <div id="error-message"> <h2>MODULE LOAD FAILURE</h2> <div id="error-detail"></div> </div> <script type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> <script type="module"> // 用动态 import 以便捕获加载失败 let THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass; async function loadModules() { try { const threeModule = await import('three'); THREE = threeModule; const controlsModule = await import('three/addons/controls/OrbitControls.js'); OrbitControls = controlsModule.OrbitControls; const composerModule = await import('three/addons/postprocessing/EffectComposer.js'); EffectComposer = composerModule.EffectComposer; const renderPassModule = await import('three/addons/postprocessing/RenderPass.js'); RenderPass = renderPassModule.RenderPass; const bloomPassModule = await import('three/addons/postprocessing/UnrealBloomPass.js'); UnrealBloomPass = bloomPassModule.UnrealBloomPass; init(); } catch (err) { showError(err); } } function showError(err) { document.getElementById('loader').classList.add('hidden'); const errBox = document.getElementById('error-message'); const errDetail = document.getElementById('error-detail'); errDetail.innerHTML = ` Unable to load Three.js modules from registry.<br><br> <code style="color: rgba(255,210,224,0.6); font-size: 12px;">${err.message || err}</code><br><br> Please check your network connection and reload. `; errBox.classList.add('show'); } // 主初始化函数 function init() { document.getElementById('loader').classList.add('hidden'); // ===== 场景 ===== const scene = new THREE.Scene(); scene.fog = new THREE.FogExp2(0x050912, 0.025); // ===== 相机 ===== const camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(0, 2, 12); // ===== 渲染器 ===== const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: 'high-performance' }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.1; renderer.outputColorSpace = THREE.SRGBColorSpace; document.getElementById('canvas-container').appendChild(renderer.domElement); // ===== 后处理 ===== const composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 0.9, // strength 0.5, // radius 0.15 // threshold ); composer.addPass(bloomPass); // ===== 控制器 ===== const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.rotateSpeed = 0.7; controls.zoomSpeed = 0.8; controls.minDistance = 5; controls.maxDistance = 30; controls.autoRotate = false; controls.enablePan = false; // ===== 灯光 ===== const ambient = new THREE.AmbientLight(0x1a3a55, 0.6); scene.add(ambient); const keyLight = new THREE.PointLight(0x00e5ff, 60, 50, 1.5); keyLight.position.set(0, 0, 0); scene.add(keyLight); const rimLight = new THREE.PointLight(0xff2e8a, 30, 40, 1.5); rimLight.position.set(-8, 4, -6); scene.add(rimLight); const fillLight = new THREE.PointLight(0x66ffcc, 20, 30, 1.5); fillLight.position.set(8, -2, 4); scene.add(fillLight); const dirLight = new THREE.DirectionalLight(0xaaccff, 0.4); dirLight.position.set(5, 10, 5); scene.add(dirLight); // ===== 水晶核心组 ===== const coreGroup = new THREE.Group(); scene.add(coreGroup); // 外层水晶 const crystalGeometry = new THREE.IcosahedronGeometry(2.2, 1); // 让顶点位置稍微随机一点,增加天然感 const positions = crystalGeometry.attributes.position; for (let i = 0; i < positions.count; i++) { const offset = 0.08; positions.setX(i, positions.getX(i) + (Math.random() - 0.5) * offset); positions.setY(i, positions.getY(i) + (Math.random() - 0.5) * offset); positions.setZ(i, positions.getZ(i) + (Math.random() - 0.5) * offset); } crystalGeometry.computeVertexNormals(); const crystalMaterial = new THREE.MeshPhysicalMaterial({ color: 0x88ddff, metalness: 0.1, roughness: 0.05, transmission: 0.85, thickness: 1.5, ior: 2.4, clearcoat: 1.0, clearcoatRoughness: 0.0, envMapIntensity: 1.5, emissive: 0x004466, emissiveIntensity: 0.5, side: THREE.DoubleSide, flatShading: true, }); const crystal = new THREE.Mesh(crystalGeometry, crystalMaterial); coreGroup.add(crystal); // 第二层水晶,稍微小一些,旋转方向相反 const crystal2Geo = new THREE.OctahedronGeometry(1.6, 0); const crystal2Mat = new THREE.MeshPhysicalMaterial({ color: 0xaaeeff, metalness: 0.2, roughness: 0.1, transmission: 0.6, thickness: 0.8, ior: 1.8, emissive: 0x00aaff, emissiveIntensity: 0.6, transparent: true, opacity: 0.8, flatShading: true, }); const crystal2 = new THREE.Mesh(crystal2Geo, crystal2Mat); coreGroup.add(crystal2); // 内部发光核心 const innerCoreGeo = new THREE.IcosahedronGeometry(0.7, 2); const innerCoreMat = new THREE.MeshBasicMaterial({ color: 0x00ffff, transparent: true, opacity: 0.9, }); const innerCore = new THREE.Mesh(innerCoreGeo, innerCoreMat); coreGroup.add(innerCore); // 中心点光源(在核心内部) const coreLight = new THREE.PointLight(0x00ffff, 30, 8, 2); coreGroup.add(coreLight); // 核心光晕 sprite const glowTexture = createGlowTexture(); const glowMaterial = new THREE.SpriteMaterial({ map: glowTexture, color: 0x00ddff, blending: THREE.AdditiveBlending, transparent: true, depthWrite: false, }); const glowSprite = new THREE.Sprite(glowMaterial); glowSprite.scale.set(6, 6, 1); coreGroup.add(glowSprite); // 外层光晕 const outerGlowMat = new THREE.SpriteMaterial({ map: glowTexture, color: 0xff44aa, blending: THREE.AdditiveBlending, transparent: true, depthWrite: false, opacity: 0.4, }); const outerGlow = new THREE.Sprite(outerGlowMat); outerGlow.scale.set(12, 12, 1); coreGroup.add(outerGlow); // ===== 粒子系统 ===== const particleCount = 4000; const particleGeometry = new THREE.BufferGeometry(); const particlePositions = new Float32Array(particleCount * 3); const particleColors = new Float32Array(particleCount * 3); const particleSizes = new Float32Array(particleCount); const particleSpeeds = new Float32Array(particleCount); const particlePhases = new Float32Array(particleCount); const particleRadii = new Float32Array(particleCount); const cyan = new THREE.Color(0x00e5ff); const magenta = new THREE.Color(0xff2e8a); const green = new THREE.Color(0x66ffcc); const white = new THREE.Color(0xffffff); for (let i = 0; i < particleCount; i++) { // 球壳分布,但近核心处更密集 const radius = 3.5 + Math.pow(Math.random(), 0.6) * 9; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); particlePositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta); particlePositions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta); particlePositions[i * 3 + 2] = radius * Math.cos(phi); particleRadii[i] = radius; particlePhases[i] = Math.random() * Math.PI * 2; particleSpeeds[i] = 0.05 + Math.random() * 0.15; // 颜色:根据半径混合 const t = (radius - 3.5) / 9; let c; if (Math.random() < 0.15) { c = magenta.clone().lerp(white, Math.random() * 0.3); } else if (Math.random() < 0.4) { c = green.clone().lerp(cyan, Math.random()); } else { c = cyan.clone().lerp(white, Math.random() * 0.4); } // 离核心越近,越偏向白色 c.lerp(white, Math.max(0, 0.5 - t) * 0.5); particleColors[i * 3] = c.r; particleColors[i * 3 + 1] = c.g; particleColors[i * 3 + 2] = c.b; particleSizes[i] = 0.04 + Math.random() * 0.12; } particleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3)); particleGeometry.setAttribute('aColor', new THREE.BufferAttribute(particleColors, 3)); particleGeometry.setAttribute('aSize', new THREE.BufferAttribute(particleSizes, 1)); particleGeometry.setAttribute('aPhase', new THREE.BufferAttribute(particlePhases, 1)); particleGeometry.setAttribute('aRadius', new THREE.BufferAttribute(particleRadii, 1)); const particleMaterial = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uPixelRatio: { value: renderer.getPixelRatio() } }, vertexShader: ` attribute vec3 aColor; attribute float aSize; attribute float aPhase; attribute float aRadius; uniform float uTime; uniform float uPixelRatio; varying vec3 vColor; varying float vAlpha; void main() { vec3 pos = position; // 围绕 Y 轴旋转,速度根据半径调整 float speed = 0.15 + (1.0 / aRadius) * 0.5; float angle = uTime * speed + aPhase; float r = length(pos.xz); float baseAngle = atan(pos.z, pos.x) + angle; pos.x = r * cos(baseAngle); pos.z = r * sin(baseAngle); // 垂直方向小幅度浮动 pos.y += sin(uTime * 0.8 + aPhase * 2.0) * 0.2; vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); gl_Position = projectionMatrix * mvPosition; // 大小衰减 gl_PointSize = aSize * 300.0 * uPixelRatio / -mvPosition.z; // 闪烁 float twinkle = 0.6 + 0.4 * sin(uTime * 2.0 + aPhase * 5.0); vAlpha = twinkle; vColor = aColor; } `, fragmentShader: ` varying vec3 vColor; varying float vAlpha; void main() { vec2 uv = gl_PointCoord - 0.5; float dist = length(uv); if (dist > 0.5) discard; // 软圆形 + 中心更亮 float alpha = smoothstep(0.5, 0.0, dist); float core = pow(alpha, 4.0); vec3 finalColor = vColor + core * 0.6; gl_FragColor = vec4(finalColor, alpha * vAlpha); } `, blending: THREE.AdditiveBlending, depthWrite: false, transparent: true, }); const particles = new THREE.Points(particleGeometry, particleMaterial); scene.add(particles); // ===== 远景星点 ===== const starGeo = new THREE.BufferGeometry(); const starCount = 1500; const starPos = new Float32Array(starCount * 3); const starCol = new Float32Array(starCount * 3); for (let i = 0; i < starCount; i++) { const r = 40 + Math.random() * 30; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); starPos[i*3] = r * Math.sin(phi) * Math.cos(theta); starPos[i*3+1] = r * Math.sin(phi) * Math.sin(theta); starPos[i*3+2] = r * Math.cos(phi); const b = 0.4 + Math.random() * 0.6; starCol[i*3] = b * 0.8; starCol[i*3+1] = b * 0.9; starCol[i*3+2] = b; } starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3)); starGeo.setAttribute('color', new THREE.BufferAttribute(starCol, 3)); const starMat = new THREE.PointsMaterial({ size: 0.15, sizeAttenuation: true, vertexColors: true, transparent: true, opacity: 0.7, }); const stars = new THREE.Points(starGeo, starMat); scene.add(stars); // ===== 能量环(可选装饰)===== const ringGeo = new THREE.TorusGeometry(4.5, 0.02, 8, 128); const ringMat = new THREE.MeshBasicMaterial({ color: 0x00e5ff, transparent: true, opacity: 0.3, }); const ring1 = new THREE.Mesh(ringGeo, ringMat); ring1.rotation.x = Math.PI / 2; scene.add(ring1); const ring2 = new THREE.Mesh( new THREE.TorusGeometry(5.5, 0.015, 8, 128), new THREE.MeshBasicMaterial({ color: 0xff2e8a, transparent: true, opacity: 0.25 }) ); ring2.rotation.x = Math.PI / 2.5; ring2.rotation.z = Math.PI / 4; scene.add(ring2); // ===== 工具:生成发光纹理 ===== function createGlowTexture() { const size = 256; const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); const gradient = ctx.createRadialGradient( size/2, size/2, 0, size/2, size/2, size/2 ); gradient.addColorStop(0, 'rgba(255,255,255,1)'); gradient.addColorStop(0.15, 'rgba(180,240,255,0.85)'); gradient.addColorStop(0.4, 'rgba(80,180,255,0.4)'); gradient.addColorStop(0.7, 'rgba(40,80,180,0.1)'); gradient.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, size, size); const texture = new THREE.CanvasTexture(canvas); return texture; } // ===== 动画循环 ===== const clock = new THREE.Clock(); let fpsTime = 0; let fpsCount = 0; let fpsDisplay = 0; const fpsEl = document.getElementById('fps'); const particlesEl = document.getElementById('particles'); particlesEl.textContent = particleCount.toString().padStart(4, '0'); function animate() { requestAnimationFrame(animate); const dt = clock.getDelta(); const t = clock.getElapsedTime(); // 核心自转 coreGroup.rotation.y += dt * 0.15; crystal.rotation.x += dt * 0.08; crystal.rotation.z -= dt * 0.05; crystal2.rotation.y -= dt * 0.3; crystal2.rotation.x += dt * 0.2; innerCore.rotation.y += dt * 0.5; innerCore.rotation.x -= dt * 0.3; // 呼吸效果 const breath = 1 + Math.sin(t * 1.2) * 0.04; innerCore.scale.setScalar(breath); // 核心光强随时间变化 keyLight.intensity = 50 + Math.sin(t * 2) * 15; coreLight.intensity = 25 + Math.sin(t * 3) * 10; // 光晕呼吸 const glowScale = 5.5 + Math.sin(t * 1.5) * 0.5; glowSprite.scale.set(glowScale, glowScale, 1); outerGlow.scale.set(11 + Math.sin(t * 0.9 + 1) * 1, 11 + Math.sin(t * 0.9 + 1) * 1, 1); // 能量环旋转 ring1.rotation.z += dt * 0.1; ring2.rotation.x += dt * 0.07; ring2.rotation.y += dt * 0.05; // 粒子 particleMaterial.uniforms.uTime.value = t; // 远景星点轻微旋转 stars.rotation.y += dt * 0.005; controls.update(); composer.render(); // FPS fpsTime += dt; fpsCount++; if (fpsTime >= 0.5) { fpsDisplay = Math.round(fpsCount / fpsTime); fpsEl.textContent = fpsDisplay.toString().padStart(3, '0'); fpsTime = 0; fpsCount = 0; } } animate(); // ===== 窗口适配 ===== window.addEventListener('resize', () => { const w = window.innerWidth; const h = window.innerHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); composer.setSize(w, h); bloomPass.setSize(w, h); }); } loadModules(); </script> </body> </html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水晶核心 · Crystal Core</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
  :root {
    --bg-deep: #050912;
    --accent-cyan: #00e5ff;
    --accent-magenta: #ff2e8a;
    --accent-mint: #66ffcc;
    --fg: #e8f4ff;
    --fg-dim: rgba(232, 244, 255, 0.55);
  }
  
  * { box-sizing: border-box; margin: 0; padding: 0; }
  
  html, body {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background:
      radial-gradient(ellipse 80% 60% at 25% 25%, rgba(0, 90, 130, 0.28) 0%, transparent 55%),
      radial-gradient(ellipse 70% 70% at 75% 75%, rgba(120, 20, 70, 0.22) 0%, transparent 55%),
      radial-gradient(circle at center, #0a1428 0%, #050912 60%, #02040a 100%);
    font-family: 'Space Grotesk', system-ui, sans-serif;
    color: var(--fg);
  }
  
  #canvas-container {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
  }
  
  canvas {
    display: block;
    width: 100% !important;
    height: 100% !important;
  }
  
  .ui-overlay {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 10;
  }
  
  .brand {
    position: absolute;
    top: 32px;
    left: 36px;
    pointer-events: auto;
  }
  
  .brand .label {
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.45em;
    text-transform: uppercase;
    color: var(--accent-cyan);
    opacity: 0.7;
    margin-bottom: 8px;
  }
  
  .brand .title {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: 0.02em;
    color: #fff;
    line-height: 1;
  }
  
  .brand .title span {
    background: linear-gradient(135deg, var(--accent-cyan), var(--accent-mint));
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
  }
  
  .brand .subtitle {
    margin-top: 10px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.15em;
    color: var(--fg-dim);
  }
  
  .stats {
    position: absolute;
    top: 32px;
    right: 36px;
    text-align: right;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.1em;
    color: var(--fg-dim);
    pointer-events: auto;
  }
  
  .stats .row { margin-bottom: 4px; display: flex; justify-content: flex-end; gap: 8px; }
  .stats .key { opacity: 0.6; }
  .stats .value { color: var(--accent-cyan); font-weight: 500; min-width: 50px; text-align: right; }
  .stats .mag { color: var(--accent-magenta); }
  
  .info-panel {
    position: absolute;
    bottom: 32px;
    left: 36px;
    pointer-events: auto;
    font-size: 12px;
    color: var(--fg-dim);
    line-height: 2;
  }
  
  .info-panel .row { display: flex; gap: 14px; align-items: center; }
  .info-panel .k {
    display: inline-block;
    min-width: 56px;
    padding: 3px 10px;
    border: 1px solid rgba(0, 229, 255, 0.25);
    background: rgba(0, 229, 255, 0.04);
    border-radius: 4px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.15em;
    color: var(--accent-cyan);
    text-align: center;
  }
  
  .legend {
    position: absolute;
    bottom: 32px;
    right: 36px;
    pointer-events: auto;
    font-family: 'JetBrains Mono', monospace;
    font-size: 10px;
    letter-spacing: 0.15em;
    color: var(--fg-dim);
    text-align: right;
  }
  
  .legend .item { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-bottom: 6px; }
  .legend .dot { width: 8px; height: 8px; border-radius: 50%; box-shadow: 0 0 8px currentColor; }
  
  .vignette {
    position: fixed;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse at center, transparent 35%, rgba(0,0,0,0.55) 100%);
    z-index: 5;
  }
  
  .scanlines {
    position: fixed;
    inset: 0;
    pointer-events: none;
    background-image: repeating-linear-gradient(
      0deg,
      transparent 0,
      transparent 2px,
      rgba(0, 229, 255, 0.012) 2px,
      rgba(0, 229, 255, 0.012) 4px
    );
    z-index: 6;
    mix-blend-mode: screen;
  }
  
  /* 加载器 */
  .loader {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 50;
    text-align: center;
    color: var(--fg-dim);
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.4em;
    text-transform: uppercase;
  }
  .loader.hidden { display: none; }
  .loader .ring {
    width: 50px;
    height: 50px;
    border: 1px solid rgba(0, 229, 255, 0.15);
    border-top-color: var(--accent-cyan);
    border-radius: 50%;
    margin: 0 auto 18px;
    animation: spin 1s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }
  
  /* 错误提示 */
  #error-message {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: linear-gradient(135deg, rgba(30, 5, 20, 0.96), rgba(15, 8, 20, 0.96));
    border: 1px solid rgba(255, 46, 138, 0.45);
    box-shadow: 0 0 80px rgba(255, 46, 138, 0.25), inset 0 0 30px rgba(255, 46, 138, 0.05);
    color: #ffd0e0;
    padding: 36px 44px;
    border-radius: 10px;
    max-width: 560px;
    text-align: center;
    z-index: 100;
    display: none;
    backdrop-filter: blur(10px);
  }
  #error-message h2 {
    color: #ff5a9a;
    font-size: 16px;
    margin-bottom: 14px;
    letter-spacing: 0.25em;
    font-weight: 700;
  }
  #error-message p {
    font-size: 13px;
    line-height: 1.7;
    color: rgba(255, 210, 224, 0.85);
  }
  #error-message code {
    display: block;
    margin-top: 16px;
    padding: 10px 14px;
    background: rgba(255, 46, 138, 0.08);
    border: 1px solid rgba(255, 46, 138, 0.2);
    border-radius: 4px;
    color: rgba(255, 200, 220, 0.7);
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    word-break: break-all;
  }
  #error-message.show {
    display: block;
    animation: errIn 0.4s cubic-bezier(.2,.9,.3,1.2);
  }
  @keyframes errIn {
    from { opacity: 0; transform: translate(-50%, -45%) scale(0.92); }
    to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
  
  .corner {
    position: absolute;
    width: 28px;
    height: 28px;
    border: 1px solid rgba(0, 229, 255, 0.35);
    pointer-events: none;
    z-index: 8;
  }
  .corner.tl { top: 18px; left: 18px; border-right: none; border-bottom: none; }
  .corner.tr { top: 18px; right: 18px; border-left: none; border-bottom: none; }
  .corner.bl { bottom: 18px; left: 18px; border-right: none; border-top: none; }
  .corner.br { bottom: 18px; right: 18px; border-left: none; border-top: none; }
</style>
</head>
<body>
  <div id="canvas-container"></div>
  <div class="vignette"></div>
  <div class="scanlines"></div>
  
  <div class="corner tl"></div>
  <div class="corner tr"></div>
  <div class="corner bl"></div>
  <div class="corner br"></div>
  
  <div class="ui-overlay">
    <div class="brand">
      <div class="label">REACTANT // Σ-07</div>
      <div class="title">CRYSTAL <span>CORE</span></div>
      <div class="subtitle">— resonant lattice · stable</div>
    </div>
    
    <div class="stats">
      <div class="row"><span class="key">FPS</span><span class="value" id="fps">000</span></div>
      <div class="row"><span class="key">PARTICLES</span><span class="value" id="particles">0000</span></div>
      <div class="row"><span class="key">FLUX</span><span class="value mag" id="flux">0.00</span></div>
    </div>
    
    <div class="info-panel">
      <div class="row"><span class="k">DRAG</span><span>旋转视角</span></div>
      <div class="row"><span class="k">SCROLL</span><span>缩放距离</span></div>
    </div>
    
    <div class="legend">
      <div class="item"><span>CYAN · 0x00E5FF</span><span class="dot" style="background:#00e5ff;color:#00e5ff;"></span></div>
      <div class="item"><span>MAGENTA · 0xFF2E8A</span><span class="dot" style="background:#ff2e8a;color:#ff2e8a;"></span></div>
      <div class="item"><span>MINT · 0x66FFCC</span><span class="dot" style="background:#66ffcc;color:#66ffcc;"></span></div>
    </div>
  </div>
  
  <div class="loader" id="loader">
    <div class="ring"></div>
    <div>Initializing Reactant</div>
  </div>
  
  <div id="error-message">
    <h2>MODULE LOAD FAILURE</h2>
    <p id="error-text">无法加载 Three.js 模块。请检查网络连接后刷新页面重试。</p>
    <code id="error-detail"></code>
  </div>
  
  <script type="importmap">
  {
    "imports": {
      "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
      "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
    }
  }
  </script>
  
  <script type="module">
    // 用动态 import 捕获模块加载失败
    async function bootstrap() {
      let THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass, OutputPass;
      
      try {
        THREE = (await import('three')).default || await import('three');
        const THREE_NS = await import('three');
        // 兼容:模块可能是 namespace 对象
        const T = THREE_NS.default ? { ...THREE_NS.default, ...THREE_NS } : THREE_NS;
        
        const OrbitModule = await import('three/addons/controls/OrbitControls.js');
        const ComposerModule = await import('three/addons/postprocessing/EffectComposer.js');
        const RenderPassModule = await import('three/addons/postprocessing/RenderPass.js');
        const BloomModule = await import('three/addons/postprocessing/UnrealBloomPass.js');
        const OutputModule = await import('three/addons/postprocessing/OutputPass.js');
        
        startScene(
          T,
          OrbitModule.OrbitControls,
          ComposerModule.EffectComposer,
          RenderPassModule.RenderPass,
          BloomModule.UnrealBloomPass,
          OutputModule.OutputPass
        );
      } catch (err) {
        showError(err);
      }
    }
    
    function showError(err) {
      document.getElementById('loader').classList.add('hidden');
      const box = document.getElementById('error-message');
      const detail = document.getElementById('error-detail');
      const msg = err && err.message ? err.message : String(err);
      detail.textContent = msg.length > 200 ? msg.slice(0, 200) + '...' : msg;
      box.classList.add('show');
    }
    
    function startScene(THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass, OutputPass) {
      document.getElementById('loader').classList.add('hidden');
      
      const container = document.getElementById('canvas-container');
      
      // ===== 场景与雾 =====
      const scene = new THREE.Scene();
      scene.fog = new THREE.FogExp2(0x050912, 0.022);
      
      // ===== 相机 =====
      const camera = new THREE.PerspectiveCamera(
        52,
        window.innerWidth / window.innerHeight,
        0.1,
        500
      );
      camera.position.set(2.5, 2.0, 11.5);
      
      // ===== 渲染器 =====
      const renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        powerPreference: 'high-performance'
      });
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.toneMapping = THREE.ACESFilmicToneMapping;
      renderer.toneMappingExposure = 1.15;
      renderer.outputColorSpace = THREE.SRGBColorSpace;
      container.appendChild(renderer.domElement);
      
      // ===== 后处理 =====
      const composer = new EffectComposer(renderer);
      composer.addPass(new RenderPass(scene, camera));
      
      const bloomPass = new UnrealBloomPass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        0.95,   // strength
        0.55,   // radius
        0.12    // threshold
      );
      composer.addPass(bloomPass);
      composer.addPass(new OutputPass());
      
      // ===== 控制器 =====
      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.06;
      controls.rotateSpeed = 0.75;
      controls.zoomSpeed = 0.85;
      controls.minDistance = 4.5;
      controls.maxDistance = 28;
      controls.enablePan = false;
      controls.target.set(0, 0, 0);
      
      // ===== 灯光体系 =====
      scene.add(new THREE.AmbientLight(0x1a3a55, 0.45));
      
      const hemi = new THREE.HemisphereLight(0x4488ff, 0x110022, 0.3);
      scene.add(hemi);
      
      // 核心内部点光(青)
      const corePointLight = new THREE.PointLight(0x00e5ff, 80, 12, 1.6);
      corePointLight.position.set(0, 0, 0);
      scene.add(corePointLight);
      
      // 侧逆光(品红)
      const rimLight = new THREE.PointLight(0xff2e8a, 40, 30, 1.4);
      rimLight.position.set(-7, 3.5, -5);
      scene.add(rimLight);
      
      // 补光(薄荷)
      const fillLight = new THREE.PointLight(0x66ffcc, 25, 25, 1.4);
      fillLight.position.set(7, -2, 5);
      scene.add(fillLight);
      
      // 顶部方向光
      const dirLight = new THREE.DirectionalLight(0xaaccff, 0.35);
      dirLight.position.set(4, 9, 4);
      scene.add(dirLight);
      
      // ===== 工具:径向发光纹理 =====
      function makeGlowTexture(stops) {
        const size = 256;
        const canvas = document.createElement('canvas');
        canvas.width = size; canvas.height = size;
        const ctx = canvas.getContext('2d');
        const g = ctx.createRadialGradient(size/2, size/2, 0, size/2, size/2, size/2);
        stops.forEach(([offset, color]) => g.addColorStop(offset, color));
        ctx.fillStyle = g;
        ctx.fillRect(0, 0, size, size);
        const tex = new THREE.CanvasTexture(canvas);
        tex.colorSpace = THREE.SRGBColorSpace;
        return tex;
      }
      
      const glowTex = makeGlowTexture([
        [0.0, 'rgba(255,255,255,1)'],
        [0.15, 'rgba(180,240,255,0.85)'],
        [0.4, 'rgba(80,180,255,0.35)'],
        [0.75, 'rgba(40,80,180,0.08)'],
        [1.0, 'rgba(0,0,0,0)']
      ]);
      
      const softGlowTex = makeGlowTexture([
        [0.0, 'rgba(255,255,255,1)'],
        [0.5, 'rgba(255,200,230,0.4)'],
        [1.0, 'rgba(0,0,0,0)']
      ]);
      
      // ===== 水晶核心组 =====
      const coreGroup = new THREE.Group();
      scene.add(coreGroup);
      
      // 外层水晶(带顶点扰动)
      const outerGeo = new THREE.IcosahedronGeometry(2.25, 1);
      const oPos = outerGeo.attributes.position;
      for (let i = 0; i < oPos.count; i++) {
        oPos.setX(i, oPos.getX(i) + (Math.random() - 0.5) * 0.08);
        oPos.setY(i, oPos.getY(i) + (Math.random() - 0.5) * 0.08);
        oPos.setZ(i, oPos.getZ(i) + (Math.random() - 0.5) * 0.08);
      }
      outerGeo.computeVertexNormals();
      
      const outerCrystal = new THREE.Mesh(
        outerGeo,
        new THREE.MeshPhysicalMaterial({
          color: 0x88ddff,
          metalness: 0.15,
          roughness: 0.04,
          transmission: 0.88,
          thickness: 1.6,
          ior: 2.3,
          clearcoat: 1.0,
          clearcoatRoughness: 0.0,
          envMapIntensity: 1.4,
          emissive: 0x003a55,
          emissiveIntensity: 0.6,
          attenuationColor: new THREE.Color(0x00ccff),
          attenuationDistance: 3.0,
          side: THREE.DoubleSide,
          flatShading: true
        })
      );
      coreGroup.add(outerCrystal);
      
      // 中层水晶(八面体)
      const midCrystal = new THREE.Mesh(
        new THREE.OctahedronGeometry(1.55, 0),
        new THREE.MeshPhysicalMaterial({
          color: 0xaaeeff,
          metalness: 0.2,
          roughness: 0.08,
          transmission: 0.55,
          thickness: 0.7,
          ior: 1.7,
          emissive: 0x0099dd,
          emissiveIntensity: 0.9,
          clearcoat: 1.0,
          clearcoatRoughness: 0.05,
          transparent: true,
          opacity: 0.85,
          flatShading: true
        })
      );
      coreGroup.add(midCrystal);
      
      // 内核(强发光二十面体)
      const innerCore = new THREE.Mesh(
        new THREE.IcosahedronGeometry(0.7, 2),
        new THREE.MeshBasicMaterial({
          color: 0xeeffff,
          transparent: true,
          opacity: 0.95
        })
      );
      coreGroup.add(innerCore);
      
      // 内核外层光球
      const innerHalo = new THREE.Mesh(
        new THREE.SphereGeometry(0.95, 32, 32),
        new THREE.MeshBasicMaterial({
          color: 0x00e5ff,
          transparent: true,
          opacity: 0.18,
          side: THREE.BackSide,
          blending: THREE.AdditiveBlending,
          depthWrite: false
        })
      );
      coreGroup.add(innerHalo);
      
      // 核心外光晕(两层 sprite)
      const innerGlow = new THREE.Sprite(new THREE.SpriteMaterial({
        map: glowTex,
        color: 0x66e5ff,
        blending: THREE.AdditiveBlending,
        transparent: true,
        depthWrite: false
      }));
      innerGlow.scale.set(6.5, 6.5, 1);
      coreGroup.add(innerGlow);
      
      const outerHalo = new THREE.Sprite(new THREE.SpriteMaterial({
        map: softGlowTex,
        color: 0xff44aa,
        blending: THREE.AdditiveBlending,
        transparent: true,
        depthWrite: false,
        opacity: 0.45
      }));
      outerHalo.scale.set(13, 13, 1);
      coreGroup.add(outerHalo);
      
      // ===== 能量环 =====
      const ring1 = new THREE.Mesh(
        new THREE.TorusGeometry(4.6, 0.018, 8, 160),
        new THREE.MeshBasicMaterial({
          color: 0x00e5ff,
          transparent: true,
          opacity: 0.35,
          blending: THREE.AdditiveBlending
        })
      );
      ring1.rotation.x = Math.PI / 2;
      scene.add(ring1);
      
      const ring2 = new THREE.Mesh(
        new THREE.TorusGeometry(5.6, 0.012, 8, 160),
        new THREE.MeshBasicMaterial({
          color: 0xff2e8a,
          transparent: true,
          opacity: 0.3,
          blending: THREE.AdditiveBlending
        })
      );
      ring2.rotation.x = Math.PI / 2.3;
      ring2.rotation.z = Math.PI / 4;
      scene.add(ring2);
      
      const ring3 = new THREE.Mesh(
        new THREE.TorusGeometry(3.9, 0.008, 8, 160),
        new THREE.MeshBasicMaterial({
          color: 0x66ffcc,
          transparent: true,
          opacity: 0.4,
          blending: THREE.AdditiveBlending
        })
      );
      ring3.rotation.x = Math.PI / 1.7;
      ring3.rotation.y = Math.PI / 3;
      scene.add(ring3);
      
      // ===== 粒子系统(自定义 shader)=====
      const PARTICLE_COUNT = 4200;
      const particleGeo = new THREE.BufferGeometry();
      const positions = new Float32Array(PARTICLE_COUNT * 3);
      const colors = new Float32Array(PARTICLE_COUNT * 3);
      const sizes = new Float32Array(PARTICLE_COUNT);
      const phases = new Float32Array(PARTICLE_COUNT);
      const radii = new Float32Array(PARTICLE_COUNT);
      const yOffsets = new Float32Array(PARTICLE_COUNT);
      
      const cCyan = new THREE.Color(0x00e5ff);
      const cMag = new THREE.Color(0xff2e8a);
      const cMint = new THREE.Color(0x66ffcc);
      const cWhite = new THREE.Color(0xffffff);
      
      for (let i = 0; i < PARTICLE_COUNT; i++) {
        const radius = 3.6 + Math.pow(Math.random(), 0.55) * 8.5;
        const theta = Math.random() * Math.PI * 2;
        const phi = Math.acos(2 * Math.random() - 1);
        
        positions[i*3]   = radius * Math.sin(phi) * Math.cos(theta);
        positions[i*3+1] = radius * Math.sin(phi) * Math.sin(theta);
        positions[i*3+2] = radius * Math.cos(phi);
        
        radii[i] = radius;
        phases[i] = Math.random() * Math.PI * 2;
        yOffsets[i] = (Math.random() - 0.5) * 0.4;
        
        // 颜色:根据半径从核心白向三种主题色扩散
        const t = Math.min(1, (radius - 3.6) / 8.5);
        let c;
        const r = Math.random();
        if (r < 0.15) c = cMag.clone().lerp(cWhite, 0.3);
        else if (r < 0.35) c = cMint.clone().lerp(cCyan, Math.random() * 0.5);
        else c = cCyan.clone().lerp(cWhite, Math.random() * 0.5);
        // 近核心更亮
        c.lerp(cWhite, Math.max(0, 0.55 - t) * 0.7);
        
        colors[i*3]   = c.r;
        colors[i*3+1] = c.g;
        colors[i*3+2] = c.b;
        
        sizes[i] = 0.035 + Math.random() * 0.11;
      }
      
      particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      particleGeo.setAttribute('aColor', new THREE.BufferAttribute(colors, 3));
      particleGeo.setAttribute('aSize', new THREE.BufferAttribute(sizes, 1));
      particleGeo.setAttribute('aPhase', new THREE.BufferAttribute(phases, 1));
      particleGeo.setAttribute('aRadius', new THREE.BufferAttribute(radii, 1));
      particleGeo.setAttribute('aYOff', new THREE.BufferAttribute(yOffsets, 1));
      
      const particleMat = new THREE.ShaderMaterial({
        uniforms: {
          uTime: { value: 0 },
          uPixelRatio: { value: renderer.getPixelRatio() }
        },
        vertexShader: `
          attribute vec3 aColor;
          attribute float aSize;
          attribute float aPhase;
          attribute float aRadius;
          attribute float aYOff;
          
          uniform float uTime;
          uniform float uPixelRatio;
          
          varying vec3 vColor;
          varying float vAlpha;
          
          void main() {
            vec3 pos = position;
            
            // 绕 Y 轴旋转,半径越小越快
            float speed = 0.12 + (1.0 / max(aRadius, 1.0)) * 0.45;
            float ang = uTime * speed + aPhase;
            float r = length(pos.xz);
            float baseA = atan(pos.z, pos.x) + ang;
            pos.x = r * cos(baseA);
            pos.z = r * sin(baseA);
            
            // Y 方向浮动
            pos.y += sin(uTime * 0.9 + aPhase * 2.0) * 0.25 + aYOff * 0.3;
            
            vec4 mv = modelViewMatrix * vec4(pos, 1.0);
            gl_Position = projectionMatrix * mv;
            gl_PointSize = aSize * 320.0 * uPixelRatio / max(-mv.z, 0.1);
            
            // 闪烁
            float tw = 0.55 + 0.45 * sin(uTime * 2.3 + aPhase * 5.0);
            vAlpha = tw;
            vColor = aColor;
          }
        `,
        fragmentShader: `
          varying vec3 vColor;
          varying float vAlpha;
          
          void main() {
            vec2 uv = gl_PointCoord - 0.5;
            float dist = length(uv);
            if (dist > 0.5) discard;
            
            float soft = smoothstep(0.5, 0.0, dist);
            float core = pow(soft, 5.0);
            
            vec3 col = vColor + core * 0.8;
            float alpha = soft * vAlpha;
            
            gl_FragColor = vec4(col, alpha);
          }
        `,
        blending: THREE.AdditiveBlending,
        depthWrite: false,
        transparent: true
      });
      
      const particles = new THREE.Points(particleGeo, particleMat);
      scene.add(particles);
      
      // ===== 远景星点 =====
      const starGeo = new THREE.BufferGeometry();
      const STAR_COUNT = 1400;
      const sPos = new Float32Array(STAR_COUNT * 3);
      const sCol = new Float32Array(STAR_COUNT * 3);
      for (let i = 0; i < STAR_COUNT; i++) {
        const r = 35 + Math.random() * 35;
        const th = Math.random() * Math.PI * 2;
        const ph = Math.acos(2 * Math.random() - 1);
        sPos[i*3]   = r * Math.sin(ph) * Math.cos(th);
        sPos[i*3+1] = r * Math.sin(ph) * Math.sin(th);
        sPos[i*3+2] = r * Math.cos(ph);
        const b = 0.4 + Math.random() * 0.6;
        sCol[i*3]   = b * 0.85;
        sCol[i*3+1] = b * 0.92;
        sCol[i*3+2] = b;
      }
      starGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3));
      starGeo.setAttribute('color', new THREE.BufferAttribute(sCol, 3));
      const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({
        size: 0.13,
        sizeAttenuation: true,
        vertexColors: true,
        transparent: true,
        opacity: 0.75,
        depthWrite: false
      }));
      scene.add(stars);
      
      // ===== 动画循环 =====
      const clock = new THREE.Clock();
      let fpsAcc = 0, fpsCount = 0;
      const fpsEl = document.getElementById('fps');
      const fluxEl = document.getElementById('flux');
      document.getElementById('particles').textContent = PARTICLE_COUNT.toString().padStart(4, '0');
      
      function animate() {
        requestAnimationFrame(animate);
        const dt = Math.min(clock.getDelta(), 0.05);
        const t = clock.getElapsedTime();
        
        // 核心自转(缓慢)
        coreGroup.rotation.y += dt * 0.16;
        outerCrystal.rotation.x += dt * 0.09;
        outerCrystal.rotation.z -= dt * 0.06;
        midCrystal.rotation.y -= dt * 0.32;
        midCrystal.rotation.x += dt * 0.22;
        innerCore.rotation.y += dt * 0.55;
        innerCore.rotation.x -= dt * 0.35;
        innerCore.rotation.z += dt * 0.18;
        
        // 呼吸效果
        const breath = 1 + Math.sin(t * 1.3) * 0.045;
        innerCore.scale.setScalar(breath);
        innerHalo.scale.setScalar(1 + Math.sin(t * 1.0) * 0.08);
        
        // 光强动态
        corePointLight.intensity = 75 + Math.sin(t * 2.1) * 18;
        rimLight.intensity = 38 + Math.sin(t * 1.4 + 1) * 10;
        fillLight.intensity = 22 + Math.sin(t * 1.7 + 2) * 8;
        
        // 光晕呼吸
        const g1 = 6.2 + Math.sin(t * 1.4) * 0.6;
        innerGlow.scale.set(g1, g1, 1);
        const g2 = 12.5 + Math.sin(t * 0.85 + 0.5) * 1.2;
        outerHalo.scale.set(g2, g2, 1);
        
        // 能量环旋转
        ring1.rotation.z += dt * 0.10;
        ring2.rotation.x += dt * 0.07;
        ring2.rotation.y += dt * 0.05;
        ring3.rotation.z -= dt * 0.13;
        ring3.rotation.y += dt * 0.04;
        
        // 粒子时间
        particleMat.uniforms.uTime.value = t;
        
        // 星点慢转
        stars.rotation.y += dt * 0.006;
        stars.rotation.x += dt * 0.002;
        
        controls.update();
        composer.render();
        
        // FPS / Flux
        fpsAcc += dt;
        fpsCount++;
        if (fpsAcc >= 0.4) {
          fpsEl.textContent = Math.round(fpsCount / fpsAcc).toString().padStart(3, '0');
          const flux = (Math.sin(t * 0.7) * 0.5 + 0.5) * 9.99;
          fluxEl.textContent = flux.toFixed(2);
          fpsAcc = 0;
          fpsCount = 0;
        }
      }
      animate();
      
      // ===== 窗口自适应 =====
      window.addEventListener('resize', () => {
        const w = window.innerWidth;
        const h = window.innerHeight;
        camera.aspect = w / h;
        camera.updateProjectionMatrix();
        renderer.setSize(w, h);
        composer.setSize(w, h);
        bloomPass.setSize(w, h);
        particleMat.uniforms.uPixelRatio.value = renderer.getPixelRatio();
      });
      
      // 鼠标视差轻微影响(可选)
      let mouseX = 0, mouseY = 0;
      window.addEventListener('pointermove', (e) => {
        mouseX = (e.clientX / window.innerWidth - 0.5) * 0.4;
        mouseY = (e.clientY / window.innerHeight - 0.5) * 0.4;
      });
    }
    
    bootstrap();
  </script>
</body>
</html>

我把代码再核对一下关键点:

  1. importmap 正确映射 three 和 three/addons/ ✓
  2. 用动态 import + try/catch 捕获模块加载失败 ✓
  3. 失败时显示带样式的错误提示框,包含原始错误信息 ✓
  4. 水晶核心由多层组成:外层二十面体(物理材质 + transmission)、中层八面体、内核发光球、光晕 sprite ✓
  5. 4200 个粒子用自定义 shader 绘制为发光圆形(不是方块),绕核心旋转 ✓
  6. 多光源(环境光、半球光、3 个点光、方向光) ✓
  7. UnrealBloomPass 让发光元素强烈发光 ✓
  8. OrbitControls 含 damping、距离限制、禁用 pan ✓
  9. resize 自适应 ✓
  10. canvas 铺满视口 ✓

视觉上:青/品红/薄荷绿三色配色(非默认蓝紫),多层信息 HUD(标题、统计、操作提示、图例),暗角、扫描线、四角装饰框,让整个界面像一个反应堆监控终端。

让我也加一些细节让画面更生动:

  • 鼠标视差影响(轻微)—— 我加了一个变量但没有实际应用,应该删掉或者应用

让我应用一下:相机可以有轻微视差。但是 OrbitControls 也在控制相机,可能会冲突。简单的方式是让 outerHalo 或者 stars 跟随鼠标轻微移动。我让 stars 跟随鼠标偏移。

实际上为了简洁,我把未使用的鼠标代码删掉,保持代码精炼。

确认最终输出。

5.17s
首Token
67
思考TPS
121s · 8,148 tok
82
输出TPS
104s · 8,577 tok
16,725
总Tokens(官方)
输入 182
总用时 233s · 平均 74 tok/s · 峰值 326 tok/s · 成本 $0.074
MiniMax-M3#5
MiniMax-M3
完成
The user wants a single HTML file that: 1. Uses importmap in `<head>` to map `three` and `three/addons/` to specific npmmirror URLs 2. Uses `<script type="module">` to build a runnable 3D scene 3. Scene should include: - Slowly rotating glowing crystal core - Orbiting particles - Reasonable lighting and materials - OrbitControls for mouse drag rotation and scroll zoom - Canvas filling the viewport, responsive to window resize 4. Should display clear error text if script fails to load Let me write this complete HTML file. I'll create: - A crystal-like core using IcosahedronGeometry with MeshPhysicalMaterial for a glassy/glowing look - Particles orbiting around the crystal - Proper lighting (ambient, point lights, directional) - OrbitControls for interaction - Error handling with try/catch and displaying error on page Let me structure this carefully: ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>发光水晶核心</title> <style> html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; background: #000; font-family: sans-serif; } canvas { display: block; } #error { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #ff5555; background: rgba(0,0,0,0.8); padding: 20px; border: 1px solid #ff5555; border-radius: 8px; font-size: 16px; text-align: center; max-width: 80%; display: none; z-index: 9999; } </style> <script type="importmap"> { "imports": { "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js", "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/" } } </script> <script type="module"> try { import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // ... scene setup } catch (e) { const err = document.getElementById('error'); err.textContent = '脚本加载失败: ' + e.message; err.style.display = 'block'; } </script> </head> <body> <div id="error"></div> <script> // Inline error handler for module load failures window.addEventListener('error', function(e) { const err = document.getElementById('error'); if (err) { err.textContent = '加载错误: ' + (e.message || '未知错误'); err.style.display = 'block'; } }); </script> </body> </html> ``` Wait, the module script needs to be after the importmap. Let me reconsider the structure. Also, for module loading errors, the error event might not catch them all. I should add a global error handler. Let me also make sure the error display is shown when modules fail to load. Module load failures can be tricky - they often throw errors that don't trigger window.error reliably. I can use the `unhandledrejection` event and also check inside the module script. Let me write the full thing now: For the crystal core, I'll use IcosahedronGeometry with detail level 1 or 0 for a faceted look. I'll use MeshPhysicalMaterial with: - transmission for glass-like transparency - thickness - roughness low - ior around 1.5-2.0 for crystal - emissive color for glow - clearcoat For particles, I'll create a Points object with circular orbit positions using BufferGeometry. For lighting: - AmbientLight low intensity - PointLight(s) with the crystal color (e.g., cyan/purple) - Maybe a directional light for definition For the glow effect, I could use: - UnrealBloomPass (post-processing) - Or just rely on emissive material Let me add bloom for a really nice glow effect. I'll need EffectComposer, RenderPass, UnrealBloomPass. Actually, to keep it simple and ensure it works well, let me include bloom post-processing. Let me write the full code now: ```javascript import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; // Scene setup const scene = new THREE.Scene(); scene.fog = new THREE.FogExp2(0x000010, 0.05); const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 2, 8); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0; document.body.appendChild(renderer.domElement); // Controls const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.minDistance = 3; controls.maxDistance = 30; controls.autoRotate = false; // Lighting const ambient = new THREE.AmbientLight(0x202040, 0.5); scene.add(ambient); const keyLight = new THREE.PointLight(0x66ccff, 3, 50); keyLight.position.set(5, 5, 5); scene.add(keyLight); const fillLight = new THREE.PointLight(0xff66cc, 2, 50); fillLight.position.set(-5, -3, -5); scene.add(fillLight); const rimLight = new THREE.DirectionalLight(0xffffff, 0.5); rimLight.position.set(0, 10, 0); scene.add(rimLight); // Crystal core const crystalGeometry = new THREE.IcosahedronGeometry(1.5, 0); const crystalMaterial = new THREE.MeshPhysicalMaterial({ color: 0x88ccff, emissive: 0x4488ff, emissiveIntensity: 0.6, metalness: 0.1, roughness: 0.05, transmission: 0.9, thickness: 1.5, ior: 1.8, clearcoat: 1.0, clearcoatRoughness: 0.1, transparent: true, opacity: 0.9 }); const crystal = new THREE.Mesh(crystalGeometry, crystalMaterial); scene.add(crystal); // Inner glow sphere const innerGlowGeometry = new THREE.IcosahedronGeometry(0.8, 2); const innerGlowMaterial = new THREE.MeshBasicMaterial({ color: 0xaaeeff, transparent: true, opacity: 0.4 }); const innerGlow = new THREE.Mesh(innerGlowGeometry, innerGlowMaterial); scene.add(innerGlow); // Particles const particleCount = 2000; const particleGeometry = new THREE.BufferGeometry(); const positions = new Float32Array(particleCount * 3); const colors = new Float32Array(particleCount * 3); const sizes = new Float32Array(particleCount); const orbitData = []; // store orbit parameters per particle const colorPalette = [ new THREE.Color(0x66ccff), new THREE.Color(0xff66cc), new THREE.Color(0xccff66), new THREE.Color(0xffcc66) ]; for (let i = 0; i < particleCount; i++) { const radius = 3 + Math.random() * 6; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const speed = (0.2 + Math.random() * 0.5) * (Math.random() < 0.5 ? 1 : -1); const axisTilt = Math.random() * Math.PI; orbitData.push({ radius, theta, phi, speed, axisTilt, baseTheta: theta, basePhi: phi }); positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta); positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta); positions[i * 3 + 2] = radius * Math.cos(phi); const c = colorPalette[Math.floor(Math.random() * colorPalette.length)]; colors[i * 3] = c.r; colors[i * 3 + 1] = c.g; colors[i * 3 + 2] = c.b; sizes[i] = 0.05 + Math.random() * 0.15; } particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); // Custom point texture (simple radial gradient) const canvas = document.createElement('canvas'); canvas.width = 64; canvas.height = 64; const ctx = canvas.getContext('2d'); const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32); gradient.addColorStop(0, 'rgba(255,255,255,1)'); gradient.addColorStop(0.4, 'rgba(255,255,255,0.5)'); gradient.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, 64, 64); const pointTexture = new THREE.CanvasTexture(canvas); const particleMaterial = new THREE.PointsMaterial({ size: 0.15, vertexColors: true, map: pointTexture, transparent:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发光水晶核心</title>
<style>
  html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; background: #000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #fff; }
  canvas { display: block; }
  #error {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
    color: #ffb3b3; background: rgba(25, 0, 0, 0.94); padding: 24px 30px;
    border: 1px solid #ff6b6b; border-radius: 10px;
    font-size: 14px; text-align: center; max-width: 80%; line-height: 1.6;
    display: none; z-index: 9999;
    box-shadow: 0 0 40px rgba(255, 107, 107, 0.35);
  }
  #error b { color: #fff; display: block; margin-bottom: 10px; font-size: 17px; }
  #hint {
    position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
    color: rgba(255,255,255,0.5); font-size: 12px; pointer-events: none;
    z-index: 10; user-select: none; letter-spacing: 1.5px;
  }
</style>
<script type="importmap">
{
  "imports": {
    "three": "https://registry.npmmirror.com/three/0.171.0/files/build/three.module.js",
    "three/addons/": "https://registry.npmmirror.com/three/0.171.0/files/examples/jsm/"
  }
}
</script>
</head>
<body>
<div id="error"><b>⚠ 场景加载失败</b><span id="errorMsg"></span></div>
<div id="hint">鼠标拖拽旋转 · 滚轮缩放</div>
<script>
  function showError(msg) {
    var el = document.getElementById('error');
    var m  = document.getElementById('errorMsg');
    if (el && m) { m.textContent = msg; el.style.display = 'block'; }
  }
  window.addEventListener('error', function (e) {
    showError((e && e.message) || '未知脚本错误');
  });
  window.addEventListener('unhandledrejection', function (e) {
    var r = e && e.reason;
    showError((r && (r.message || String(r))) || '模块加载失败');
  });
</script>
<script type="module">
try {
  import * as THREE from 'three';
  import { OrbitControls }    from 'three/addons/controls/OrbitControls.js';
  import { EffectComposer }   from 'three/addons/postprocessing/EffectComposer.js';
  import { RenderPass }       from 'three/addons/postprocessing/RenderPass.js';
  import { UnrealBloomPass }  from 'three/addons/postprocessing/UnrealBloomPass.js';

  /* ============ Scene ============ */
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x02030a);
  scene.fog        = new THREE.FogExp2(0x02030a, 0.035);

  const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 200);
  camera.position.set(0, 2, 9);

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.toneMapping         = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.0;
  document.body.appendChild(renderer.domElement);

  /* ============ Controls ============ */
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping  = true;
  controls.dampingFactor  = 0.06;
  controls.minDistance    = 4;
  controls.maxDistance    = 25;
  controls.target.set(0, 0, 0);

  /* ============ Lighting ============ */
  scene.add(new THREE.AmbientLight(0x223044, 0.6));

  const lightA = new THREE.PointLight(0x66ccff, 4, 40, 1.2);
  lightA.position.set( 6,  5,  6); scene.add(lightA);

  const lightB = new THREE.PointLight(0xff77cc, 3, 40, 1.2);
  lightB.position.set(-6, -3, -4); scene.add(lightB);

  const lightC = new THREE.PointLight(0xffeeaa, 2, 30, 1.2);
  lightC.position.set( 0, -7,  3); scene.add(lightC);

  const dirLight = new THREE.DirectionalLight(0xffffff, 0.4);
  dirLight.position.set(5, 10, 7);
  scene.add(dirLight);

  /* ============ Crystal Core ============ */
  const crystalGroup = new THREE.Group();
  scene.add(crystalGroup);

  const crystalGeo = new THREE.IcosahedronGeometry(1.6, 0);
  const crystalMat = new THREE.MeshPhysicalMaterial({
    color:        0x9ad8ff,
    emissive:     0x4488ff,
    emissiveIntensity: 0.8,
    metalness:    0.0,
    roughness:    0.05,
    transmission: 0.85,
    thickness:    2.0,
    ior:          1.7,
    clearcoat:    1.0,
    clearcoatRoughness: 0.05,
    transparent:  true,
    opacity:      0.95,
    side:         THREE.DoubleSide
  });
  const crystal = new THREE.Mesh(crystalGeo, crystalMat);
  crystalGroup.add(crystal);

  /* Highlight edges only */
  const edges  = new THREE.EdgesGeometry(crystalGeo);
  const edgeMat = new THREE.LineBasicMaterial({
    color: 0xaaeeff, transparent: true, opacity: 0.55
  });
  const edgeLines = new THREE.LineSegments(edges, edgeMat);
  crystalGroup.add(edgeLines);

  /* Inner pulsing glow */
  const glowGeo = new THREE.IcosahedronGeometry(0.7, 3);
  const glowMat = new THREE.MeshBasicMaterial({
    color: 0xbbecff, transparent: true, opacity: 0.55
  });
  const glowCore = new THREE.Mesh(glowGeo, glowMat);
  crystalGroup.add(glowCore);

  /* ============ Orbiting Particles ============ */
  const PARTICLE_COUNT = 1800;
  const particleGeo = new THREE.BufferGeometry();
  const posArr      = new Float32Array(PARTICLE_COUNT * 3);
  const colArr      = new Float32Array(PARTICLE_COUNT * 3);
  const orbitInfo   = [];

  const palette = [
    new THREE.Color(0x66ccff),
    new THREE.Color(0xff77dd),
    new THREE.Color(0x99ffcc),
    new THREE.Color(0xffcc77),
    new THREE.Color(0xbb88ff)
  ];

  for (let i = 0; i < PARTICLE_COUNT; i++) {
    const r     = 3.2 + Math.random() * 6.5;
    const theta = Math.random() * Math.PI * 2;
    const phi   = Math.acos(2 * Math.random() - 1);
    const speed = (0.15 + Math.random() * 0.5) * (Math.random() < 0.5 ? 1 : -1);
    const tilt  = (Math.random() - 0.5) * Math.PI * 0.6;
    const wob   = 0.2 + Math.random() * 0.5;
    orbitInfo.push({ r, theta, phi, speed, tilt, wob, phase: Math.random() * Math.PI * 2 });

    posArr[i*3]     = r * Math.sin(phi) * Math.cos(theta);
    posArr[i*3 + 1] = r * Math.sin(phi) * Math.sin(theta);
    posArr[i*3 + 2] = r * Math.cos(phi);

    const c = palette[Math.floor(Math.random() * palette.length)];
    colArr[i*3]     = c.r;
    colArr[i*3 + 1] = c.g;
    colArr[i*3 + 2] = c.b;
  }
  particleGeo.setAttribute('position', new THREE.BufferAttribute(posArr, 3));
  particleGeo.setAttribute('color',    new THREE.BufferAttribute(colArr, 3));

  /* Soft circular sprite */
  const cvs = document.createElement('canvas');
  cvs.width = cvs.height = 64;
  const cx = cvs.getContext('2d');
  const grad = cx.createRadialGradient(32, 32, 0, 32, 32, 32);
  grad.addColorStop(0,   'rgba(255,255,255,1)');
  grad.addColorStop(0.3, 'rgba(255,255,255,0.6)');
  grad.addColorStop(1,   'rgba(255,255,255,0)');
  cx.fillStyle = grad;
  cx.fillRect(0, 0, 64, 64);
  const sprite = new THREE.CanvasTexture(cvs);

  const particleMat = new THREE.PointsMaterial({
    size: 0.18,
    map: sprite,
    vertexColors: true,
    transparent:  true,
    blending:     THREE.AdditiveBlending,
    depthWrite:   false,
    sizeAttenuation: true
  });
  const particles = new THREE.Points(particleGeo, particleMat);
  scene.add(particles);

  /* ============ Post-Processing (Bloom) ============ */
  const composer = new EffectComposer(renderer);
  composer.addPass(new RenderPass(scene, camera));
  const bloom = new UnrealBloomPass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.1,  /* strength */
    0.7,  /* radius   */
    0.15  /* threshold*/
  );
  composer.addPass(bloom);

  /* ============ Resize ============ */
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    composer.setSize(window.innerWidth, window.innerHeight);
  });

  /* ============ Animation ============ */
  const clock = new THREE.Clock();
  const posAttr = particleGeo.attributes.position;

  function animate() {
    requestAnimationFrame(animate);
    const t = clock.getElapsedTime();

    /* Crystal slow self-rotation */
    crystal.rotation.y = t * 0.25;
    crystal.rotation.x = Math.sin(t * 0.15) * 0.3;

    edgeLines.rotation.y = -t * 0.18;
    edgeLines.rotation.z =  t * 0.08;

    glowCore.rotation.y = -t * 0.5;
    glowCore.rotation.x =  t * 0.3;
    glowMat.opacity     = 0.4 + Math.sin(t * 1.8) * 0.15;
    crystalMat.emissiveIntensity = 0.7 + Math.sin(t * 1.2) * 0.2;

    /* Particles orbital motion */
    const arr = posAttr.array;
    for (let i = 0; i < PARTICLE_COUNT; i++) {
      const d  = orbitInfo[i];
      const ang = d.theta + t * d.speed;
      const ph  = d.phi + Math.sin(t * d.wob + d.phase) * 0.08;
      const y   = d.r * Math.cos(ph);
      const ring= d.r * Math.sin(ph);
      arr[i*3]     = ring * Math.cos(ang);
      arr[i*3 + 1] = y + Math.sin(t * 0.5 + d.phase) * d.tilt * 0.3;
      arr[i*3 + 2] = ring * Math.sin(ang);
    }
    posAttr.needsUpdate = true;

    particles.rotation.y = t * 0.03;

    controls.update();
    composer.render();
  }
  animate();
} catch (e) {
  showError(e && e.message ? e.message : String(e));
}
</script>
</body>
</html>
1.22s
首Token
85
思考TPS
83.5s · 7,128 tok
137
输出TPS
21.0s · 2,885 tok
10,013
总Tokens(官方)
输入 329
总用时 106s · 平均 96 tok/s · 峰值 395 tok/s · 成本 $0.012