1. 调试工具配置

1.1. 使用 Stats

Stats 跟随着 three一起安装在 node_modules\three\examples\jsm\libs 下
javascript

const stats = Stats();
// 设置模式,运行时鼠标点击可以自动切换3种模式
stats.setMode(0);
// 放置在浏览器右上角
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
// 放到dom中显示
document.body.appendChild(stats.domElement);

function animate() {
    // **别忘了此处的实时更新
    stats.update();
      renderer.render( scene, camera );
}
renderer.setAnimationLoop( animate );

1.2. 浏览器开发者工具配置

javascript

// 自定义调试脚本 debug.js
class ThreeJSDebugger {
  constructor(scene, camera, renderer) {
    this.scene = scene;
    this.camera = camera;
    this.renderer = renderer;
    this.helpers = new Map();
    this.initGlobalHelpers();
  }

  initGlobalHelpers() {
    // 将调试器挂载到window,方便控制台调用
    window.THREE_DEBUG = {
      showAxes: (size = 5) => this.addAxesHelper(size),
      showGrid: (size = 10, divisions = 10) => this.addGridHelper(size, divisions),
      showCamera: () => this.addCameraHelper(),
      showLights: () => this.addLightHelpers(),
      showBoundingBoxes: () => this.addBoundingBoxHelpers(),
      showStats: () => this.addStatsPanel(),
      clearHelpers: () => this.removeAllHelpers(),
      logSceneGraph: () => this.logSceneHierarchy(),
      getObjectByClick: () => this.setupRaycastDebug(),
      toggleWireframe: () => this.toggleWireframeMode()
    };
  }

  addAxesHelper(size) {
    const helper = new THREE.AxesHelper(size);
    this.scene.add(helper);
    this.helpers.set(`axes_${Date.now()}`, helper);
    console.log(`已添加坐标轴助手,大小: ${size}`);
  }

  addGridHelper(size, divisions) {
    const helper = new THREE.GridHelper(size, divisions, 0x444444, 0x888888);
    this.scene.add(helper);
    this.helpers.set(`grid_${Date.now()}`, helper);
  }

  addCameraHelper() {
    const helper = new THREE.CameraHelper(this.camera);
    this.scene.add(helper);
    this.helpers.set('camera', helper);
  }

  addLightHelpers() {
    this.scene.traverse((object) => {
      if (object.isLight) {
        let helper;
        if (object.isDirectionalLight) {
          helper = new THREE.DirectionalLightHelper(object, 1);
        } else if (object.isPointLight) {
          helper = new THREE.PointLightHelper(object, 1);
        } else if (object.isSpotLight) {
          helper = new THREE.SpotLightHelper(object);
        }
        if (helper) {
          this.scene.add(helper);
          this.helpers.set(`light_${object.uuid}`, helper);
        }
      }
    });
  }

  logSceneHierarchy() {
    console.group('Three.js 场景层级');
    this.scene.traverse((obj) => {
      const indent = ' '.repeat(obj.parent ? 2 : 0);
      console.log(`${indent}${obj.type}: ${obj.name || 'unnamed'} (${obj.uuid})`);
    });
    console.groupEnd();
  }

  setupRaycastDebug() {
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();

    const onClick = (event) => {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

      raycaster.setFromCamera(mouse, this.camera);
      const intersects = raycaster.intersectObjects(this.scene.children, true);

      if (intersects.length > 0) {
        const object = intersects[0].object;
        console.group('点击对象信息');
        console.log('对象:', object);
        console.log('世界位置:', object.position);
        console.log('材质:', object.material);
        console.log('几何体:', object.geometry);
        console.groupEnd();

        // 高亮显示
        this.highlightObject(object);
      }
    };

    window.addEventListener('click', onClick);
    console.log('点击调试已启用,点击场景中的对象查看信息');
  }

  highlightObject(object) {
    // 移除之前的高亮
    this.scene.traverse((obj) => {
      if (obj.userData.isHighlighted) {
        obj.material = obj.userData.originalMaterial;
        delete obj.userData.isHighlighted;
      }
    });

    // 保存原始材质并应用高亮
    object.userData.originalMaterial = object.material;
    object.userData.isHighlighted = true;
    object.material = new THREE.MeshBasicMaterial({ 
      color: 0xffff00,
      wireframe: true 
    });

    // 3秒后恢复
    setTimeout(() => {
      if (object.userData.isHighlighted) {
        object.material = object.userData.originalMaterial;
        delete obj.userData.isHighlighted;
      }
    }, 3000);
  }
}

// 使用示例
// 在控制台输入: THREE_DEBUG.showAxes(10)
// 在控制台输入: THREE_DEBUG.logSceneGraph()

1.3. 性能分析工具

1.3.1. 性能监控面板

javascript

class PerformanceMonitor {
  constructor(renderer) {
    this.renderer = renderer;
    this.metrics = {
      fps: 0,
      memory: 0,
      drawCalls: 0,
      triangles: 0,
      geometries: 0,
      textures: 0
    };

    this.initStats();
    this.initMemoryMonitor();
    this.initCustomPanel();
  }

  initStats() {
    // 使用Stats.js
    this.stats = new Stats();
    this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3: custom
    document.body.appendChild(this.stats.dom);

    // 自定义面板
    this.customPanel = new Stats.Panel('THREE', '#ff8', '#221');
    this.stats.addPanel(this.customPanel);
  }

  initMemoryMonitor() {
    // 监控WebGL内存使用
    if (this.renderer.info) {
      setInterval(() => {
        const info = this.renderer.info;
        this.metrics = {
          fps: this.stats.dom.children[0].children[0].innerText,
          memory: (performance.memory ? performance.memory.usedJSHeapSize / 1048576 : 0).toFixed(2),
          drawCalls: info.render.calls,
          triangles: info.render.triangles,
          geometries: info.memory.geometries,
          textures: info.memory.textures
        };

        // 更新自定义面板
        this.customPanel.update(
          this.metrics.drawCalls,
          Math.max(this.metrics.drawCalls, 1000)
        );

        // 输出警告
        if (this.metrics.drawCalls > 500) {
          console.warn(`Draw Calls过高: ${this.metrics.drawCalls}`);
        }
        if (this.metrics.triangles > 1000000) {
          console.warn(`三角形数量过多: ${this.metrics.triangles}`);
        }
      }, 1000);
    }
  }

  initCustomPanel() {
    // 创建自定义性能显示
    this.infoBox = document.createElement('div');
    this.infoBox.style.cssText = `
      position: fixed;
      top: 60px;
      left: 0;
      background: rgba(0,0,0,0.8);
      color: white;
      padding: 10px;
      font-family: monospace;
      font-size: 12px;
      z-index: 1000;
      max-width: 300px;
    `;
    document.body.appendChild(this.infoBox);

    setInterval(() => this.updateInfoBox(), 500);
  }

  updateInfoBox() {
    const info = this.renderer.info;
    const html = `
      <div><strong>Three.js 性能监控</strong></div>
      <div>Draw Calls: ${info.render.calls}</div>
      <div>Triangles: ${info.render.triangles.toLocaleString()}</div>
      <div>Points: ${info.render.points}</div>
      <div>Lines: ${info.render.lines}</div>
      <div>Geometries: ${info.memory.geometries}</div>
      <div>Textures: ${info.memory.textures}</div>
      <div>Programs: ${info.programs ? info.programs.length : 'N/A'}</div>
      ${performance.memory ? `
        <div>内存使用: ${(performance.memory.usedJSHeapSize / 1048576).toFixed(2)} MB</div>
        <div>内存限制: ${(performance.memory.jsHeapSizeLimit / 1048576).toFixed(2)} MB</div>
      ` : ''}
    `;
    this.infoBox.innerHTML = html;
  }

  logPerformance() {
    console.table({
      'Draw Calls': this.metrics.drawCalls,
      'Triangles': this.metrics.triangles.toLocaleString(),
      'Geometries': this.metrics.geometries,
      'Textures': this.metrics.textures,
      'FPS': this.metrics.fps,
      'Memory (MB)': this.metrics.memory
    });
  }
}

// 使用
const perfMonitor = new PerformanceMonitor(renderer);
// 在控制台输入: perfMonitor.logPerformance()