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.