1. Three.js 环境配置与开发工具

1.1. 环境配置方案对比

1.1.1. 配置方案概览

配置方案概览

1.1.2. 各方案详细配置

1.1.2.1. 方案一:CDN快速开始(推荐初学者)

html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Three.js CDN入门</title>
    <style>
        body { margin: 0; overflow: hidden; }
        #info {
            position: absolute;
            top: 10px;
            width: 100%;
            text-align: center;
            color: white;
            font-family: Arial, sans-serif;
        }
    </style>
</head>
<body>
    <!-- Three.js CDN -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.135.0/build/three.min.js"></script>
    <!-- 常用控制器 -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/controls/OrbitControls.js"></script>
    <!-- 性能监控 -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/libs/stats.min.js"></script>
    <!-- GUI调试 -->
    <script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/libs/dat.gui.min.js"></script>

    <div id="info">Three.js CDN示例 - FPS: <span id="fps">0</span></div>

    <script>
        // 基础Three.js代码
        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 });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        document.body.appendChild(renderer.domElement);

        // 添加立方体
        const geometry = new THREE.BoxGeometry(1, 1, 1);
        const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
        const cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
        camera.position.z = 5;

        // 性能监控
        const stats = new Stats();
        stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
        document.body.appendChild(stats.dom);

        // 动画循环
        function animate() {
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        }
        animate();

        // 窗口大小调整
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
    </script>
</body>
</html>

1.1.2.2. 方案二:NPM + Vite(现代项目推荐)

bash

# 1. 创建项目
npm create vite@latest threejs-project -- --template vanilla
cd threejs-project

# 2. 安装Three.js及相关依赖
npm install three
npm install -D @types/three vite-plugin-three

# 3. 安装常用扩展
npm install @react-three/fiber @react-three/drei  # React集成
npm install three-stdlib                          # 官方示例工具
npm install cannon-es                            # 物理引擎
npm install gsap                                 # 动画库

vite.config.js 配置:

javascript

import { defineConfig } from 'vite';
import three from 'vite-plugin-three';

export default defineConfig({
  plugins: [three()],
  build: {
    target: 'esnext',  // 支持现代ES特性
    sourcemap: true,   // 便于调试
    rollupOptions: {
      output: {
        manualChunks: {
          'three': ['three'],  // 单独打包Three.js
          'vendor': ['cannon-es', 'gsap']  // 第三方库
        }
      }
    }
  },
  server: {
    host: true,  // 允许外部访问
    port: 3000,
    open: true   // 自动打开浏览器
  }
});

package.json 脚本配置:

json

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "analyze": "vite-bundle-analyzer",
    "lint": "eslint src --ext .js,.jsx,.ts,.tsx",
    "format": "prettier --write src/**/*.{js,jsx,ts,tsx,json,css,md}"
  },
  "dependencies": {
    "three": "^0.135.0",
    "three-stdlib": "^2.8.5",
    "cannon-es": "^0.19.0",
    "gsap": "^3.10.4"
  },
  "devDependencies": {
    "@types/three": "^0.135.0",
    "vite": "^3.0.0",
    "vite-plugin-three": "^0.2.2",
    "@vitejs/plugin-legacy": "^2.0.0",
    "eslint": "^8.0.0",
    "prettier": "^2.0.0",
    "vite-bundle-analyzer": "^0.6.0"
  }
}

主入口文件 main.js:

javascript

import * as THREE from 'three';
// 初始化场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);

// 相机
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
camera.position.set(0, 2, 5);

// 渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

document.body.appendChild(renderer.domElement);

1.1.2.3. 方案三:TypeScript配置

tsconfig.json:

json

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "types": ["three", "vite/client"],
    "allowSyntheticDefaultImports": true
  },
  "include": ["src"]
}

1.1.2.4. 方案四:React集成(React Three Fiber)

bash

# 创建React + Three.js项目
npx create-react-app threejs-react-app --template typescript
cd threejs-react-app
npm install @react-three/fiber @react-three/drei drei-stats
npm install three @types/three

App.tsx:
tsx

import React, { useRef, useState } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { OrbitControls, Stats, Environment, Box } from '@react-three/drei';
import * as THREE from 'three';

function RotatingBox() {
  const meshRef = useRef<THREE.Mesh>(null);

  useFrame((state) => {
    if (meshRef.current) {
      meshRef.current.rotation.x = state.clock.elapsedTime;
      meshRef.current.rotation.y = state.clock.elapsedTime * 0.5;
    }
  });

  return (
    <Box ref={meshRef} args={[1, 1, 1]}>
      <meshStandardMaterial color="hotpink" />
    </Box>
  );
}

function Scene() {
  const { camera } = useThree();

  return (
    <>
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 10, 7]} intensity={1} castShadow />
      <RotatingBox />
      <OrbitControls camera={camera} />
      <Environment preset="city" />
      <Stats />
    </>
  );
}

function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Canvas
        shadows
        camera={{ position: [0, 2, 5], fov: 75 }}
        gl={{
          antialias: true,
          alpha: true,
          powerPreference: 'high-performance'
        }}
      >
        <Scene />
      </Canvas>
    </div>
  );
}

export default App;

1.2. 二、开发工具详解

1.2.1. 代码编辑器选择

1.2.1.1. VS Code 强烈推荐

推荐配置

.vscode/settings.json:

json

{
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "files.associations": {
    "*.glsl": "glsl",
    "*.vert": "glsl",
    "*.frag": "glsl",
    "*.vs": "glsl",
    "*.fs": "glsl"
  },
  "emmet.includeLanguages": {
    "javascript": "javascriptreact"
  },
  "typescript.preferences.importModuleSpecifier": "relative",
  "javascript.preferences.importModuleSpecifier": "relative",
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

.vscode/extensions.json:

json

{
  "recommendations": [
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "ms-vscode.vscode-typescript-next",
    "slevesque.shader",
    "ritwickdey.liveserver",
    "threejs.threejs-snippets",
    "ms-vscode.live-server",
    "zixuanchen.vitest-explorer"
  ]
}

1.2.1.2. WebStorm(企业级推荐)

text

优势:
- 强大的Three.js代码补全
- 内置GLSL支持
- 性能分析工具集成
- 调试WebGL上下文

配置建议:
1. 启用Node.js Core library
2. 配置GLSL文件关联
3. 安装Three.js插件
4. 配置WebGL调试器

1.2.1.3. Three.js 官方编辑器(原型设计)

text

在线地址:https://threejs.org/editor/

功能特点:
✓ 可视化场景编辑
✓ 实时材质调整
✓ 模型导入导出
✓ 代码生成导出

使用场景:
- 快速原型设计
- 材质和光照测试
- 场景布局预览

1.2.2. 3D模型和资源工具

1.2.2.1. 资源管理工具类

javascript

class AssetManager {
  constructor() {
    this.textures = new Map();
    this.models = new Map();
    this.audio = new Map();
    this.fonts = new Map();
    this.loadingManager = new THREE.LoadingManager();

    this.setupLoadingManager();
    this.initLoaders();
  }

  setupLoadingManager() {
    this.loadingManager.onStart = (url, itemsLoaded, itemsTotal) => {
      console.log(`开始加载: ${url}, 进度: ${itemsLoaded}/${itemsTotal}`);
      this.showLoadingScreen();
    };

    this.loadingManager.onLoad = () => {
      console.log('所有资源加载完成');
      this.hideLoadingScreen();
    };

    this.loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
      const progress = (itemsLoaded / itemsTotal) * 100;
      console.log(`加载中: ${url}, 进度: ${progress.toFixed(2)}%`);
      this.updateProgressBar(progress);
    };

    this.loadingManager.onError = (url) => {
      console.error(`加载失败: ${url}`);
    };
  }

  initLoaders() {
    this.textureLoader = new THREE.TextureLoader(this.loadingManager);
    this.gltfLoader = new GLTFLoader(this.loadingManager);
    this.fontLoader = new FontLoader(this.loadingManager);
    this.audioLoader = new THREE.AudioLoader(this.loadingManager);

    // 配置DRACO压缩
    const dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.5/');
    dracoLoader.setDecoderConfig({ type: 'js' });
    this.gltfLoader.setDRACOLoader(dracoLoader);
  }

  async loadTexture(key, url) {
    return new Promise((resolve, reject) => {
      if (this.textures.has(key)) {
        resolve(this.textures.get(key));
        return;
      }

      this.textureLoader.load(
        url,
        (texture) => {
          texture.encoding = THREE.sRGBEncoding;
          this.textures.set(key, texture);
          resolve(texture);
        },
        undefined,
        (error) => reject(error)
      );
    });
  }

  async loadModel(key, url) {
    return new Promise((resolve, reject) => {
      if (this.models.has(key)) {
        resolve(this.models.get(key));
        return;
      }

      this.gltfLoader.load(
        url,
        (gltf) => {
          this.models.set(key, gltf);
          resolve(gltf);
        },
        undefined,
        (error) => reject(error)
      );
    });
  }

  async loadFont(key, url) {
    return new Promise((resolve, reject) => {
      if (this.fonts.has(key)) {
        resolve(this.fonts.get(key));
        return;
      }

      this.fontLoader.load(
        url,
        (font) => {
          this.fonts.set(key, font);
          resolve(font);
        },
        undefined,
        (error) => reject(error)
      );
    });
  }

  getTexture(key) {
    return this.textures.get(key);
  }

  getModel(key) {
    return this.models.get(key);
  }

  dispose() {
    // 释放所有资源
    this.textures.forEach(texture => texture.dispose());
    this.models.forEach(model => {
      model.scene.traverse((obj) => {
        if (obj.isMesh) {
          obj.geometry.dispose();
          if (Array.isArray(obj.material)) {
            obj.material.forEach(material => material.dispose());
          } else {
            obj.material.dispose();
          }
        }
      });
    });

    this.textures.clear();
    this.models.clear();
    this.audio.clear();
    this.fonts.clear();
  }

  showLoadingScreen() {
    if (!this.loadingScreen) {
      this.loadingScreen = document.createElement('div');
      this.loadingScreen.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #1a1a2e;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        z-index: 9999;
        color: white;
        font-family: Arial, sans-serif;
      `;

      this.progressBar = document.createElement('div');
      this.progressBar.style.cssText = `
        width: 300px;
        height: 20px;
        background: #333;
        border-radius: 10px;
        margin-top: 20px;
        overflow: hidden;
      `;

      this.progressFill = document.createElement('div');
      this.progressFill.style.cssText = `
        width: 0%;
        height: 100%;
        background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
        transition: width 0.3s ease;
      `;

      this.progressBar.appendChild(this.progressFill);
      this.loadingScreen.innerHTML = '<h2>加载资源中...</h2>';
      this.loadingScreen.appendChild(this.progressBar);
      document.body.appendChild(this.loadingScreen);
    }
  }

  updateProgressBar(progress) {
    if (this.progressFill) {
      this.progressFill.style.width = `${progress}%`;
    }
  }

  hideLoadingScreen() {
    if (this.loadingScreen) {
      setTimeout(() => {
        this.loadingScreen.style.opacity = '0';
        this.loadingScreen.style.transition = 'opacity 0.5s ease';
        setTimeout(() => {
          if (this.loadingScreen.parentNode) {
            this.loadingScreen.parentNode.removeChild(this.loadingScreen);
          }
          this.loadingScreen = null;
        }, 500);
      }, 500);
    }
  }
}

// 使用示例
const assetManager = new AssetManager();
await assetManager.loadModel('character', '/models/character.glb');
await assetManager.loadTexture('metal', '/textures/metal.jpg');

1.2.3. 构建和部署优化

1.2.3.1. 生产环境构建配置

webpack.config.js(备选方案):

javascript

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.[contenthash].js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      {
        test: /\.(glsl|vs|fs|vert|frag)$/,
        exclude: /node_modules/,
        use: ['raw-loader', 'glslify-loader']
      },
      {
        test: /\.(png|jpg|jpeg|gif|hdr|exr)$/,
        type: 'asset/resource',
        generator: {
          filename: 'assets/[hash][ext][query]'
        }
      }
    ]
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,  // 移除console
            drop_debugger: true  // 移除debugger
          },
          output: {
            comments: false
          }
        }
      })
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        three: {
          test: /[\\/]node_modules[\\/](three)[\\/]/,
          name: 'three',
          chunks: 'all',
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: false
    }),
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240,
      minRatio: 0.8
    })
  ],
  performance: {
    hints: 'warning',
    maxAssetSize: 1024 * 1024,  // 1MB
    maxEntrypointSize: 1024 * 1024 * 2,  // 2MB
    assetFilter: function(assetFilename) {
      return !assetFilename.endsWith('.map');
    }
  }
};

1.3. 三、推荐开发工作流

1.3.1. 完整开发流程

完整开发流程

1.3.2. 快速开始模板

bash

# 使用官方模板快速开始
npx degit mrdoob/three.js/examples/webgl_animation_cloth my-threejs-project
cd my-threejs-project

# 或使用社区模板
# React: https://github.com/pmndrs/react-three-fiber
# Vue: https://github.com/troisjs/trois
# Svelte: https://github.com/hugozap/svelte-threejs

1.4. 四、故障排除指南

1.4.1. 常见问题解决

javascript

// 1. WebGL上下文创建失败
try {
  const renderer = new THREE.WebGLRenderer({ 
    antialias: true,
    powerPreference: 'high-performance' 
  });
} catch (error) {
  console.error('WebGL不支持,尝试降级:', error);
  // 降级方案
  const renderer = new THREE.WebGLRenderer({ 
    powerPreference: 'low-power' 
  });
}

// 2. 内存泄漏检测
function checkMemoryLeaks() {
  const startMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;

  // 执行一些操作后...
  const endMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;

  if (endMemory - startMemory > 10 * 1024 * 1024) { // 10MB
    console.warn('可能的内存泄漏');
  }
}

// 3. 跨域资源加载
const textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = 'anonymous';

// 4. 移动端适配问题
function checkMobileSupport() {
  const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
  if (isMobile) {
    // 降低渲染质量
    renderer.setPixelRatio(1);
    // 减少多边形数量
    // 禁用阴影等消耗性能的特性
  }
}

1.5. 总结

通过合理的环境配置和工具选择,可以极大提升Three.js开发效率和项目质量。建议:

  1. 初学者:从CDN开始,快速体验

  2. 正式项目:使用Vite + NPM + TypeScript

  3. React项目:选择React Three Fiber生态

  4. 性能关键:配置完整的监控和优化工具

  5. 团队协作:统一开发环境和代码规范

  6. 开发工具:VSCode免费快捷,配置灵活【推荐】

  7. 开发工具:WebStorm企业级选择,功能强大