Integração de Realidade Aumentada: Combinando Câmera e Three.js em Mini Programs

A convergência entre o feed de vídeo em tempo real e elementos gráficos tridimensionais é o pilar fundamental de qualquer aplicação de Realidade Aumentada (AR). Para desenvolvedores que utilizam o ecossistema de WeChat Mini Programs, esse desafio técnico resume-se a uma questão principal: como renderizar um cenário Three.js de forma que a câmera do dispositivo sirva como plano de fundo.

Existem duas abordagens principais para atingir esse objetivo, cada uma com suas vantagens e complexidades técnicas.

Abordagem 1: Estrutura de Camadas com Transparência (Dual Canvas)

Esta técnica consiste em sobrepor dois elementos na interface: o componente nativo de câmera (camera) na camada inferior e um canvas WebGL na camada superior. O segredo aqui é garantir que o fundo do renderizador WebGL seja compleatmente transparente.

Configuração do Layout (WXML)

No arquivo de interface, posicionamos ambos os elementos de forma absoluta para preencher a tela, utilizando o z-index para controlar a ordem de empilhamento.

<view>
  <!-- Camada WebGL (Superior) -->
  <canvas 
    type="webgl" 
    id="mainCanvas" 
    style="position:fixed; top:0; width:{{viewWidth}}px; height:{{viewHeight}}px; z-index:10;">
  </canvas>
  
  <!-- Camada da Câmera (Inferior) -->
  <camera 
    device-position="back" 
    flash="off" 
    frame-size="medium"
    style="position:fixed; top:0; width:100%; height:100%; z-index:1;">
  </camera>
</view>

Lógica de Renderização (JavaScript)

Ao inicializar o Three.js, é crucial configurar o parâmetro alpha no construtor do renderizador. Sem isso, o canvas terá um fundo preto ou branco sólido por padrão.

import * as THREE from '../../libs/three.js';

Page({
  data: { viewWidth: 0, viewHeight: 0 },

  onLoad() {
    this.setupVisualContext();
  },

  setupVisualContext() {
    const query = wx.createSelectorQuery();
    query.select('#mainCanvas').node().exec((res) => {
      const canvas = res[0].node;
      const sysInfo = wx.getSystemInfoSync();
      
      canvas.width = sysInfo.windowWidth * sysInfo.pixelRatio;
      canvas.height = sysInfo.windowHeight * sysInfo.pixelRatio;

      this.setData({
        viewWidth: sysInfo.windowWidth,
        viewHeight: sysInfo.windowHeight
      });

      this.initThreeScene(canvas);
    });
  },

  initThreeScene(canvas) {
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 0.1, 1000);
    
    // Importante: Habilitar o canal alpha para transparência
    const renderer = new THREE.WebGLRenderer({
      canvas: canvas,
      alpha: true,
      antialias: true
    });
    renderer.setSize(canvas.width, canvas.height);

    const geometry = new THREE.BoxGeometry(2, 2, 2);
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const box = new THREE.Mesh(geometry, material);
    box.position.z = -5;
    scene.add(box);

    const animate = () => {
      canvas.requestAnimationFrame(animate);
      box.rotation.y += 0.02;
      renderer.render(scene, camera);
    };
    animate();
  }
});

Abordagem 2: Projeção de Textura de Câmera (Single Canvas)

Nesta segunda técnica, utilizamos apenas um canvas WebGL. Capturamos os frames da câmera em tempo real e os aplicamos como uma textura em um plano (Mesh) posicionado ao fundo da cena 3D.

Configuração do Layout (WXML)

A câmera é mantida fora da área visível (usando coordeandas negativas no estilo), servindo apenas como fonte de dados.

<view>
  <canvas type="webgl" id="arCanvas" style="width:{{w}}px; height:{{h}}px;"></canvas>
  <camera device-position="back" frame-size="medium" style="position:absolute; top:-9999px;"></camera>
</view>

Processamento de Frames e Texturização

Aqui, utilizamos o onCameraFrame para extrair os dados brutos (RGBA) de cada quadro e atualizamos a textura dinamicamente.

import * as THREE from '../../libs/three.js';

Page({
  onLoad() {
    this.initARCamera();
    this.initWebGL();
  },

  initARCamera() {
    const cameraCtx = wx.createCameraContext();
    const listener = cameraCtx.onCameraFrame((frame) => {
      const rawData = new Uint8Array(frame.data);
      // Criação da textura a partir de dados brutos
      const texture = new THREE.DataTexture(
        rawData, 
        frame.width, 
        frame.height, 
        THREE.RGBAFormat
      );
      texture.needsUpdate = true;
      this.currentFrameTexture = texture;
    });
    listener.start();
  },

  initWebGL() {
    // ... setup básico de cena e câmera ...
    
    // Criar o plano de fundo que receberá a imagem da câmera
    const bgGeo = new THREE.PlaneGeometry(2, 2);
    const bgMat = new THREE.MeshBasicMaterial();
    const bgMesh = new THREE.Mesh(bgGeo, bgMat);
    
    // O plano deve estar atrás de todos os objetos
    bgMesh.position.z = -10; 
    // Ajuste de orientação: frames de câmera costumam vir rotacionados
    bgMesh.rotation.z = Math.PI / 2; 
    
    this.scene.add(bgMesh);
    this.backgroundPlane = bgMesh;
    this.renderLoop();
  },

  renderLoop() {
    if (this.currentFrameTexture) {
      // Libera a memória da textura anterior antes de atribuir a nova
      if (this.backgroundPlane.material.map) {
        this.backgroundPlane.material.map.dispose();
      }
      this.backgroundPlane.material.map = this.currentFrameTexture;
    }
    
    this.renderer.render(this.scene, this.camera);
    this.canvas.requestAnimationFrame(() => this.renderLoop());
  }
});

Comparativo e Considerações Técnicas

A Abordagem 1 é significativamente mais simples de implementar e consome menos CPU, pois delega a renderização do vídeo ao sistema operacional. No entanto, ela pode apresentar latência visual entre o movimento da câmera e o objeto 3D, além de dificuldades em alguns modelos de dispositivos para manter a transparência perfeita do canvas.

A Abordagem 2 oferece controle total. Como o vídeo e os objetos 3D estão no mesmo contexto WebGL, é possível aplicar filtros de pós-processamento uniformes e garantir que a sincronia entre o frame e o objeto seja perfeita. O desafio aqui é o gerenciamento de memória (vincular e desvincular texturas a cada frame) e a necessidade de ajustar manualmente a proporção e rotação do plano de fundo para evitar distorções na imagem.

Para casos de uso simples como visualização de produtos (V-Commerce), a primeira opção costuma ser suficiente. Para aplicações avançadas que exigem análise de imagem (Computer Vision) ou efeitos visuais complexos, a segunda opção é o padrão da indústria.

Tags: WeChat Mini Program three.js WebGL Realidade Aumentada javascript

Publicado em 6-13 02:20 por Thomas