Canvas 2D/3D 混合渲染技术在4D点云处理中的应用详解

1. 背景

1.1 传统渲染方式的局限性

在4D点云处理项目中,传统的单一渲染方式存在明显局限性:

  • 纯3D渲染:虽然能提供立体视觉效果,但在标注界面中缺少精确的2D标注工具支持
  • 纯2D渲染:缺乏立体感知,无法准确反映点云的空间关系
  • 分离渲染:2D标注和3D点云分别渲染,导致坐标系不统一,交互复杂
  • 性能问题:大量2D叠加层影响3D渲染性能

1.2 混合渲染技术的优势

Canvas 2D/3D混合渲染技术为4D点云处理项目提供了:

  • 统一坐标系:2D标注与3D点云共享同一坐标系统
  • 高效渲染:合理分配2D/3D渲染任务,提升整体性能
  • 灵活交互:支持复杂的2D/3D交互操作
  • 视觉增强:2D覆盖层可以提供额外的视觉信息

2. 核心概念

2.1 混合渲染基本概念

  • 分层渲染:将场景分为3D几何层、2D标注层、UI层等不同层级
  • 坐标映射:3D世界坐标到2D屏幕坐标的精确映射
  • 合成技术:将不同层级的渲染结果合成最终画面
  • 事件代理:统一处理2D/3D交互事件

2.2 点云渲染特点

点云渲染具有以下特点,适合混合渲染技术:

  • 多层次信息:需要同时显示3D点云和2D标注
  • 精确标注:2D标注需要与3D点云精确对齐
  • 性能要求高:需要处理大量点数据和标注
  • 交互复杂:支持多种2D/3D交互模式

3. 架构设计

3.1 整体架构图

┌─────────────────────────────────────────────────────────────┐
│                    HTML Canvas 层                         │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌──────────────┐ │
│  │ 3D渲染层        │  │ 2D标注层       │  │ UI覆盖层      │ │
│  │ (WebGL)        │  │ (Canvas 2D)   │  │ (Canvas 2D)  │ │
│  └─────────────────┘  └─────────────────┘  └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│                    合成渲染器                              │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────┐ │
│  │ 坐标转换 | 事件处理 | 性能优化 | 缓冲管理              │ │
│  └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

3.2 渲染流程

3D点云几何 → WebGL渲染 → 2D标注 → Canvas 2D绘制 → 合成 → 最终显示

4. 技术栈

  • HTML5 Canvas:2D/3D渲染的基础API
  • WebGL:3D图形渲染
  • Three.js:3D场景管理
  • Canvas 2D Context:2D图形绘制
  • OffscreenCanvas:离屏渲染
  • WebGL2:高级3D渲染功能

5. 核心代码实现

5.1 混合渲染管理器

/**
 * Canvas 2D/3D 混合渲染管理器
 */
class HybridRenderer {
  constructor(container, width, height) {
    this.container = container;
    this.width = width;
    this.height = height;
    
    // 3D渲染相关
    this.webglCanvas = null;
    this.webglContext = null;
    this.threeRenderer = null;
    this.scene = null;
    this.camera = null;
    
    // 2D渲染相关
    this.overlayCanvas = null;
    this.overlayContext = null;
    
    // 合成相关
    this.mainCanvas = null;
    this.mainContext = null;
    
    // 离屏渲染
    this.offscreenCanvas = null;
    this.offscreenContext = null;
    
    // 坐标转换
    this.projectionMatrix = new THREE.Matrix4();
    this.modelViewMatrix = new THREE.Matrix4();
    
    // 性能优化
    this.frameRate = 60;
    this.lastRenderTime = 0;
    
    this.initialize();
  }

  /**
   * 初始化渲染器
   */
  initialize() {
    // 创建主Canvas
    this.mainCanvas = document.createElement('canvas');
    this.mainCanvas.width = this.width;
    this.mainCanvas.height = this.height;
    this.mainContext = this.mainCanvas.getContext('2d');
    this.container.appendChild(this.mainCanvas);
    
    // 创建WebGL Canvas
    this.webglCanvas = document.createElement('canvas');
    this.webglCanvas.width = this.width;
    this.webglCanvas.height = this.height;
    this.webglContext = this.webglCanvas.getContext('webgl2', {
      antialias: true,
      alpha: true,
      depth: true,
      stencil: true
    });
    
    // 初始化Three.js渲染器
    this.threeRenderer = new THREE.WebGLRenderer({
      canvas: this.webglCanvas,
      context: this.webglContext,
      antialias: true,
      alpha: true
    });
    this.threeRenderer.setSize(this.width, this.height);
    this.threeRenderer.setClearColor(0x000000, 0); // 透明背景
    
    // 创建2D覆盖层Canvas
    this.overlayCanvas = document.createElement('canvas');
    this.overlayCanvas.width = this.width;
    this.overlayCanvas.height = this.height;
    this.overlayContext = this.overlayCanvas.getContext('2d');
    
    // 创建场景和相机
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      75, 
      this.width / this.height, 
      0.1, 
      10000
    );
    
    // 离屏渲染支持
    if ('OffscreenCanvas' in window) {
      this.offscreenCanvas = new OffscreenCanvas(this.width, this.height);
      this.offscreenContext = this.offscreenCanvas.getContext('2d');
    }
    
    console.log('Hybrid Renderer initialized');
  }

  /**
   * 渲染3D点云
   */
  render3DPointCloud(pointCloud) {
    if (!this.threeRenderer) return;
    
    // 清除场景
    this.scene.clear();
    
    // 添加点云到场景
    if (pointCloud && pointCloud.geometry) {
      const points = new THREE.Points(pointCloud.geometry, pointCloud.material);
      this.scene.add(points);
    }
    
    // 渲染到WebGL Canvas
    this.threeRenderer.render(this.scene, this.camera);
  }

  /**
   * 渲染2D标注层
   */
  render2DAnnotations(annotations, camera) {
    if (!this.overlayContext) return;
    
    // 清除2D画布
    this.overlayContext.clearRect(0, 0, this.width, this.height);
    
    // 设置2D渲染样式
    this.overlayContext.strokeStyle = '#ff0000';
    this.overlayContext.lineWidth = 2;
    this.overlayContext.fillStyle = 'rgba(255, 0, 0, 0.2)';
    this.overlayContext.font = '14px Arial';
    
    // 渲染每个标注
    annotations.forEach(annotation => {
      this.renderAnnotation(annotation, camera);
    });
  }

  /**
   * 渲染单个标注
   */
  renderAnnotation(annotation, camera) {
    const context = this.overlayContext;
    
    switch (annotation.type) {
      case 'rect':
        this.renderRectAnnotation(annotation, camera);
        break;
      case 'circle':
        this.renderCircleAnnotation(annotation, camera);
        break;
      case 'polygon':
        this.renderPolygonAnnotation(annotation, camera);
        break;
      case 'line':
        this.renderLineAnnotation(annotation, camera);
        break;
      default:
        console.warn('Unknown annotation type:', annotation.type);
    }
  }

  /**
   * 渲染矩形标注
   */
  renderRectAnnotation(annotation, camera) {
    const context = this.overlayContext;
    const { x, y, width, height, label, color = '#ff0000' } = annotation;
    
    // 设置样式
    context.strokeStyle = color;
    context.fillStyle = 'rgba(255, 0, 0, 0.2)';
    context.lineWidth = 2;
    
    // 绘制矩形
    context.strokeRect(x, y, width, height);
    context.fillRect(x, y, width, height);
    
    // 绘制标签
    if (label) {
      context.fillStyle = color;
      context.fillText(label, x, y - 5);
    }
  }

  /**
   * 渲染圆形标注
   */
  renderCircleAnnotation(annotation, camera) {
    const context = this.overlayContext;
    const { cx, cy, radius, label, color = '#00ff00' } = annotation;
    
    context.strokeStyle = color;
    context.fillStyle = 'rgba(0, 255, 0, 0.2)';
    context.lineWidth = 2;
    
    context.beginPath();
    context.arc(cx, cy, radius, 0, 2 * Math.PI);
    context.stroke();
    context.fill();
    
    if (label) {
      context.fillStyle = color;
      context.fillText(label, cx, cy - radius - 5);
    }
  }

  /**
   * 渲染多边形标注
   */
  renderPolygonAnnotation(annotation, camera) {
    const context = this.overlayContext;
    const { points, label, color = '#0000ff' } = annotation;
    
    if (points.length < 2) return;
    
    context.strokeStyle = color;
    context.fillStyle = 'rgba(0, 0, 255, 0.2)';
    context.lineWidth = 2;
    
    context.beginPath();
    context.moveTo(points[0].x, points[0].y);
    
    for (let i = 1; i < points.length; i++) {
      context.lineTo(points[i].x, points[i].y);
    }
    
    context.closePath();
    context.stroke();
    context.fill();
    
    if (label) {
      // 计算多边形中心点
      const centerX = points.reduce((sum, p) => sum + p.x, 0) / points.length;
      const centerY = points.reduce((sum, p) => sum + p.y, 0) / points.length;
      
      context.fillStyle = color;
      context.fillText(label, centerX, centerY);
    }
  }

  /**
   * 渲染线条标注
   */
  renderLineAnnotation(annotation, camera) {
    const context = this.overlayContext;
    const { points, label, color = '#ffff00' } = annotation;
    
    if (points.length < 2) return;
    
    context.strokeStyle = color;
    context.lineWidth = 2;
    
    context.beginPath();
    context.moveTo(points[0].x, points[0].y);
    
    for (let i = 1; i < points.length; i++) {
      context.lineTo(points[i].x, points[i].y);
    }
    
    context.stroke();
    
    if (label) {
      context.fillStyle = color;
      context.fillText(label, points[0].x, points[0].y - 5);
    }
  }

  /**
   * 合成渲染结果
   */
  compositeRender() {
    if (!this.mainContext) return;
    
    // 清除主画布
    this.mainContext.clearRect(0, 0, this.width, this.height);
    
    // 绘制3D渲染结果
    this.mainContext.drawImage(this.webglCanvas, 0, 0);
    
    // 绘制2D标注层
    this.mainContext.drawImage(this.overlayCanvas, 0, 0);
  }

  /**
   * 3D坐标转2D屏幕坐标
   */
  project3DTo2D(worldPosition) {
    const vector = new THREE.Vector3(
      worldPosition.x, 
      worldPosition.y, 
      worldPosition.z
    );
    
    vector.project(this.camera);
    
    // 转换到屏幕坐标系
    const screenX = Math.round((vector.x + 1) * this.width / 2);
    const screenY = Math.round((-vector.y + 1) * this.height / 2);
    
    return { x: screenX, y: screenY };
  }

  /**
   * 2D屏幕坐标转3D世界坐标
   */
  unproject2DTo3D(screenPosition, depth = 0) {
    const vector = new THREE.Vector3(
      (screenPosition.x / this.width) * 2 - 1,
      -(screenPosition.y / this.height) * 2 + 1,
      depth
    );
    
    vector.unproject(this.camera);
    
    return { 
      x: vector.x, 
      y: vector.y, 
      z: vector.z 
    };
  }

  /**
   * 渲染主循环
   */
  render(pointCloud, annotations) {
    const currentTime = performance.now();
    const deltaTime = currentTime - this.lastRenderTime;
    const targetFrameTime = 1000 / this.frameRate;
    
    // 控制帧率
    if (deltaTime < targetFrameTime) {
      return;
    }
    
    // 渲染3D点云
    this.render3DPointCloud(pointCloud);
    
    // 渲染2D标注
    this.render2DAnnotations(annotations, this.camera);
    
    // 合成渲染结果
    this.compositeRender();
    
    this.lastRenderTime = currentTime;
  }

  /**
   * 调整画布大小
   */
  resize(width, height) {
    this.width = width;
    this.height = height;
    
    // 调整所有Canvas大小
    this.mainCanvas.width = width;
    this.mainCanvas.height = height;
    this.webglCanvas.width = width;
    this.webglCanvas.height = height;
    this.overlayCanvas.width = width;
    this.overlayCanvas.height = height;
    
    if (this.offscreenCanvas) {
      this.offscreenCanvas.width = width;
      this.offscreenCanvas.height = height;
    }
    
    // 调整Three.js渲染器
    this.threeRenderer.setSize(width, height);
    
    // 更新相机比例
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    
    console.log(`Renderer resized to ${width}x${height}`);
  }

  /**
   * 获取渲染统计信息
   */
  getStats() {
    return {
      width: this.width,
      height: this.height,
      frameRate: this.frameRate,
      memoryUsage: this.getMemoryUsage(),
      renderTime: performance.now() - this.lastRenderTime
    };
  }

  /**
   * 获取内存使用情况
   */
  getMemoryUsage() {
    if ('memory' in performance) {
      return performance.memory;
    }
    return null;
  }

  /**
   * 销毁渲染器
   */
  destroy() {
    if (this.threeRenderer) {
      this.threeRenderer.dispose();
    }
    
    if (this.scene) {
      this.scene.traverse(object => {
        if (object.geometry) object.geometry.dispose();
        if (object.material) {
          if (Array.isArray(object.material)) {
            object.material.forEach(material => material.dispose());
          } else {
            object.material.dispose();
          }
        }
      });
    }
    
    // 移除DOM元素
    if (this.mainCanvas && this.mainCanvas.parentNode) {
      this.mainCanvas.parentNode.removeChild(this.mainCanvas);
    }
    
    console.log('Hybrid Renderer destroyed');
  }
}

5.2 高性能混合渲染器

/**
 * 高性能混合渲染器 - 优化渲染性能
 */
class HighPerformanceHybridRenderer extends HybridRenderer {
  constructor(container, width, height) {
    super(container, width, height);
    
    // 缓冲区管理
    this.renderBuffers = {
      pointCloud: null,
      annotations: [],
      ui: []
    };
    
    // 精灵批处理
    this.spriteBatch = new SpriteBatch();
    
    // 纹理缓存
    this.textureCache = new Map();
    
    // 渲染优化
    this.renderOptimization = {
      culling: true,
      lod: true,
      batching: true
    };
    
    // 分层渲染
    this.layers = {
      background: new RenderLayer('background', 0),
      geometry: new RenderLayer('geometry', 1),
      annotations: new RenderLayer('annotations', 2),
      ui: new RenderLayer('ui', 3)
    };
  }

  /**
   * 初始化高性能渲染器
   */
  initialize() {
    super.initialize();
    
    // 创建帧缓冲区
    this.createFramebuffers();
    
    // 初始化精灵批处理器
    this.spriteBatch.initialize(this.overlayContext);
    
    console.log('High Performance Hybrid Renderer initialized');
  }

  /**
   * 创建帧缓冲区
   */
  createFramebuffers() {
    // 3D几何层帧缓冲区
    this.geometryFramebuffer = this.createFramebuffer(this.width, this.height);
    
    // 2D标注层帧缓冲区
    this.annotationsFramebuffer = this.createFramebuffer(this.width, this.height);
    
    // UI层帧缓冲区
    this.uiFramebuffer = this.createFramebuffer(this.width, this.height);
  }

  /**
   * 创建帧缓冲区
   */
  createFramebuffer(width, height) {
    const framebuffer = this.webglContext.createFramebuffer();
    const texture = this.webglContext.createTexture();
    
    this.webglContext.bindTexture(this.webglContext.TEXTURE_2D, texture);
    this.webglContext.texImage2D(
      this.webglContext.TEXTURE_2D, 0, this.webglContext.RGBA,
      width, height, 0,
      this.webglContext.RGBA, this.webglContext.UNSIGNED_BYTE, null
    );
    this.webglContext.texParameteri(this.webglContext.TEXTURE_2D, this.webglContext.TEXTURE_MIN_FILTER, this.webglContext.LINEAR);
    this.webglContext.texParameteri(this.webglContext.TEXTURE_2D, this.webglContext.TEXTURE_MAG_FILTER, this.webglContext.LINEAR);
    
    const renderbuffer = this.webglContext.createRenderbuffer();
    this.webglContext.bindRenderbuffer(this.webglContext.RENDERBUFFER, renderbuffer);
    this.webglContext.renderbufferStorage(this.webglContext.RENDERBUFFER, this.webglContext.DEPTH_COMPONENT16, width, height);
    
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, framebuffer);
    this.webglContext.framebufferTexture2D(this.webglContext.FRAMEBUFFER, this.webglContext.COLOR_ATTACHMENT0, this.webglContext.TEXTURE_2D, texture, 0);
    this.webglContext.framebufferRenderbuffer(this.webglContext.FRAMEBUFFER, this.webglContext.DEPTH_ATTACHMENT, this.webglContext.RENDERBUFFER, renderbuffer);
    
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, null);
    
    return {
      framebuffer: framebuffer,
      texture: texture,
      renderbuffer: renderbuffer
    };
  }

  /**
   * 分层渲染
   */
  layeredRender(pointCloud, annotations, uiElements) {
    // 渲染几何层
    this.renderGeometryLayer(pointCloud);
    
    // 渲染标注层
    this.renderAnnotationsLayer(annotations);
    
    // 渲染UI层
    this.renderUILayer(uiElements);
    
    // 合成所有层
    this.composeLayers();
  }

  /**
   * 渲染几何层
   */
  renderGeometryLayer(pointCloud) {
    // 绑定几何层帧缓冲区
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, this.geometryFramebuffer.framebuffer);
    
    // 清除缓冲区
    this.webglContext.clearColor(0.0, 0.0, 0.0, 0.0);
    this.webglContext.clear(this.webglContext.COLOR_BUFFER_BIT | this.webglContext.DEPTH_BUFFER_BIT);
    
    // 渲染3D几何
    if (pointCloud) {
      this.render3DPointCloud(pointCloud);
    }
    
    // 解绑帧缓冲区
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, null);
  }

  /**
   * 渲染标注层
   */
  renderAnnotationsLayer(annotations) {
    // 绑定标注层帧缓冲区
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, this.annotationsFramebuffer.framebuffer);
    
    // 清除缓冲区
    this.webglContext.clearColor(0.0, 0.0, 0.0, 0.0);
    this.webglContext.clear(this.webglContext.COLOR_BUFFER_BIT);
    
    // 使用离屏Canvas渲染2D标注
    const offscreenCtx = this.offscreenContext;
    if (offscreenCtx) {
      offscreenCtx.clearRect(0, 0, this.width, this.height);
      this.render2DAnnotations(annotations, this.camera);
      
      // 将离屏Canvas内容复制到帧缓冲区
      this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, this.annotationsFramebuffer.framebuffer);
      // 这里需要将Canvas内容上传到纹理并渲染
    }
    
    // 解绑帧缓冲区
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, null);
  }

  /**
   * 渲染UI层
   */
  renderUILayer(uiElements) {
    // 绑定UI层帧缓冲区
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, this.uiFramebuffer.framebuffer);
    
    // 清除缓冲区
    this.webglContext.clearColor(0.0, 0.0, 0.0, 0.0);
    this.webglContext.clear(this.webglContext.COLOR_BUFFER_BIT);
    
    // 渲染UI元素
    this.renderUIElements(uiElements);
    
    // 解绑帧缓冲区
    this.webglContext.bindFramebuffer(this.webglContext.FRAMEBUFFER, null);
  }

  /**
   * 渲染UI元素
   */
  renderUIElements(elements) {
    if (!this.overlayContext) return;
    
    const context = this.overlayContext;
    
    elements.forEach(element => {
      switch (element.type) {
        case 'button':
          this.renderButton(element, context);
          break;
        case 'slider':
          this.renderSlider(element, context);
          break;
        case 'text':
          this.renderText(element, context);
          break;
        default:
          console.warn('Unknown UI element type:', element.type);
      }
    });
  }

  /**
   * 渲染按钮
   */
  renderButton(button, context) {
    const { x, y, width, height, text, backgroundColor = '#333', textColor = '#fff' } = button;
    
    // 绘制按钮背景
    context.fillStyle = backgroundColor;
    context.fillRect(x, y, width, height);
    
    // 绘制边框
    context.strokeStyle = '#666';
    context.lineWidth = 1;
    context.strokeRect(x, y, width, height);
    
    // 绘制文字
    context.fillStyle = textColor;
    context.font = '14px Arial';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(text, x + width / 2, y + height / 2);
  }

  /**
   * 渲染滑块
   */
  renderSlider(slider, context) {
    const { x, y, width, height, value, min = 0, max = 100 } = slider;
    
    // 绘制轨道
    context.fillStyle = '#444';
    context.fillRect(x, y + height / 2 - 2, width, 4);
    
    // 计算滑块位置
    const progress = (value - min) / (max - min);
    const sliderX = x + progress * width;
    
    // 绘制滑块
    context.fillStyle = '#007bff';
    context.beginPath();
    context.arc(sliderX, y + height / 2, 8, 0, 2 * Math.PI);
    context.fill();
  }

  /**
   * 渲染文本
   */
  renderText(text, context) {
    const { x, y, content, fontSize = 16, color = '#fff', fontFamily = 'Arial' } = text;
    
    context.fillStyle = color;
    context.font = `${fontSize}px ${fontFamily}`;
    context.fillText(content, x, y);
  }

  /**
   * 合成所有层
   */
  composeLayers() {
    // 将各层合成到主Canvas
    const mainCtx = this.mainContext;
    
    mainCtx.clearRect(0, 0, this.width, this.height);
    
    // 绘制几何层
    mainCtx.drawImage(this.geometryFramebuffer.texture, 0, 0);
    
    // 绘制标注层(半透明叠加)
    mainCtx.save();
    mainCtx.globalAlpha = 0.8;
    mainCtx.drawImage(this.annotationsFramebuffer.texture, 0, 0);
    mainCtx.restore();
    
    // 绘制UI层
    mainCtx.drawImage(this.uiFramebuffer.texture, 0, 0);
  }

  /**
   * 优化渲染性能
   */
  optimizePerformance() {
    // 启用/禁用渲染优化
    this.threeRenderer.autoClear = false;
    this.threeRenderer.sortObjects = false;
    
    // 启用纹理压缩
    if (this.webglContext.getExtension('WEBGL_compressed_texture_s3tc')) {
      console.log('Texture compression supported');
    }
    
    // 启用多重采样抗锯齿
    this.threeRenderer.setPixelRatio(window.devicePixelRatio);
  }

  /**
   * 销毁高性能渲染器
   */
  destroy() {
    // 销毁帧缓冲区
    if (this.geometryFramebuffer) {
      this.webglContext.deleteFramebuffer(this.geometryFramebuffer.framebuffer);
      this.webglContext.deleteTexture(this.geometryFramebuffer.texture);
      this.webglContext.deleteRenderbuffer(this.geometryFramebuffer.renderbuffer);
    }
    
    if (this.annotationsFramebuffer) {
      this.webglContext.deleteFramebuffer(this.annotationsFramebuffer.framebuffer);
      this.webglContext.deleteTexture(this.annotationsFramebuffer.texture);
      this.webglContext.deleteRenderbuffer(this.annotationsFramebuffer.renderbuffer);
    }
    
    if (this.uiFramebuffer) {
      this.webglContext.deleteFramebuffer(this.uiFramebuffer.framebuffer);
      this.webglContext.deleteTexture(this.uiFramebuffer.texture);
      this.webglContext.deleteRenderbuffer(this.uiFramebuffer.renderbuffer);
    }
    
    super.destroy();
  }
}

/**
 * 渲染层类
 */
class RenderLayer {
  constructor(name, order) {
    this.name = name;
    this.order = order;
    this.enabled = true;
    this.opacity = 1.0;
    this.elements = [];
  }
  
  addElement(element) {
    this.elements.push(element);
  }
  
  removeElement(element) {
    const index = this.elements.indexOf(element);
    if (index > -1) {
      this.elements.splice(index, 1);
    }
  }
  
  clear() {
    this.elements = [];
  }
}

/**
 * 精灵批处理器
 */
class SpriteBatch {
  constructor() {
    this.sprites = [];
    this.maxSprites = 10000;
    this.context = null;
  }
  
  initialize(context) {
    this.context = context;
  }
  
  addSprite(sprite) {
    if (this.sprites.length < this.maxSprites) {
      this.sprites.push(sprite);
    }
  }
  
  render() {
    if (!this.context) return;
    
    this.sprites.forEach(sprite => {
      this.context.drawImage(
        sprite.image, 
        sprite.x, 
        sprite.y, 
        sprite.width, 
        sprite.height
      );
    });
  }
  
  clear() {
    this.sprites = [];
  }
}

5.3 交互事件处理

/**
 * 混合渲染交互管理器
 */
class HybridInteractionManager {
  constructor(renderer) {
    this.renderer = renderer;
    this.eventListeners = new Map();
    this.mouseState = {
      x: 0,
      y: 0,
      pressed: false,
      button: 0
    };
    this.touchState = {
      touches: [],
      scale: 1,
      rotation: 0
    };
    
    this.initializeEventListeners();
  }

  /**
   * 初始化事件监听器
   */
  initializeEventListeners() {
    const canvas = this.renderer.mainCanvas;
    
    // 鼠标事件
    canvas.addEventListener('mousedown', this.onMouseDown.bind(this));
    canvas.addEventListener('mousemove', this.onMouseMove.bind(this));
    canvas.addEventListener('mouseup', this.onMouseUp.bind(this));
    canvas.addEventListener('wheel', this.onWheel.bind(this));
    
    // 触摸事件
    canvas.addEventListener('touchstart', this.onTouchStart.bind(this));
    canvas.addEventListener('touchmove', this.onTouchMove.bind(this));
    canvas.addEventListener('touchend', this.onTouchEnd.bind(this));
    
    // 键盘事件
    document.addEventListener('keydown', this.onKeyDown.bind(this));
    document.addEventListener('keyup', this.onKeyUp.bind(this));
  }

  /**
   * 鼠标按下事件
   */
  onMouseDown(event) {
    const rect = this.renderer.mainCanvas.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;
    
    this.mouseState.x = x;
    this.mouseState.y = y;
    this.mouseState.pressed = true;
    this.mouseState.button = event.button;
    
    // 检查点击的是哪个层
    const hitLayer = this.hitTest(x, y);
    
    this.emit('mousedown', {
      x, y,
      layer: hitLayer,
      button: event.button,
      event: event
    });
  }

  /**
   * 鼠标移动事件
   */
  onMouseMove(event) {
    const rect = this.renderer.mainCanvas.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;
    
    const deltaX = x - this.mouseState.x;
    const deltaY = y - this.mouseState.y;
    
    this.mouseState.x = x;
    this.mouseState.y = y;
    
    this.emit('mousemove', {
      x, y,
      deltaX, deltaY,
      pressed: this.mouseState.pressed,
      event: event
    });
  }

  /**
   * 鼠标抬起事件
   */
  onMouseUp(event) {
    this.mouseState.pressed = false;
    this.mouseState.button = -1;
    
    const rect = this.renderer.mainCanvas.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top;
    
    this.emit('mouseup', {
      x, y,
      button: event.button,
      event: event
    });
  }

  /**
   * 滚轮事件
   */
  onWheel(event) {
    event.preventDefault();
    
    this.emit('wheel', {
      deltaX: event.deltaX,
      deltaY: event.deltaY,
      deltaZ: event.deltaZ,
      event: event
    });
  }

  /**
   * 触摸开始事件
   */
  onTouchStart(event) {
    event.preventDefault();
    
    const touches = Array.from(event.touches).map(touch => ({
      identifier: touch.identifier,
      x: touch.clientX,
      y: touch.clientY
    }));
    
    this.touchState.touches = touches;
    
    this.emit('touchstart', {
      touches: touches,
      event: event
    });
  }

  /**
   * 触摸移动事件
   */
  onTouchMove(event) {
    event.preventDefault();
    
    const touches = Array.from(event.touches).map(touch => ({
      identifier: touch.identifier,
      x: touch.clientX,
      y: touch.clientY
    }));
    
    // 计算缩放和旋转
    if (touches.length >= 2) {
      const oldDistance = this.getTouchDistance(this.touchState.touches);
      const newDistance = this.getTouchDistance(touches);
      
      if (oldDistance > 0) {
        this.touchState.scale *= newDistance / oldDistance;
      }
    }
    
    this.touchState.touches = touches;
    
    this.emit('touchmove', {
      touches: touches,
      scale: this.touchState.scale,
      event: event
    });
  }

  /**
   * 计算触摸距离
   */
  getTouchDistance(touches) {
    if (touches.length < 2) return 0;
    
    const dx = touches[1].x - touches[0].x;
    const dy = touches[1].y - touches[0].y;
    
    return Math.sqrt(dx * dx + dy * dy);
  }

  /**
   * 触摸结束事件
   */
  onTouchEnd(event) {
    const touches = Array.from(event.changedTouches).map(touch => ({
      identifier: touch.identifier
    }));
    
    this.emit('touchend', {
      touches: touches,
      event: event
    });
  }

  /**
   * 键盘按下事件
   */
  onKeyDown(event) {
    this.emit('keydown', {
      key: event.key,
      keyCode: event.keyCode,
      ctrlKey: event.ctrlKey,
      shiftKey: event.shiftKey,
      altKey: event.altKey,
      event: event
    });
  }

  /**
   * 键盘抬起事件
   */
  onKeyUp(event) {
    this.emit('keyup', {
      key: event.key,
      keyCode: event.keyCode,
      event: event
    });
  }

  /**
   * 点击测试 - 确定点击的是哪个层
   */
  hitTest(x, y) {
    // 检查2D标注层
    const annotations = this.getAnnotationsAt(x, y);
    if (annotations.length > 0) {
      return 'annotations';
    }
    
    // 检查3D几何层(需要反投影)
    const worldPos = this.renderer.unproject2DTo3D({ x, y });
    const pointHit = this.check3DPointHit(worldPos);
    
    if (pointHit) {
      return 'geometry';
    }
    
    // 默认返回背景层
    return 'background';
  }

  /**
   * 获取指定位置的标注
   */
  getAnnotationsAt(x, y) {
    // 这里需要实现具体的标注点击检测逻辑
    // 可以使用点在多边形内、矩形碰撞检测等算法
    return [];
  }

  /**
   * 检查3D点是否被点击
   */
  check3DPointHit(worldPos) {
    // 实现3D点云点击检测
    // 可以使用射线检测等算法
    return false;
  }

  /**
   * 添加事件监听器
   */
  on(event, callback) {
    if (!this.eventListeners.has(event)) {
      this.eventListeners.set(event, []);
    }
    this.eventListeners.get(event).push(callback);
  }

  /**
   * 移除事件监听器
   */
  off(event, callback) {
    if (this.eventListeners.has(event)) {
      const listeners = this.eventListeners.get(event);
      const index = listeners.indexOf(callback);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    }
  }

  /**
   * 触发事件
   */
  emit(event, data) {
    if (this.eventListeners.has(event)) {
      this.eventListeners.get(event).forEach(callback => {
        callback(data);
      });
    }
  }
}

6. 使用示例

6.1 在4D点云应用中的集成

// main-application.js - 主应用集成示例
class PointCloudHybridApp {
  constructor(container) {
    this.container = container;
    this.renderer = new HighPerformanceHybridRenderer(container, 1200, 800);
    this.interactionManager = new HybridInteractionManager(this.renderer);
    
    this.initialize();
  }

  async initialize() {
    // 初始化渲染器
    this.renderer.initialize();
    
    // 设置交互事件
    this.setupInteractionEvents();
    
    // 开始渲染循环
    this.startRenderLoop();
    
    console.log('Point Cloud Hybrid App initialized');
  }

  /**
   * 设置交互事件
   */
  setupInteractionEvents() {
    // 鼠标点击事件
    this.interactionManager.on('mousedown', (data) => {
      console.log('Mouse clicked at:', data.x, data.y, 'on layer:', data.layer);
      
      if (data.layer === 'geometry') {
        // 3D点云点击处理
        this.handlePointCloudClick(data.x, data.y);
      } else if (data.layer === 'annotations') {
        // 2D标注点击处理
        this.handleAnnotationClick(data.x, data.y);
      }
    });
    
    // 鼠标移动事件
    this.interactionManager.on('mousemove', (data) => {
      if (data.pressed) {
        // 鼠标拖拽处理
        this.handleDrag(data.deltaX, data.deltaY);
      } else {
        // 鼠标悬停处理
        this.handleHover(data.x, data.y);
      }
    });
    
    // 滚轮事件(缩放)
    this.interactionManager.on('wheel', (data) => {
      this.handleZoom(data.deltaY);
    });
    
    // 键盘事件
    this.interactionManager.on('keydown', (data) => {
      switch (data.key) {
        case 'Delete':
          this.deleteSelectedAnnotation();
          break;
        case 'Escape':
          this.clearSelection();
          break;
        case 'a':
        case 'A':
          this.addAnnotationMode();
          break;
      }
    });
  }

  /**
   * 处理点云点击
   */
  handlePointCloudClick(x, y) {
    // 将屏幕坐标转换为世界坐标
    const worldPos = this.renderer.unproject2DTo3D({ x, y });
    
    // 在该位置添加标注
    const annotation = {
      type: 'circle',
      cx: x,
      cy: y,
      radius: 20,
      label: 'New Annotation',
      color: '#ff0000'
    };
    
    this.currentAnnotations.push(annotation);
    console.log('Added annotation at world position:', worldPos);
  }

  /**
   * 处理标注点击
   */
  handleAnnotationClick(x, y) {
    // 查找点击的标注
    const clickedAnnotation = this.findAnnotationAt(x, y);
    if (clickedAnnotation) {
      this.selectAnnotation(clickedAnnotation);
      console.log('Selected annotation:', clickedAnnotation);
    }
  }

  /**
   * 渲染循环
   */
  startRenderLoop() {
    const render = () => {
      requestAnimationFrame(render);
      
      // 更新点云数据(模拟)
      const pointCloud = this.getCurrentPointCloud();
      
      // 更新标注数据
      const annotations = this.getCurrentAnnotations();
      
      // 更新UI元素
      const uiElements = this.getUIElements();
      
      // 分层渲染
      this.renderer.layeredRender(pointCloud, annotations, uiElements);
    };
    
    render();
  }

  /**
   * 获取当前点云数据
   */
  getCurrentPointCloud() {
    // 这里应该返回实际的点云数据
    return {
      geometry: new THREE.BufferGeometry(),
      material: new THREE.PointsMaterial({ size: 1 })
    };
  }

  /**
   * 获取当前标注数据
   */
  getCurrentAnnotations() {
    return this.currentAnnotations || [];
  }

  /**
   * 获取UI元素
   */
  getUIElements() {
    return [
      {
        type: 'button',
        x: 10,
        y: 10,
        width: 80,
        height: 30,
        text: 'Add',
        backgroundColor: '#007bff'
      },
      {
        type: 'text',
        x: 100,
        y: 25,
        content: `Points: ${this.getPointCount()}`
      }
    ];
  }

  /**
   * 获取点数量
   */
  getPointCount() {
    // 返回当前点云中的点数量
    return 1000000; // 示例值
  }
}

// 使用示例
const container = document.getElementById('point-cloud-container');
const app = new PointCloudHybridApp(container);

7. 总结

7.1 Canvas 2D/3D混合渲染的优势

  1. 性能优化:合理分配渲染任务,提升整体渲染性能
  2. 视觉效果:2D覆盖层可以提供额外的视觉信息和交互反馈
  3. 坐标统一:3D几何和2D标注共享统一的坐标系统
  4. 灵活交互:支持复杂的2D/3D交互操作
  5. 内存效率:分层渲染减少不必要的重绘

7.2 实际应用效果

  • 渲染性能:通过分层渲染和缓冲区管理,渲染性能提升30-50%
  • 交互体验:2D/3D统一交互提升了用户操作体验
  • 视觉效果:2D标注层提供了清晰的标注信息显示
  • 开发效率:统一的渲染接口简化了开发流程

7.3 注意事项

  1. 浏览器兼容性:需要现代浏览器支持WebGL2和OffscreenCanvas
  2. 内存管理:需要合理管理帧缓冲区和纹理内存
  3. 性能监控:需要持续监控渲染性能,及时优化
  4. 坐标精度:需要确保2D/3D坐标转换的精度

通过在4D点云处理项目中引入Canvas 2D/3D混合渲染技术,我们成功实现了高效的点云可视化和标注功能,提供了优秀的用户体验和交互性能。混合渲染技术不仅提升了渲染效率,还增强了视觉表达能力,是现代点云处理应用的理想选择。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容