Referência de trabalho: three.js e WebXR

Consulta, não leitura corrida. Cada entrada responde a uma pergunta de execução — “como ponho um objeto na cena”, “o que é fov”, “por que o clique acerta o lugar errado” — com o menor código que roda, o significado de cada parâmetro e o que acontece quando ele é preenchido errado.

Quando esta página foi escrita, e onde procurar o que mudou

Escrita em 9 de setembro de 2026, por volta das 21 horas. As assinaturas, os valores padrão e os nomes desta página foram extraídos da versão da biblioteca instalada no projeto do professor naquele momento — a série r169, resolvida como 0.169.0 no arquivo de trava —, e os exemplos foram compilados contra ela. A interface de realidade estendida do navegador não tem versão de projeto: ou o aparelho a implementa, ou não.

Uma referência de biblioteca tem prazo de validade, e o desta é o próximo salto de versão. Quando algum nome aqui não existir mais, ou um parâmetro tiver mudado de ordem, a página errada é esta, e não o programa. As fontes abaixo dizem o que vale hoje.

Onde O que traz
threejs.org/docs a referência oficial da biblioteca, sempre na versão mais recente. Cada classe tem a lista completa de propriedades e métodos
threejs.org/manual o manual introdutório, com explicações longas e exemplos interativos
threejs.org/examples centenas de demonstrações executáveis, cada uma com o código à mostra
as notas de versão da biblioteca o que mudou em cada série. É onde se descobre o que quebrou entre a instalada aqui e a atual
a documentação da interface, na MDN a referência da interface do navegador, com o estado de suporte de cada recurso
a especificação, no W3C a definição normativa da interface, para quando a documentação não bastar
immersiveweb.dev portal da comunidade do padrão, com guias e notícias de suporte
os exemplos oficiais do padrão demonstrações executáveis no visor, uma por recurso

Todos os oito foram abertos e conferidos no dia em que esta página foi escrita. Dois candidatos caíram na conferência e não estão aqui: a tradução em português da documentação da interface devolveu erro, e um site de tabela de compatibilidade recusou a consulta. Endereço que não responde não entra — nem com ressalva.

A documentação da biblioteca está em inglês; a da interface do navegador tem tradução parcial para o português, e a versão em inglês é a que se mantém atualizada.

Como conferir a versão que o seu projeto usa, em vez de confiar nesta página: o arquivo de dependências do projeto declara a faixa aceita, e o arquivo de trava registra a versão exata resolvida na sua máquina. Divergindo entre si, o arquivo de trava tem razão; divergindo do que está escrito aqui, quem está desatualizada é esta página.

Como esta referência está montada

Toda tabela de parâmetros desta página tem quatro colunas, e a quarta é a que costuma faltar nas referências de biblioteca.

Coluna O que traz
Parâmetro o nome exato, como o código o escreve
O que é o significado, com a unidade quando existe
Padrão o valor assumido quando o parâmetro é omitido
Se errar o sintoma concreto de um valor mal escolhido

As assinaturas e os padrões vêm da versão instalada no projeto do professor, e não da documentação de uma versão qualquer. Todo exemplo de código foi compilado contra a mesma configuração estrita que o projeto usa: o que está escrito aqui compila.

Onde um parâmetro aceita ângulo, a unidade está dita — a biblioteca mistura os dois casos, e essa mistura é fonte constante de erro. Câmera e arestas recebem graus; rotação de objeto recebe radianos.

Onde procurar

A pergunta A entrada
o que é malha, geometria, material, vértice o vocabulário mínimo
para que lado é o +X, e em que unidade eu escrevo eixos e unidades
como faço aparecer alguma coisa na tela a cena mínima
o que é fov, near, far, aspect a câmera
o que cada opção do renderizador faz o renderizador
onde ponho o código que roda a cada quadro o laço
que formas existem prontas, e o que é “segmento” as formas prontas
o que é roughness, metalness, side, emissive os materiais
por que meu objeto está preto as luzes
como movo, giro e redimensiono mover, girar, redimensionar
qual a diferença entre rotation e quaternion a rotação
como faço um objeto acompanhar outro agrupar
como sei o que o usuário apontou ou clicou saber o que a pessoa apontou
como carrego um modelo pronto carregar um modelo pronto
como ponho imagem, foto ou texto numa superfície as texturas
como toco a animação que veio no modelo a animação do modelo
como entro em realidade virtual entrar em realidade virtual
o que é espaço de referência, e qual escolher o espaço de referência
como entro em realidade aumentada entrar em realidade aumentada
como leio gatilho, aperto e posição dos controles ler os controles
como movo a pessoa pelo ambiente mover a pessoa
por que caiu para dezoito quadros por segundo o orçamento de desempenho
está tudo preto, ou nada abre — e agora quando não funciona
o que significa aquela palavra o glossário

O vocabulário mínimo

Sete palavras sustentam tudo o que vem depois. Elas aparecem nas assinaturas, nas mensagens de erro e na documentação da biblioteca, e quem não as separa lê o resto por adivinhação.

Vértice é um ponto no espaço, com três coordenadas. É a unidade de que tudo é feito.

Triângulo é o que se forma com três vértices, e é a única superfície que a placa de vídeo sabe desenhar. Um cubo aparentemente feito de seis quadrados é, por dentro, doze triângulos.

Geometria é o conjunto de vértices e triângulos que define a forma — e nada mais. Uma geometria não tem cor, não tem posição no mundo e não aparece na tela sozinha. CylinderGeometry é a receita de um cilindro, e não um cilindro na cena.

Material define como a luz se comporta ao encontrar a superfície: que cor devolve, quão áspera ou polida ela é, se é transparente. Também não aparece sozinho.

Malha, em inglês Mesh, é o casamento dos dois: uma geometria mais um material, agora sim um objeto que existe na cena, com posição, rotação e escala próprias. A mesma geometria e o mesmo material podem servir a muitas malhas ao mesmo tempo, e essa partilha é o principal recurso de economia (o orçamento de desempenho).

Normal é a direção para a qual um pedaço de superfície aponta. A biblioteca a calcula sozinha nas geometrias prontas, e é ela que decide o quanto de luz aquele pedaço recebe. Superfície com a normal invertida fica preta ou desaparece, e é isso que MeshNormalMaterial existe para revelar.

Coordenada de textura, chamada UV, é o par de números que diz qual ponto de uma imagem cai sobre qual ponto da superfície. Também vem pronta nas geometrias da biblioteca; passa a importar quando o modelo vem de fora e a imagem aparece esticada.

Cena é a raiz da árvore de objetos. Tudo o que deve ser desenhado precisa estar pendurado nela, direta ou indiretamente. Objeto criado e não acrescentado à cena não aparece — e não gera erro nenhum.

Eixos, unidades e sentido de giro

Convenção Valor Consequência prática
Unidade metro 0.4 é quarenta centímetros; altura de olho de pessoa em pé fica em torno de 1.6
+X para a direita —
+Y para cima chão é o plano y = 0; gravidade, quando houver, desce em -Y
+Z para trás, em direção a quem olha o objeto “à sua frente” tem z negativo
Frente de um objeto -Z local lookAt aponta o -Z do objeto para o alvo
Sentido de giro anti-horário, olhando do lado positivo do eixo para a origem ângulo positivo em rotation.y gira para a esquerda de quem olha de cima
Ângulo em rotation radiano Math.PI é meia volta; use MathUtils.degToRad(90) para não espalhar constantes

A unidade é a convenção mais cara de desrespeitar. O padrão WebXR entrega as posições do aparelho em metros, sempre, e não há como reconfigurá-lo. Uma cena escrita em centímetros funciona no desktop, onde a câmera é ajustada até parecer certa, e revela o problema no instante em que alguém entra no visor: a bancada tem o tamanho de um prédio, e a pessoa, o de um parafuso.

O -Z como frente surpreende, e é a fonte do modelo importado que aparece de costas. Não é escolha da biblioteca: vem da convenção de câmera adotada pela computação gráfica, em que a câmera olha ao longo do próprio -Z.

A cena mínima que roda

Três objetos e um laço. Todo o resto se acrescenta a isto.

import { PerspectiveCamera, Scene, WebGLRenderer } from 'three';

const cena = new Scene();
const camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
const renderer = new WebGLRenderer({ antialias: true });

renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);

camera.position.set(0, 1.6, 2);
camera.lookAt(0, 1, 0);

renderer.setAnimationLoop(() => {
  renderer.render(cena, camera);
});
Peça Papel
Scene a raiz da árvore. Não recebe parâmetro nenhum
PerspectiveCamera de onde se olha, e o quanto se enxerga (a câmera)
WebGLRenderer quem desenha, e quem conversa com a placa de vídeo (o renderizador)
renderer.domElement o elemento canvas que o renderizador criou; precisa entrar na página
renderer.render(cena, camera) desenha um quadro, uma vez
setAnimationLoop manda desenhar continuamente (o laço)

Quatro coisas que faltam nesse exemplo e que produzem “não aparece nada”: não há luz (as luzes), não há objeto acrescentado à cena (pôr um objeto na cena), o canvas pode ter altura zero por falta de estilo, e a câmera pode estar dentro do objeto que deveria enxergar.

A câmera em perspectiva, parâmetro a parâmetro

new PerspectiveCamera(fov, aspect, near, far)

Parâmetro O que é Padrão Se errar
fov campo de visão vertical, em graus: o ângulo de abertura entre a borda de cima e a de baixo do que a câmera enxerga 50 valor alto (acima de 90) distorce as bordas e produz sensação de lente olho de peixe; valor baixo (abaixo de 20) achata a profundidade e parece um zoom exagerado
aspect proporção largura ÷ altura da área de desenho 1 proporção diferente da do canvas estica ou achata a imagem inteira; é o erro que aparece ao redimensionar a janela sem atualizar este campo
near distância mínima, em metros, a partir da qual a câmera enxerga 0.1 valor pequeno demais consome a precisão de profundidade e faz superfícies próximas piscarem uma sobre a outra; objeto mais perto que este valor some
far distância máxima, em metros, até onde a câmera enxerga 2000 valor muito maior que o necessário desperdiça precisão de profundidade; objeto além dele desaparece sem aviso

O nome técnico do volume que esses quatro números delimitam é frustum: a pirâmide truncada que vai do plano near ao plano far, com abertura dada por fov e aspect. Só o que está dentro dela chega à tela.

A precisão de profundidade é uma razão, e não uma diferença. O que estraga o desenho é o quociente far / near, e não a distância entre os dois. Passar near de 0.1 para 0.01 piora dez vezes; aumentar far de 100 para 1000 piora dez vezes. Para uma cena de bancada, 0.05 e 50 são generosos.

import { PerspectiveCamera, WebGLRenderer } from 'three';

export function acompanharTamanho(
  camera: PerspectiveCamera,
  renderer: WebGLRenderer,
  tela: HTMLCanvasElement,
): void {
  const largura = tela.clientWidth;
  const altura = Math.max(1, tela.clientHeight);

  renderer.setSize(largura, altura, false);
  camera.aspect = largura / altura;
  camera.updateProjectionMatrix();
}

updateProjectionMatrix é obrigatório depois de mexer em fov, aspect, near ou far. A câmera guarda esses valores numa matriz calculada uma vez; sem a chamada, o campo novo fica no objeto e não chega ao desenho — o sintoma é a imagem continuar esticada depois de um redimensionamento aparentemente tratado.

O terceiro argumento de setSize, aqui false, diz para não escrever largura e altura no estilo do canvas. Com true, a biblioteca fixa o tamanho em CSS e passa a brigar com o seu leiaute.

Dentro de uma sessão imersiva, esta câmera sai de cena: quem define posição, orientação e campo de visão é o aparelho, e a câmera em uso passa a ser a de renderer.xr.getCamera(). Mexer em fov durante a sessão não tem efeito, e é o que se espera — o campo de visão ali é uma característica física do visor.

Câmera ortográfica

new OrthographicCamera(left, right, top, bottom, near, far)

Parâmetro O que é Padrão Se errar
left, right as bordas esquerda e direita do volume visível, em unidades de mundo — e não em pixels -1, 1 os padrões -1 e 1 abrem uma janela de dois metros: numa cena de bancada, a peça inteira cabe num canto e parece minúscula
top, bottom as bordas superior e inferior 1, -1 largura e altura fora da proporção da área de desenho esticam a imagem — aqui não há aspect para corrigir isso, e a conta é sua
near, far os mesmos limites de profundidade da câmera em perspectiva 0.1, 2000 vale a mesma razão far / near; e como aqui não há perspectiva, objeto atrás do plano near some sem ficar maior antes

Aqui não há fov, porque não há perspectiva: dois objetos do mesmo tamanho aparecem do mesmo tamanho, perto ou longe. Serve para planta baixa, minimapa, vista técnica e painel de interface medido em unidades de mundo. Não serve para o que se vê dentro do visor — a visão humana tem perspectiva, e uma cena ortográfica em estereoscopia não fecha.

O renderizador, opção por opção

new WebGLRenderer(parâmetros?)

Opção O que é Padrão Se errar
canvas o elemento onde desenhar a biblioteca cria um omitido, o canvas novo nasce fora da página: sem appendChild, o programa roda inteiro e a tela fica em branco, sem erro
antialias suaviza as bordas serrilhadas das formas false desligado, toda aresta diagonal aparece escadeada — é o defeito visual mais notado, e o mais barato de corrigir
alpha permite que o fundo do canvas seja transparente false é o que produz a tela preta em realidade aumentada: sem ele o fundo é opaco e cobre a imagem da câmera, com o programa perfeitamente funcional
premultipliedAlpha como o canal de transparência se combina com a cor true desligado sem que a textura esteja no mesmo convênio, bordas transparentes ganham um halo claro em volta
stencil reserva o buffer de estêncil, usado em recorte e máscara false ligado sem uso, gasta memória de vídeo por quadro; desligado com recorte em uso, a máscara simplesmente não recorta
depth reserva o buffer de profundidade, que decide o que fica na frente true desligado, os objetos se desenham na ordem de inserção e o que está atrás aparece na frente
preserveDrawingBuffer mantém o desenho após o quadro, permitindo capturá-lo false desligado, toDataURL no canvas devolve imagem em branco; ligado sem necessidade, custa uma cópia do quadro inteiro
powerPreference pede ao navegador a placa dedicada ou a econômica 'default' em portátil com duas placas, o padrão costuma cair na integrada — e a queda de quadros aparece só no aparelho de quem usa
failIfMajorPerformanceCaveat recusa criar o contexto se só houver renderização por software false desligado, a página abre e roda a dois quadros por segundo em vez de avisar que aquela máquina não serve
Ajuste depois de criado O que faz
setSize(l, a, atualizarEstilo?) define a resolução de desenho
setPixelRatio(n) quantos pixels reais por pixel de CSS; limite a 2 (o orçamento de desempenho)
setClearColor(cor, alfa?) a cor com que o quadro começa. Em realidade aumentada, deixe o alfa em 0
outputColorSpace espaço de cor da saída; o padrão já é o adequado para tela
shadowMap.enabled liga o cálculo de sombras (as luzes)
xr.enabled liga o modo de realidade estendida (entrar em realidade virtual)
setAnimationLoop(f) instala o laço (o laço)
dispose() libera os recursos do renderizador

Nunca crie mais de um renderizador por página. Cada um abre um contexto de vídeo próprio, e o navegador impõe um limite pequeno de contextos simultâneos: o sintoma é a cena mais antiga apagar sozinha quando a nova nasce.

O laço e a passagem do tempo

Use renderer.setAnimationLoop, e não requestAnimationFrame da janela. Dentro de uma sessão imersiva, quem decide a hora do próximo quadro é o aparelho, com o ritmo dele; o relógio da janela para de servir e a cena congela no visor enquanto continua andando na tela.

import { Clock, PerspectiveCamera, Scene, WebGLRenderer } from 'three';

export function iniciarLaco(
  renderer: WebGLRenderer,
  cena: Scene,
  camera: PerspectiveCamera,
  aCadaQuadro: (delta: number, quadro: XRFrame | undefined) => void,
): void {
  const relogio = new Clock();

  renderer.setAnimationLoop((_instante: number, quadro?: XRFrame) => {
    aCadaQuadro(relogio.getDelta(), quadro);
    renderer.render(cena, camera);
  });
}
Peça O que é
primeiro argumento o instante do quadro, em milissegundos desde o início da página
segundo argumento o quadro da sessão, presente só dentro de realidade virtual ou aumentada; é dele que saem pose e teste de contato
new Clock(autoStart?) relógio auxiliar; autoStart (padrão true) o dispara na primeira leitura
relogio.getDelta() segundos decorridos desde a leitura anterior
relogio.getElapsedTime() segundos desde o início
setAnimationLoop(null) desliga o laço

Multiplique todo movimento pelo delta. Um giro escrito como objeto.rotation.y += 0.01 anda ao dobro da velocidade num aparelho de 120 quadros por segundo em relação a um de 60. Escrito como objeto.rotation.y += 0.6 * delta, anda a 0,6 radiano por segundo em qualquer um. A regra vale para deslocamento, giro, escala e contagem regressiva.

Chame getDelta uma vez por quadro, e só uma. Cada chamada zera o cronômetro interno; duas chamadas no mesmo quadro fazem a segunda devolver quase zero, e o sintoma é um movimento que anda pela metade sem razão aparente.

Pôr um objeto na cena

Sempre os mesmos três passos: uma geometria, um material, e uma malha que junta os dois.

import { CylinderGeometry, Mesh, MeshStandardMaterial, Scene } from 'three';

export function acrescentarEixo(cena: Scene): Mesh {
  const forma = new CylinderGeometry(0.02, 0.02, 0.4, 24);
  const acabamento = new MeshStandardMaterial({ color: 0x8899aa, roughness: 0.4 });
  const eixo = new Mesh(forma, acabamento);

  eixo.name = 'eixo';
  eixo.position.set(0, 0.95, 0);
  cena.add(eixo);
  return eixo;
}

new Mesh(geometria, material) — ambos opcionais, e omiti-los produz uma malha com forma e material padrão que serve para nada além de teste. O terceiro passo, cena.add, é o que costuma faltar: sem ele o objeto existe na memória, não gera erro e não aparece.

Uma malha aceita um material ou um arranjo de materiais. O arranjo só faz sentido em geometrias que declaram grupos — BoxGeometry declara seis, um por face —, e é assim que se faz um dado com seis números diferentes com uma só malha.

As formas prontas, parâmetro a parâmetro

Antes das tabelas, três palavras que se repetem em todas elas.

Segmento é em quantos pedaços a superfície é dividida para ser aproximada por triângulos. Um círculo com 8 segmentos é um octógono; com 64, parece redondo. Segmento é orçamento, não qualidade: dobrar o número dobra os triângulos, e em visor a conta se paga duas vezes, uma por olho.

Theta e phi são os dois ângulos com que se recorta uma forma de revolução. Nas geometrias abaixo, thetaStart diz onde o recorte começa e thetaLength diz quanto ele varre — ambos em radianos. Um thetaLength de Math.PI dá meia forma, e é assim que se faz uma cúpula, uma calha ou um corte de demonstração sem modelador.

Aberto ou tampado (openEnded) decide se as extremidades de cilindro e cone recebem tampa. Aberto, o objeto fica oco e mostra o interior — que só será visível se o material desenhar as duas faces (os materiais).

Caixa

new BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)

Parâmetro O que é Padrão Se errar
width comprimento das arestas paralelas ao eixo X, em metros 1 escala errada é quase sempre unidade trocada, não erro de código
height comprimento das arestas paralelas ao eixo Y, em metros 1 a caixa nasce centrada na origem, e metade desta altura fica abaixo do ponto onde ela foi posicionada: é o que produz o objeto meio enterrado no chão
depth comprimento das arestas paralelas ao eixo Z, em metros 1 omitido junto com os outros dois, sai um cubo de um metro — grande demais para bancada, e o reflexo é culpar a câmera
widthSegments em quantas faces a largura se divide 1 acima de 1 só ajuda se a caixa for deformada ou receber luz por vértice
heightSegments em quantas faces a altura se divide 1 subir os três segmentos multiplica os triângulos entre si: 10 × 10 × 10 dá seiscentas faces onde bastavam doze
depthSegments em quantas faces a profundidade se divide 1 mesmo custo multiplicativo; numa caixa que não vai ser deformada, qualquer valor acima de 1 é gasto sem efeito visível

A caixa nasce centrada na origem: uma caixa de 0,4 de altura vai de -0,2 a +0,2 em Y. Para apoiá-la no chão, position.y recebe metade da altura. Isto vale para quase todas as geometrias, e é a causa do objeto “meio enterrado” no piso.

Esfera

new SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength)

Parâmetro O que é Padrão Se errar
radius raio, em metros 1 o padrão 1 dá uma esfera de dois metros de diâmetro; numa cena de bancada isso enche a sala, e o reflexo é procurar o defeito na câmera
widthSegments segmentos horizontais, ao redor do equador. Mínimo 3 32 abaixo de 12 a silhueta fica visivelmente poligonal
heightSegments segmentos verticais, do polo ao polo. Mínimo 2 16 valores baixos aparecem como faixas horizontais no sombreado, mesmo com widthSegments alto: os dois precisam subir juntos
phiStart ângulo horizontal inicial, em radianos 0 girar aqui move junto a costura da textura; para só orientar a esfera, gire a malha, não a geometria
phiLength quanto varre na horizontal 2π (volta inteira) π produz meia esfera cortada na vertical
thetaStart ângulo vertical inicial, em radianos 0 (polo norte) conta do polo norte para baixo, e não do equador: π/2 começa na linha do equador, não no topo
thetaLength quanto varre na vertical π (polo a polo) π/2 produz uma cúpula

O padrão 32 × 16 já é generoso para uma peça pequena. Uma esfera de dois centímetros com 64 × 32 gasta oito vezes mais triângulos para uma diferença que ninguém enxerga a um metro.

Cilindro

new CylinderGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength)

Parâmetro O que é Padrão Se errar
radiusTop raio no topo, em metros 1 0 aqui produz um cone, sem erro nem aviso — é a confusão mais comum quando se queria apenas um tubo fino
radiusBottom raio na base 1 valores diferentes produzem um tronco de cone; 0 no topo produz um cone
height altura, em metros 1 o cilindro nasce centrado na origem e em pé, com o eixo em Y: metade da altura fica abaixo do ponto onde ele foi posicionado
radialSegments segmentos ao redor da circunferência 32 abaixo de 8 o cilindro vira prisma visível
heightSegments anéis de faces ao longo da altura 1 maior que 1 só serve para deformar o cilindro depois
openEnded true deixa as extremidades sem tampa false aberto e com material de face única, o interior fica invisível
thetaStart ângulo inicial da varredura, em radianos 0 (posição de três horas) o zero fica em +X, não em +Z: uma calha que deveria abrir para a frente abre para o lado
thetaLength ângulo total varrido, em radianos 2π π produz meio cilindro — uma calha

O cilindro nasce em pé, com o eixo em Y. Para deitá-lo ao longo de Z, gire -π/2 em X. Esta é a geometria de eixo, pino, parafuso, tubo e disco: um disco é um cilindro de altura pequena.

Cone

new ConeGeometry(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength)

Parâmetro O que é Padrão Se errar
radius raio da base, em metros 1 o padrão 1 dá um cone de dois metros de largura; num marcador de direção de bancada, o valor de trabalho é centésimo disso
height altura, em metros 1 a ponta nasce em +Y, e a altura conta a partir do centro: o cone fica meio enterrado se position.y não somar height / 2
radialSegments segmentos ao redor da base 32 abaixo de 8 a base vira polígono visível; acima de 48, num marcador pequeno, gasta triângulo em silhueta que ninguém enxerga
heightSegments anéis ao longo da altura 1 maior que 1 só serve para deformar o cone depois; sem deformação, cada anel a mais é custo puro
openEnded true deixa a base sem tampa false aberto e com material de face única, o interior fica invisível e o cone parece oco por dentro
thetaStart ângulo inicial da varredura, em radianos 0 (posição de três horas) girar aqui em vez de girar a malha desalinha a costura do cone em relação ao resto da peça
thetaLength ângulo total varrido, em radianos 2π menor que a volta produz uma fatia — e a face do corte só aparece com material de face dupla

É o cilindro com radiusTop igual a zero, escrito de forma mais curta. Serve de seta, ponteiro e marcador de direção — com a ponta em +Y, e portanto girado para apontar aonde se quer.

Cápsula

new CapsuleGeometry(radius, length, capSegments, radialSegments)

Parâmetro O que é Padrão Se errar
radius raio do corpo e das calotas, em metros 1 entra duas vezes na altura total, uma por calota; contar só o length produz a cápsula um raio mais alta do que se pretendia de cada lado
length comprimento apenas do trecho reto, sem as calotas 1 pedir length igual à altura desejada dá uma cápsula alta demais — a conta é length + 2 × radius
capSegments segmentos de curva de cada calota 4 o padrão 4 deixa a calota visivelmente facetada de perto, que é justamente a distância de uso quando ela aproxima um dedo ou uma mão
radialSegments segmentos ao redor da circunferência 8 8 é baixo para uma cápsula vista de perto: a silhueta sai octogonal. Para volume de colisão, que não se desenha, 8 sobra

A altura total é length + 2 × radius, e esquecer as calotas nessa conta é o que produz a cápsula maior do que o pretendido. É a forma usada como volume aproximado de braço, dedo e corpo em teste de contato.

Plano

new PlaneGeometry(width, height, widthSegments, heightSegments)

Parâmetro O que é Padrão Se errar
width largura, ao longo de X, em metros 1 um chão de 1 × 1 é um tapete de um metro: a pessoa dá dois passos e sai dele, e o efeito no visor é cair no vazio
height altura, ao longo de Y, em metros 1 o plano nasce em pé; usado como chão sem o giro de -π/2 em X, ele vira uma parede a um metro do rosto
widthSegments divisões na largura 1 deixado em 1, o plano tem quatro vértices: qualquer deformação em terreno ou pano não tem onde acontecer, e a superfície continua reta
heightSegments divisões na altura 1 um plano de 1 × 1 segmento tem quatro vértices: mapa de deslocamento aplicado sobre ele não faz absolutamente nada

O plano nasce em pé, no plano XY, encarando +Z. Para usá-lo como chão, gire -π/2 em X. E como ele tem uma face só, visto por trás desaparece: um chão que some quando a pessoa se abaixa é um plano com material de face única (os materiais).

Círculo

new CircleGeometry(radius, segments, thetaStart, thetaLength)

Parâmetro O que é Padrão Se errar
radius raio, em metros 1 o círculo nasce em pé, no plano XY, como o plano — e some visto por trás com material de face única
segments número de triângulos que aproximam o disco. Mínimo 3 32 abaixo de 12 o disco vira polígono à vista; com 3 sai um triângulo, e o resultado não é erro nem aviso
thetaStart ângulo inicial, em radianos 0 (posição de três horas) um indicador de progresso que deveria começar no topo começa à direita: o zero fica em +X, não em +Y
thetaLength ângulo varrido, em radianos 2π menor que a volta produz uma fatia de pizza — que é como se desenha progresso plano dentro da cena, e é o uso mais comum deste parâmetro

Com thetaLength menor que a volta inteira, sai uma fatia de pizza — que é como se desenha um indicador de progresso plano dentro da cena.

Anel

new RingGeometry(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength)

Parâmetro O que é Padrão Se errar
innerRadius raio do furo 0.5 maior que o externo produz geometria degenerada, sem erro
outerRadius raio externo, em metros 1 a espessura visível do anel é a diferença entre os dois raios; próximos demais, a mira some a um metro de distância
thetaSegments segmentos ao redor; mais deixa o anel mais redondo. Mínimo 3 32 abaixo de 16 a mira de superfície aparece como polígono, e num anel fino a falha é mais notada que numa esfera
phiSegments anéis concêntricos entre os dois raios. Mínimo 1 1 subir aqui não deixa o anel mais redondo — quem faz isso é thetaSegments; só acrescenta triângulos
thetaStart ângulo inicial, em radianos 0 (posição de três horas) um arco de progresso que deveria abrir no topo abre à direita
thetaLength ângulo varrido, em radianos 2π menor que a volta produz um arco

É a mira de superfície de quase todo exemplo de realidade aumentada: um anel plano, girado -π/2 em X para deitar no chão, posicionado onde o teste de contato encontrou superfície (entrar em realidade aumentada).

Toro e nó de toro

new TorusGeometry(radius, tube, radialSegments, tubularSegments, arc)

Parâmetro O que é Padrão Se errar
radius do centro do toro ao centro do tubo, em metros 1 o diâmetro externo é 2 × (radius + tube), e não 2 × radius: pedir o raio do furo aqui dá um toro maior do que o pretendido
tube raio do próprio tubo, em metros 0.4 igual ou maior que radius fecha o furo e a superfície se autointersecta — sai uma forma dobrada sobre si, sem erro
radialSegments segmentos na seção do tubo 12 o padrão 12 já mostra facetas num tubo grosso visto de perto
tubularSegments segmentos ao longo do anel 48 é o que decide se o anel parece redondo; abaixo de 24 a volta fica poligonal
arc ângulo total do anel, em radianos 2π menor que a volta produz um arco — e as duas extremidades ficam abertas, invisíveis com material de face única

new TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q) acrescenta p (quantas voltas o traçado dá em torno do eixo de simetria, padrão 2) e q (quantas voltas dá em torno do círculo interno, padrão 3). É a peça com que se demonstra a biblioteca: milhares de triângulos, forma reconhecível, nenhuma utilidade estrutural.

Arestas

new EdgesGeometry(geometry, thresholdAngle)

Parâmetro O que é Padrão Se errar
geometry a geometria de origem, de onde as arestas serão extraídas null ela continua sendo usada pela malha original; descartá-la depois de extrair as arestas apaga as duas
thresholdAngle em graus: uma aresta só é desenhada se o ângulo entre as normais das duas faces vizinhas passar deste valor 1 valor baixo desenha toda subdivisão interna e produz uma bola de linhas; valor alto some com as arestas curvas

Esta geometria não tem superfície: ela só produz as linhas, e precisa de LineSegments com um material de linha para aparecer. É como se mostra a estrutura de uma peça sem escondê-la atrás do próprio volume.

import { BoxGeometry, EdgesGeometry, LineBasicMaterial, LineSegments } from 'three';

export function contorno(): LineSegments {
  const caixa = new BoxGeometry(0.3, 0.2, 0.1);
  return new LineSegments(
    new EdgesGeometry(caixa, 15),
    new LineBasicMaterial({ color: 0x33ddaa }),
  );
}

thresholdAngle de 15 graus é o valor de trabalho: mostra os cantos reais de uma peça e ignora a subdivisão de superfícies curvas.

O que toda geometria oferece

Membro O que faz
parameters os valores com que ela foi criada, só de leitura. Mudá-los depois não altera a forma
translate(x, y, z) desloca os vértices, mudando onde fica a origem do objeto
rotateX/Y/Z(rad) gira os vértices de vez; é como se deita um plano na criação, sem tocar na malha
scale(x, y, z) escala os vértices de vez
center() recentra a geometria na origem
computeBoundingBox() calcula a caixa envolvente, exposta em boundingBox
computeVertexNormals() recalcula normais; necessário depois de mexer nos vértices à mão
dispose() libera a memória de vídeo (o orçamento de desempenho)

A diferença entre geometria.translate(0, 1, 0) e malha.position.y = 1 importa: o primeiro move os vértices e muda o ponto em torno do qual o objeto gira; o segundo move o objeto inteiro e preserva o centro de giro. Uma tampa que gira em torno do próprio centro, quando deveria girar na dobradiça, é este caso.

Materiais

Todos recebem um objeto de opções no construtor, e todas as opções são também propriedades que se pode mudar depois.

Material O que é Quando usar
MeshStandardMaterial modelo fisicamente plausível, com aspereza e metalicidade o padrão de trabalho
MeshBasicMaterial ignora a luz por completo: mostra a cor pura mira, contorno, marcador, painel — o que precisa ter sempre a mesma cor
MeshPhysicalMaterial o padrão mais verniz, transmissão, espessura e iridescência vidro e verniz; é o mais caro de todos
MeshNormalMaterial pinta cada ponto pela direção da normal depuração: revela face invertida e normal errada
LineBasicMaterial cor de linhas arestas e traçados

As opções comuns a todos

Opção O que é Padrão Se errar
color cor base, como 0x8899aa ou '#8899aa' branco cor escura com pouca luz vira preto indistinguível
opacity opacidade, de 0 (invisível) a 1 1 sem transparent: true não tem efeito nenhum
transparent liga o tratamento de transparência false ligado sem necessidade, ordena o objeto na fila de transparentes e pode fazê-lo desenhar por cima do que deveria estar na frente
alphaTest descarta o pixel cuja opacidade for menor que este valor 0 em 0 nada é descartado, e uma folhagem recortada precisa de transparent, que a põe na fila dos transparentes e faz o recorte se desenhar por cima do que estava na frente
side quais faces desenhar: FrontSide, BackSide, DoubleSide FrontSide plano visto por trás desaparece; DoubleSide custa o dobro de preenchimento
visible desenha ou não true esconder por aqui não desativa o teste de raio: o objeto invisível continua sendo clicado, e o clique parece acertar o nada
depthTest respeita quem está na frente true desligado, o objeto atravessa tudo — o que é desejável em mira e em interface
depthWrite grava a própria profundidade true desligar ajuda em vidro e fumaça sobrepostos
wireframe desenha só as arestas dos triângulos false ferramenta de inspeção, não de acabamento
toneMapped participa do mapeamento de tons do renderizador true ligado, a cor que chega à tela não é a que foi escrita: um painel de interface em 0xff0000 sai alaranjado e desbotado

As opções do material padrão

Opção O que é Padrão Se errar
roughness aspereza da superfície, de 0 (espelho) a 1 (fosco) 1 0 sem mapa de ambiente devolve um preto espelhado sem nada para refletir
metalness o quanto a superfície é metal — fisicamente é 0 ou 1, sem meio-termo 0 metal (1) sem ambiente ao redor fica preto; é o erro mais comum com este material
emissive cor emitida pela própria superfície, independentemente de luz 0x000000, nenhuma no preto, o objeto depende inteiramente da luz da cena; e mesmo aceso ele não ilumina o que está em volta — quem esperava um abajur vê um objeto brilhante numa sala escura
emissiveIntensity multiplicador da emissão 1 mexer aqui com emissive no preto não faz nada: qualquer intensidade vezes preto é preto
map textura de cor (as texturas) null textura em espaço de cor errado deixa tudo lavado ou escuro demais
normalMap textura que finge relevo alterando as normais null relevo aparente sem custo de triângulo — e tratado como cor em vez de dado, o relevo sai torto
normalScale intensidade do relevo fingido, em Vector2 (1, 1) valor negativo inverte o sentido do relevo: a saliência vira reentrância, e a iluminação fica sutilmente errada
roughnessMap, metalnessMap mapas que variam aspereza e metal ponto a ponto null são multiplicados pelos valores escalares acima; com metalness: 0, o mapa de metal não tem efeito nenhum
aoMap oclusão ambiente pré-calculada null exige o segundo conjunto de coordenadas de textura; sem ele, não aparece
displacementMap desloca vértices de verdade null exige geometria subdividida; num plano de 1 × 1 segmento não faz nada
envMap ambiente refletido null é o que falta quando metal fica preto
flatShading desliga a suavização entre faces false ligado sem intenção, a esfera aparece facetada mesmo com muitos segmentos, e o reflexo é procurar o defeito na geometria

Metal preto é o sintoma mais frequente deste material. Com metalness: 1, a superfície não tem cor própria: ela devolve o que há em volta. Sem envMap e sem ambiente, não há o que devolver, e o resultado correto é preto. As saídas são baixar metalness, fornecer um mapa de ambiente, ou usar o material básico.

LineBasicMaterial aceita ainda linewidth, e é honesto saber que na maioria das plataformas ele é ignorado: a espessura fica em um pixel, por limitação do WebGL, e não da biblioteca. Linha grossa se faz com geometria de tubo, ou com os utilitários de linha dos complementos.

Material se compartilha: um só material para os cinquenta parafusos iguais, e não cinquenta materiais idênticos. Cada material distinto obriga o renderizador a trocar de estado, e a troca é justamente o que custa caro.

Luzes

Objeto com MeshStandardMaterial numa cena sem luz é um objeto preto. Esta é a causa mais comum de “programa funciona, tela escura” — e nada no console acusa.

Luz Assinatura O que faz
AmbientLight (color, intensity) acrescenta a mesma quantidade de luz a tudo, de todas as direções. Não produz volume nem sombra
HemisphereLight (skyColor, groundColor, intensity) luz de cima com a cor do céu e de baixo com a cor do chão; dá ambiente sem achatar
DirectionalLight (color, intensity) raios paralelos, como o sol. A direção é o que conta
PointLight (color, intensity, distance, decay) lâmpada: irradia igualmente para todos os lados a partir de um ponto
SpotLight (color, intensity, distance, angle, penumbra, decay) holofote: um cone de luz a partir de um ponto
Parâmetro O que é Padrão Se errar
color cor da luz, em hexadecimal 0xffffff luz colorida multiplica a cor do material; azul sobre amarelo dá quase preto
intensity força da luz 1 com o mapeamento de tons ligado, valores entre 1 e 3 são a faixa usual
skyColor / groundColor cores de cima e de baixo, na hemisférica 0xffffff iguais, a luz achata e vira ambiente puro; a diferença entre as duas é o que dá volume sem custo de sombra
distance alcance máximo, em metros; 0 significa sem limite 0 valor pequeno demais apaga a luz antes de ela chegar à cena
decay com que rapidez a luz enfraquece com a distância 2 2 é o valor fisicamente correto; menor achata a queda
angle abertura do cone do holofote, em radianos, no máximo π/2 π/3 acima do máximo, o cone deixa de fazer sentido
penumbra quanto da borda do cone é suavizada, de 0 a 1 0 0 produz uma borda dura e artificial
import { DirectionalLight, HemisphereLight, Scene } from 'three';

export function iluminar(cena: Scene): DirectionalLight {
  const ambiente = new HemisphereLight(0xdfe7ff, 0x35302b, 1.1);
  const principal = new DirectionalLight(0xffffff, 2.2);

  principal.position.set(1.5, 3, 1);
  principal.target.position.set(0, 0.9, 0);
  cena.add(ambiente, principal, principal.target);
  return principal;
}

A luz direcional não tem posição no sentido usual. O que conta é a direção que vai da posição dela até o alvo (target), e mudar (1.5, 3, 1) para (15, 30, 10) não altera nada, porque a direção é a mesma. E o alvo, quando movido, precisa estar na cena — sem isso, a matriz dele não é atualizada e a luz continua apontando para a origem.

Sombras se ligam à parte, e cobram caro:

Passo O que fazer
no renderizador renderer.shadowMap.enabled = true
na luz luz.castShadow = true
em quem projeta objeto.castShadow = true
em quem recebe objeto.receiveShadow = true
na qualidade luz.shadow.mapSize.set(1024, 1024)
no enquadramento ajustar luz.shadow.camera para cobrir só a área útil

Cada luz com sombra desenha a cena de novo, do ponto de vista dela, a cada quadro. Em visor, com dois olhos, uma única luz com sombra pode custar mais que toda a cena. Comece sem sombras e ligue-as quando a cena estiver fechada.

Mover, girar, redimensionar

Todo objeto de cena — malha, grupo, linha, luz, câmera — herda de Object3D e carrega os mesmos campos.

Campo Tipo O que é
position Vector3 deslocamento em relação ao pai, em metros
rotation Euler giro em radianos, aplicado na ordem XYZ por padrão
quaternion Quaternion o mesmo giro, em forma que se compõe sem trava de eixo
scale Vector3 fator multiplicativo; 1 é o tamanho original
up Vector3 qual direção é “para cima” nas contas de lookAt; padrão (0, 1, 0)
matrix Matrix4 a transformação local, calculada a partir dos quatro primeiros
matrixWorld Matrix4 a transformação acumulada com a de todos os pais

rotation e quaternion são duas vistas do mesmo giro: mexer num atualiza o outro. Não há como dessincronizá-los, e não existe escolher “usar um dos dois” — existe escolher por qual dos dois se escreve.

Vector3, o tipo mais usado de todos

new Vector3(x, y, z) — os três padrão 0.

Método O que faz Cuidado
set(x, y, z) escreve os três de uma vez —
copy(outro) copia os valores de outro vetor não guarda referência; é cópia
clone() devolve um vetor novo com os mesmos valores aloca — evite dentro do laço
add, sub, multiply operações componente a componente modificam o próprio vetor, e não devolvem um novo
addScaledVector(v, k) soma v multiplicado por k é a forma de aplicar velocidade vezes delta sem alocar
length() comprimento usa raiz quadrada
lengthSq() comprimento ao quadrado prefira ao comparar distâncias: dispensa a raiz
normalize() reduz a comprimento 1, preservando a direção vetor de comprimento zero vira (0,0,0), não erro
distanceTo(v) distância até outro ponto —
dot(v) produto escalar com vetores normalizados, é o cosseno do ângulo entre eles
cross(v) produto vetorial dá a perpendicular aos dois
applyQuaternion(q) gira o vetor —
applyMatrix4(m) aplica uma transformação completa —
setFromMatrixPosition(m) extrai só a posição de uma matriz é como se lê onde um objeto está no mundo

Quase todo método muda o objeto em que é chamado. a.add(b) altera a; quem espera um terceiro vetor de volta escreve a.clone().add(b). Confundir os dois é a origem do objeto que deriva alguns centímetros por quadro até sumir.

Rotação: quando rotation, quando quaternion

import { MathUtils, Object3D, Quaternion, Vector3 } from 'three';

export function girarDeVez(peca: Object3D): void {
  peca.rotation.set(0, MathUtils.degToRad(30), 0);
}

export function acumularGiro(peca: Object3D, eixo: Vector3, graus: number): void {
  const passo = new Quaternion().setFromAxisAngle(eixo.normalize(), MathUtils.degToRad(graus));
  peca.quaternion.multiply(passo);
}
Situação O que usar
orientação declarada uma vez, legível rotation, com MathUtils.degToRad
giro que se acumula a cada quadro quaternion.multiply
interpolar entre duas orientações quaternion.slerp(destino, t)
copiar a orientação de outro objeto quaternion.copy(outro.quaternion)
apontar para um ponto lookAt(ponto), que recebe coordenada de mundo

O problema que o quaternião resolve tem nome: trava de eixo. Ao compor giros em rotation, chega um ponto em que dois dos três eixos passam a girar em torno da mesma direção, e um grau de liberdade se perde — o sintoma é o objeto travar num plano ou dar um salto brusco no meio de um movimento contínuo.

MathUtils.degToRad(g) e MathUtils.radToDeg(r) existem para que ninguém espalhe Math.PI / 180 pelo código. MathUtils.clamp(v, min, max) e MathUtils.lerp(a, b, t) completam o que se usa no dia a dia.

Escala

scale.setScalar(0.5) reduz à metade nos três eixos. Escala não uniforme — scale.set(2, 1, 1) — deforma as normais e estraga a iluminação; quando for inevitável, prefira gerar a geometria já com as medidas certas.

Escala 0 em qualquer eixo achata o objeto e produz matriz não inversível: o objeto some, o teste de raio deixa de acertá-lo e nenhum erro é emitido. Para esconder, use visible = false.

Agrupar: o que é local e o que é do mundo

Group é um objeto sem forma própria que existe para carregar outros. Mover o grupo move tudo dentro dele, e cada filho mantém a posição relativa ao pai.

import { Group, Mesh, Object3D, Scene, Vector3 } from 'three';

export function montarConjunto(cena: Scene, corpo: Mesh, tampa: Mesh): Group {
  const conjunto = new Group();

  conjunto.name = 'conjunto';
  conjunto.add(corpo, tampa);
  tampa.position.set(0, 0.12, 0);
  conjunto.position.set(0, 0.9, -0.3);
  cena.add(conjunto);
  return conjunto;
}

export function ondeEstaNoMundo(alvo: Object3D): Vector3 {
  return alvo.getWorldPosition(new Vector3());
}
Operação O que faz Cuidado
pai.add(a, b, …) anexa um ou vários um objeto tem um pai; add o remove do anterior
pai.remove(a) desanexa, sem destruir não libera memória de vídeo (o orçamento de desempenho)
pai.attach(a) anexa preservando a posição no mundo é o que se usa ao pegar uma peça com a mão
pai.clear() remove todos os filhos —
obj.traverse(fn) percorre o objeto e todos os descendentes inclui o próprio objeto
obj.traverseVisible(fn) percorre só o que está visível —
obj.getObjectByName(n) procura por name na subárvore devolve undefined se não achar
obj.getWorldPosition(v) escreve no vetor a posição no mundo precisa de um Vector3 de destino
obj.getWorldQuaternion(q) escreve no quaternião a orientação acumulada no mundo precisa de um Quaternion de destino
obj.localToWorld(v) / worldToLocal(v) converte um ponto entre os dois sistemas modifica o vetor recebido
obj.visible esconde o objeto e os filhos não desativa o teste de raio, que continua acertando
obj.userData espaço livre para dados seus sobrevive à clonagem

A diferença entre add e attach é a que mais custa a aparecer. add preserva as coordenadas locais, e o objeto salta para outro ponto do mundo. attach recalcula as locais para que ele permaneça exatamente onde está. Pegar um objeto com o controle é attach; montá-lo num conjunto pela origem é add.

Matrizes se atualizam antes do desenho, não na hora da escrita. Quem lê matrixWorld ou getWorldPosition logo depois de mudar position, ainda no mesmo quadro, lê o valor anterior. A saída é chamar obj.updateMatrixWorld(true) antes da leitura.

Saber o que a pessoa apontou

Raycaster lança um raio e devolve o que ele atravessa, em ordem de distância.

new Raycaster(origin, direction, near, far)

Parâmetro O que é Padrão Se errar
origin ponto de onde o raio parte, em coordenada de mundo (0,0,0) passar coordenada local de um objeto que tem pai faz o raio sair de outro lugar da cena, e o teste não acerta nada
direction direção do raio; precisa estar normalizada (0,0,-1) direção não normalizada produz distâncias erradas nos resultados
near ignora o que estiver mais perto que isto 0 não aceita negativo
far ignora o que estiver mais longe infinito limitar acelera o teste em cena grande
import { Camera, Object3D, Raycaster, Vector2, type Intersection } from 'three';

const raio = new Raycaster();
const ponto = new Vector2();

export function sobOCursor(
  evento: PointerEvent,
  tela: HTMLCanvasElement,
  camera: Camera,
  candidatos: Object3D[],
): Intersection | undefined {
  const caixa = tela.getBoundingClientRect();

  ponto.x = ((evento.clientX - caixa.left) / caixa.width) * 2 - 1;
  ponto.y = -((evento.clientY - caixa.top) / caixa.height) * 2 + 1;

  raio.setFromCamera(ponto, camera);
  return raio.intersectObjects(candidatos, true)[0];
}
Chamada O que faz
setFromCamera(ponto, camera) monta o raio que sai da câmera e passa pelo ponto da tela
intersectObject(obj, recursivo?) testa um objeto
intersectObjects(lista, recursivo?) testa vários, e devolve tudo ordenado por distância
ray.origin / ray.direction o raio em si, para montá-lo à mão (é o caso do controle XR)

As coordenadas de setFromCamera vão de -1 a 1, com o Y invertido em relação ao da página: -1 é a borda esquerda ou inferior, +1 a direita ou superior. O sinal de menos na segunda linha é essa inversão, e trocá-lo produz um clique que acerta o objeto espelhado na vertical.

O segundo argumento precisa ser true quase sempre. Um modelo carregado é um grupo com dezenas de malhas dentro; com false, o teste examina só o grupo, que não tem geometria, e não encontra nada.

Cada resultado traz:

Campo O que é
object a malha atingida — não o grupo, e sim a folha da árvore
distance distância da origem do raio até o ponto de contato, em metros
point o ponto de contato, em coordenada de mundo
face o triângulo atingido, com a normal dele
uv a coordenada de textura no ponto, quando existe

Dentro de sessão imersiva não há cursor, e o raio parte do controle:

import { Matrix4, Object3D, Raycaster } from 'three';

const temporaria = new Matrix4();

export function apontarComOControle(controle: Object3D, raio: Raycaster): void {
  temporaria.identity().extractRotation(controle.matrixWorld);
  raio.ray.origin.setFromMatrixPosition(controle.matrixWorld);
  raio.ray.direction.set(0, 0, -1).applyMatrix4(temporaria);
}

O -Z do controle é a direção para a frente, por convenção do padrão. Reaproveitar uma única Matrix4 declarada fora da função, como acima, evita alocar um objeto por quadro por controle — lixo suficiente para produzir engasgo perceptível em visor.

Carregar um modelo pronto

O formato é o glTF, de preferência na variante binária .glb, que traz malha, material, textura e animação num arquivo só.

import { Box3, Scene, Vector3 } from 'three';
import { GLTFLoader, type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';

export async function carregar(url: string, cena: Scene): Promise<GLTF> {
  const gltf: GLTF = await new GLTFLoader().loadAsync(url);
  const caixa = new Box3().setFromObject(gltf.scene);
  const tamanho = caixa.getSize(new Vector3());

  if (tamanho.y > 2) {
    gltf.scene.scale.setScalar(1 / tamanho.y);
  }
  cena.add(gltf.scene);
  return gltf;
}
Membro O que é
loadAsync(url) devolve promessa; é a forma curta
load(url, aoCarregar, aoProgredir, aoFalhar) com funções de retorno; use quando quiser barra de progresso
gltf.scene o grupo a acrescentar à cena
gltf.animations os clipes de animação (a animação do modelo)
gltf.cameras câmeras que vieram no arquivo, se houver
Ferramenta de medida O que faz
new Box3().setFromObject(obj) a caixa que envolve o objeto, já com todas as transformações internas
caixa.getSize(v) escreve as três dimensões no vetor
caixa.getCenter(v) escreve o centro
caixa.containsPoint(p) se um ponto está dentro
caixa.intersectsBox(outra) se duas caixas se tocam — é o teste de colisão barato

Medir antes de confiar. Modelo exportado em centímetros chega cem vezes maior; modelo com a origem no pé ou no centro muda onde ele encosta no chão. As duas coisas se descobrem com Box3, em três linhas, antes de alguém entrar no visor.

Onde pôr o arquivo. Com Vite, o que está na pasta pública é servido a partir da raiz do endereço, e o caminho no código é '/modelo.glb'. Arquivo referenciado por caminho relativo do código-fonte não chega ao pacote de produção — funciona em desenvolvimento e some depois da compilação.

Imagens, fotos e rótulos

import { CanvasTexture, MeshStandardMaterial, RepeatWrapping, TextureLoader } from 'three';

export function comFoto(url: string): MeshStandardMaterial {
  const textura = new TextureLoader().load(url);

  textura.wrapS = RepeatWrapping;
  textura.wrapT = RepeatWrapping;
  textura.repeat.set(2, 2);
  textura.anisotropy = 4;
  return new MeshStandardMaterial({ map: textura });
}

export function comRotulo(texto: string): MeshStandardMaterial {
  const tela = document.createElement('canvas');
  tela.width = 512;
  tela.height = 128;

  const pincel = tela.getContext('2d');
  if (pincel !== null) {
    pincel.fillStyle = '#101418';
    pincel.fillRect(0, 0, tela.width, tela.height);
    pincel.fillStyle = '#e8eef5';
    pincel.font = '48px sans-serif';
    pincel.fillText(texto, 24, 80);
  }
  return new MeshStandardMaterial({ map: new CanvasTexture(tela) });
}
Propriedade O que é Cuidado
wrapS / wrapT o que fazer fora do intervalo 0..1, na horizontal e na vertical para repeat funcionar, precisa ser RepeatWrapping
repeat quantas vezes a imagem se repete em cada direção —
offset deslocamento da imagem sobre a superfície é como se anima um painel de rolagem sem mexer na geometria
center / rotation ponto e ângulo de giro da imagem ângulo em radianos
anisotropy nitidez em superfícies vistas de raspão valores acima do máximo da placa são reduzidos em silêncio
colorSpace espaço de cor da imagem textura de cor pede espaço de cor de tela; mapa de dados, como o de normais, não
flipY inverte a imagem na vertical ao carregar o glTF já vem no sentido certo; inverter aqui estraga
needsUpdate avisa que o conteúdo mudou obrigatório depois de redesenhar o canvas de origem
dispose() libera a memória de vídeo —

Espaço de cor errado é o defeito silencioso das texturas. Uma foto tratada como dado linear aparece lavada; um mapa de normais tratado como cor produz relevo torto. A regra curta: o que representa cor recebe espaço de cor de tela; o que representa número — normais, aspereza, oclusão — não.

CanvasTexture é o caminho curto para texto dentro da cena: desenha-se no canvas com a interface de duas dimensões e usa-se o resultado como textura. É legível de perto e borra ao aproximar demais — para painel de interface dentro do ambiente, resolve o caso comum sem carregar biblioteca de tipografia tridimensional.

Tocar a animação que veio no modelo

import { AnimationMixer, LoopRepeat } from 'three';
import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';

export function tocarPrimeira(gltf: GLTF): ((delta: number) => void) | undefined {
  const primeira = gltf.animations[0];
  if (primeira === undefined) {
    return undefined;
  }
  const misturador = new AnimationMixer(gltf.scene);
  const acao = misturador.clipAction(primeira);

  acao.setLoop(LoopRepeat, Infinity);
  acao.timeScale = 1;
  acao.play();

  return (delta: number) => misturador.update(delta);
}
Peça O que é
new AnimationMixer(raiz) quem aplica as animações à árvore de objetos daquela raiz
clipAction(clipe) cria ou recupera a ação que toca aquele clipe
acao.play() / stop() inicia e para
acao.setLoop(modo, vezes) LoopOnce, LoopRepeat ou LoopPingPong
acao.timeScale velocidade; 2 toca ao dobro, -1 toca de trás para a frente
acao.fadeIn(s) / fadeOut(s) entra e sai suavemente, em segundos
acao.crossFadeTo(outra, s) troca de animação sem salto
acao.clampWhenFinished mantém a última pose ao terminar, em vez de voltar à primeira
misturador.update(delta) avança o tempo; sem isto, nada anda

O misturador não anda sozinho. Animação parada com o programa rodando é, quase sempre, o update(delta) esquecido — e o delta precisa vir do mesmo relógio do laço, em segundos.

As duas camadas, e por que a distinção economiza horas

Camada O que é Como aparece
WebXR Device API padrão do navegador. Não se instala e não tem versão de projeto do professor: ou o navegador a implementa, ou não navigator.xr, e os tipos com prefixo XR
three.js biblioteca instalada pelo projeto, substituível. Desenha a cena e conversa com a camada de baixo Scene, Mesh, WebGLRenderer, e o subobjeto renderer.xr

Quando o aparelho recusa alguma coisa, quem recusou foi a camada de baixo, e trocar de biblioteca não muda a resposta. Quando a cena aparece torta, escura ou vazia, quem desenhou foi a de cima. Boa parte do tempo perdido em depuração vai embora ao decidir, primeiro, de qual das duas é o problema.

Duas condições valem para tudo o que envolve a camada de baixo, e as duas falham sem mensagem de erro:

Condição Como se verifica O que acontece se faltar
contexto seguro window.isSecureContext o navegador não expõe a interface. A causa é o endereço servido, não o aparelho
interface presente navigator.xr é undefined ou não não há o que perguntar. Ausência de resposta não é resposta negativa

Servir por HTTPS é requisito da própria interface. Em rede local, o caminho usual é um certificado de desenvolvimento aceito no aparelho, ou um túnel que forneça endereço seguro.

Perguntar o que o aparelho suporta

export type Suporte = 'sim' | 'nao' | 'desconhecido';

export async function suporteDe(modo: XRSessionMode): Promise<Suporte> {
  const xr: XRSystem | undefined = navigator.xr;
  if (xr === undefined) {
    return 'desconhecido';
  }
  try {
    return (await xr.isSessionSupported(modo)) ? 'sim' : 'nao';
  } catch {
    return 'desconhecido';
  }
}
Modo O que é
'inline' a cena na própria página, sem visor, sem sessão imersiva
'immersive-vr' sessão imersiva que substitui o mundo
'immersive-ar' sessão imersiva que sobrepõe ao mundo visível

São três respostas, e não duas. Alguns navegadores rejeitam a promessa em vez de devolver falso — para um modo que não reconhecem, ou fora de contexto seguro. Tratar a rejeição como negativa transforma “não sei responder” em “este aparelho não serve”, e é assim que um relatório honesto vira um relatório confiante e errado.

Entrar em realidade virtual

O caminho curto, com o botão pronto dos complementos:

import { WebGLRenderer } from 'three';
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';

export function habilitarVr(renderer: WebGLRenderer): void {
  renderer.xr.enabled = true;
  renderer.xr.setReferenceSpaceType('local-floor');
  document.body.appendChild(VRButton.createButton(renderer));
}

VRButton.createButton(renderer, sessionInit?) devolve um elemento pronto que pede a sessão, mostra o motivo quando o aparelho não suporta e encerra ao sair. Enquanto o projeto está sendo montado, ele poupa a máquina de estados inteira.

Membro de renderer.xr O que faz
enabled liga o modo XR do renderizador; sem isto, nada acontece
isPresenting se está desenhando para o visor ou para a janela
getSession() a sessão em curso, ou nulo
setSession(s) entrega uma sessão aberta à mão
getReferenceSpace() o espaço em vigor
setReferenceSpace(e) troca o espaço — é como se faz locomoção (mover a pessoa)
setReferenceSpaceType(t) declara o tipo desejado antes de a sessão abrir
getCamera() a câmera que o aparelho está usando durante a sessão
getController(i) nó de cena no raio do controle de índice i
getControllerGrip(i) nó de cena no punho do mesmo controle
getHand(i) nó de cena da mão rastreada, quando o recurso existe
setFoveation(n) reduz a resolução na periferia, de 0 a 1, para ganhar desempenho

A ordem importa. setReferenceSpaceType precisa vir antes de a sessão ser entregue: a biblioteca pede o espaço ao produzir o primeiro quadro. Invertidas as duas linhas, a cena é registrada contra o espaço errado e o console não acusa nada.

Abrindo a sessão à mão, sem o botão:

export async function abrirSessao(
  modo: XRSessionMode,
  opcionais: readonly string[],
): Promise<XRSession> {
  const xr: XRSystem | undefined = navigator.xr;
  if (xr === undefined) {
    throw new Error('Este navegador não expõe a interface de realidade estendida.');
  }
  return xr.requestSession(modo, { optionalFeatures: [...opcionais] });
}
Campo de requestSession O que é Consequência
requiredFeatures recursos obrigatórios um item indisponível impede a sessão de abrir
optionalFeatures recursos desejáveis um item indisponível vira ausência observável, e a sessão abre
domOverlay o elemento de página a sobrepor à cena usado com o recurso dom-overlay
Membro de XRSession O que é
enabledFeatures o que a sessão de fato concedeu. É opcional no padrão: pode vir indefinido, e aí o estado é indeterminado, não negado
requestReferenceSpace(tipo) pede um espaço; rejeita quando o aparelho não o oferece
requestAnimationFrame(f) agenda o próximo quadro da sessão
requestHitTestSource(opções) cria a fonte de teste de contato (entrar em realidade aumentada)
inputSources as fontes de entrada ativas
visibilityState 'visible', 'visible-blurred' ou 'hidden' — o aparelho pode suspender sem encerrar
end() encerra por decisão do programa
evento 'end' avisa que a sessão terminou, inclusive quando quem terminou foi o usuário

A escolha entre obrigatório e opcional é assimétrica. Pedir demais como obrigatório troca uma ausência que se poderia relatar por uma sessão que não abre — e o usuário só vê um botão que não faz nada.

Espaço de referência: onde fica o zero do mundo

É a decisão mais cara de mudar depois, porque toda coordenada escrita na cena depende dela.

Espaço Onde fica a origem Quando usar
viewer na cabeça de quem observa último recurso; garantido pelo padrão em toda sessão
local num ponto arbitrário, fixo desde o início da sessão sentado, sem referência de piso
local-floor no chão do espaço físico, sob quem observa em pé, com o chão importando — o caso comum
bounded-floor no chão, mais os limites da área livre que o aparelho conhece quando se quer respeitar a área demarcada
unbounded sem fronteira declarada percursos longos, em que o erro acumulado precisa ser corrigido pelo aparelho

Com local-floor, um objeto em y = 0 fica no chão da sala, e a altura escrita no código é altura de verdade. Com local, o mesmo objeto nasce na altura em que a cabeça estava quando a sessão abriu — e a bancada aparece na altura do rosto.

const PREFERIDOS: readonly XRReferenceSpaceType[] = ['local-floor', 'local', 'viewer'];

export async function escolherEspaco(sessao: XRSession): Promise<XRReferenceSpaceType> {
  for (const tipo of PREFERIDOS) {
    try {
      await sessao.requestReferenceSpace(tipo);
      return tipo;
    } catch {
      continue;
    }
  }
  throw new Error('A sessão não concedeu espaço de referência algum.');
}

A rejeição é a resposta, e não a falha. Pedir um espaço que o aparelho não oferece é como se pergunta a ele o que oferece. A lista termina em viewer porque o padrão garante esse; assim o laço sempre termina com resposta.

Entrar em realidade aumentada

Duas diferenças de fundo em relação à realidade virtual: o fundo precisa ser ausência de pintura, para a câmera aparecer atrás, e a cena precisa descobrir onde estão as superfícies reais.

import { Mesh, MeshBasicMaterial, RingGeometry, WebGLRenderer } from 'three';
import { ARButton } from 'three/examples/jsm/webxr/ARButton.js';

export function habilitarAr(renderer: WebGLRenderer, painel: HTMLElement): Mesh {
  renderer.xr.enabled = true;
  document.body.appendChild(
    ARButton.createButton(renderer, {
      requiredFeatures: ['hit-test'],
      optionalFeatures: ['dom-overlay', 'anchors'],
      domOverlay: { root: painel },
    }),
  );

  const mira = new Mesh(
    new RingGeometry(0.06, 0.08, 32).rotateX(-Math.PI / 2),
    new MeshBasicMaterial({ color: 0x66ffcc }),
  );
  mira.visible = false;
  return mira;
}

Crie o renderizador com alpha: true e não pinte fundo opaco: cor de limpeza desenhada por cima da imagem da câmera é o que produz a tela preta em modo aumentado, com o programa perfeitamente funcional.

Recurso O que habilita Garantido?
hit-test lançar um raio contra as superfícies reais que o aparelho encontrou não
anchors prender um objeto a um ponto do mapa e deixar o aparelho corrigi-lo não
plane-detection receber os planos que o aparelho reconheceu não
dom-overlay uma camada de página por cima da cena, para botões e texto não
hand-tracking pose das mãos, sem controle não
light-estimation estimativa da luz do ambiente real não

Nenhum é garantido em aparelho nenhum — daí a assimetria entre obrigatório e opcional valer especialmente aqui.

O teste de contato precisa do quadro da sessão, e por isso mora dentro do laço:

import type { Object3D, WebGLRenderer } from 'three';

export function seguirSuperficie(
  renderer: WebGLRenderer,
  quadro: XRFrame | undefined,
  fonte: XRHitTestSource | undefined,
  mira: Object3D,
): void {
  const espaco = renderer.xr.getReferenceSpace();
  if (quadro === undefined || fonte === undefined || espaco === null) {
    return;
  }
  const primeiro = quadro.getHitTestResults(fonte)[0];
  if (primeiro === undefined) {
    mira.visible = false;
    return;
  }
  const pose = primeiro.getPose(espaco);
  if (pose !== undefined) {
    mira.visible = true;
    mira.matrix.fromArray(pose.transform.matrix);
    mira.matrix.decompose(mira.position, mira.quaternion, mira.scale);
  }
}
Peça O que é
requestHitTestSource({ space }) pede a fonte uma vez, ao abrir a sessão, contra o espaço do observador
frame.getHitTestResults(fonte) lê os contatos a cada quadro; lista vazia é normal
resultado.getPose(espaco) posição e orientação do contato no espaço dado; pode não vir
pose.transform.matrix a transformação como arranjo de dezesseis números
matrix.decompose(p, q, s) separa a matriz em posição, orientação e escala do objeto

O par fromArray mais decompose é a ponte entre as duas camadas: o padrão entrega matriz, a biblioteca trabalha com posição e quaternião.

Ler os controles

import type { WebGLRenderer, XRTargetRaySpace } from 'three';

export function ligarControle(renderer: WebGLRenderer, indice: number): XRTargetRaySpace {
  const controle: XRTargetRaySpace = renderer.xr.getController(indice);

  controle.addEventListener('selectstart', () => {
    controle.userData['acionado'] = true;
  });
  controle.addEventListener('selectend', () => {
    controle.userData['acionado'] = false;
  });
  controle.addEventListener('connected', () => {
    controle.visible = true;
  });
  controle.addEventListener('disconnected', () => {
    controle.visible = false;
  });
  return controle;
}
Evento Quando dispara
selectstart / selectend gatilho principal, ao acionar e ao soltar
select acionamento completo; em aparelho sem controle, equivale ao toque na tela
squeezestart / squeezeend aperto lateral, o mesmo par
squeeze aperto completo
connected / disconnected a fonte entrou ou saiu durante a sessão
Nó Onde fica Para que serve
getController(i) no raio de apontamento pendurar a linha de mira e montar o raio
getControllerGrip(i) no punho pendurar o modelo do controle
getHand(i) na mão rastreada articulações, quando o recurso existe

Fontes de entrada aparecem e desaparecem com a sessão aberta: o controle que dormiu, a mão que saiu de campo, o segundo controle que só foi ligado depois. Código que as enumera uma vez, na abertura, funciona na demonstração e falha na mesa de quem usa — os eventos connected e disconnected existem por isso.

Para desenhar o controle na mão da pessoa, XRControllerModelFactory monta o modelo que o próprio aparelho informa, e o resultado entra no nó de punho.

Mover a pessoa pelo ambiente

Não se move a câmera dentro de uma sessão: quem a controla é o pescoço de quem está lá. O que se move é o mundo, deslocando a origem do espaço de referência.

import type { WebGLRenderer } from 'three';

export function saltar(renderer: WebGLRenderer, dx: number, dz: number): boolean {
  const base = renderer.xr.getReferenceSpace();
  if (base === null) {
    return false;
  }
  const deslocado = base.getOffsetReferenceSpace(
    new XRRigidTransform({ x: -dx, y: 0, z: -dz }),
  );
  renderer.xr.setReferenceSpace(deslocado);
  return true;
}
Peça O que é
XRRigidTransform({x,y,z}, {x,y,z,w}?) um deslocamento com orientação opcional, em quaternião
getOffsetReferenceSpace(t) deriva um espaço novo, deslocado em relação ao atual
renderer.xr.setReferenceSpace(e) passa a usar o espaço derivado

O sinal é a parte que se erra. A transformação recebe onde fica a origem do espaço novo dentro do atual; as poses lidas no espaço novo saem, portanto, com o deslocamento invertido. Para a pessoa avançar dois metros, a origem nova recai dois metros atrás.

Fazer assim, em vez de mover a câmera, é o que mantém o registro entre o que o pescoço faz e o que a cena mostra — e é a diferença entre um deslocamento confortável e um enjoo em trinta segundos. Deslocamento instantâneo, em saltos, enjoa menos que deslizamento contínuo.

Orçamento de desempenho

Em visor, cada quadro é desenhado duas vezes, uma por olho, e o alvo é 72 quadros por segundo ou mais. O que passa despercebido no desktop aparece imediatamente ali.

Medida O que fazer Por quê
densidade de pixels setPixelRatio(Math.min(devicePixelRatio, 2)) sem teto, tela densa desenha quatro vezes mais pixels do que se enxerga
repetição de objetos cem parafusos iguais são um InstancedMesh, não cem malhas cada malha é uma ordem de desenho separada
material compartilhado um material para muitos objetos cada material distinto força troca de estado
segmentos de geometria reduzir onde a silhueta não denuncia triângulo desenhado duas vezes, uma por olho
sombras desligadas até que se prove necessário cada luz com sombra redesenha a cena inteira
alocação por quadro nada de new Vector3() dentro do laço o coletor de lixo produz engasgo periódico
descarte dispose() ao remover em definitivo remover da cena não libera memória de vídeo
foveação renderer.xr.setFoveation(0.5) reduz resolução na periferia, onde a visão não distingue
import { BoxGeometry, InstancedMesh, Matrix4, MeshStandardMaterial } from 'three';

export function campoDePecas(quantas: number): InstancedMesh {
  const conjunto = new InstancedMesh(
    new BoxGeometry(0.04, 0.04, 0.04),
    new MeshStandardMaterial({ color: 0xb0b6bd }),
    quantas,
  );
  const molde = new Matrix4();

  for (let i = 0; i < quantas; i += 1) {
    molde.setPosition((i % 10) * 0.06, 0.9, Math.floor(i / 10) * 0.06);
    conjunto.setMatrixAt(i, molde);
  }
  conjunto.instanceMatrix.needsUpdate = true;
  return conjunto;
}

new InstancedMesh(geometria, material, count) — o count é o número máximo de cópias, reservado de uma vez; para exibir menos, ajuste conjunto.count sem recriar o objeto. Depois de escrever as matrizes, instanceMatrix.needsUpdate = true é obrigatório, ou todas as cópias ficam empilhadas na origem.

dispose é o que libera memória de vídeo, e a ausência dele é o vazamento que faz o ambiente ficar lento depois de vinte minutos, sem sintoma antes disso. Descarte a geometria, o material e cada textura do material — e só quando ninguém mais os usa, já que são compartilháveis.

Quando não funciona

Sintoma Causa provável Onde confirmar
tela preta, sem erro no console falta luz, ou material padrão sem luz alguma as luzes
objeto preto, com luz na cena metalness: 1 sem mapa de ambiente o material padrão
não aparece nada, e o objeto existe faltou cena.add, ou está atrás da câmera, ou fora de near/far a câmera
a imagem estica ao redimensionar aspect desatualizado, ou falta updateProjectionMatrix a câmera
superfícies piscam uma sobre a outra razão far / near grande demais a câmera
o botão de entrar não aparece página fora de HTTPS, ou navegador sem navigator.xr as duas camadas
em realidade aumentada, só se vê preto falta alpha: true, ou há cor de limpeza opaca entrar em realidade aumentada
a cena inteira parece gigante modelo em centímetros, ou cena fora de metros eixos e unidades
a bancada nasce na altura do rosto espaço local onde se queria local-floor o espaço de referência
o objeto está meio enterrado no chão geometria centrada na origem; falta somar metade da altura a caixa
o chão some quando me abaixo plano com face única, visto por trás o plano
o clique acerta o objeto errado sinal do Y trocado, ou intersectObjects sem descer aos filhos saber o que a pessoa apontou
o clique não acerta nada em modelo carregado segundo argumento false saber o que a pessoa apontou
o objeto salta ao ser pego add onde o caso pedia attach agrupar
leio a posição e vem o valor antigo matriz de mundo ainda não atualizada no quadro agrupar
a animação do modelo não anda falta mixer.update(delta) a animação do modelo
o movimento corre mais rápido num aparelho movimento sem multiplicar por delta o laço
o movimento anda pela metade getDelta() chamado duas vezes no mesmo quadro o laço
a textura repetida não repete falta wrapS/wrapT em modo de repetição as texturas
o rótulo não atualiza falta needsUpdate na textura de canvas as texturas
todas as cópias na origem falta instanceMatrix.needsUpdate o orçamento de desempenho
lento depois de vinte minutos falta de dispose no que foi removido o orçamento de desempenho
a cena antiga apagou sozinha mais de um renderizador na mesma página o renderizador

Diante de qualquer sintoma, a primeira pergunta continua sendo a de as duas camadas: o problema está na camada que desenha ou na que fala com o aparelho? Erro de luz, escala e hierarquia é da de cima. Erro de sessão, espaço e recurso é da de baixo, e nenhuma mudança na cena o resolve.

Glossário

Termo O que é
anisotropia ajuste de nitidez de textura em superfície vista de raspão
aspereza o quanto uma superfície espalha a luz, em vez de refleti-la como espelho
buffer de profundidade memória que guarda a distância de cada pixel, e decide o que fica na frente
caixa envolvente a menor caixa alinhada aos eixos que contém um objeto
espaço de referência o sistema de coordenadas contra o qual as poses do aparelho são relatadas
frustum o volume em pirâmide truncada que a câmera enxerga
geometria conjunto de vértices e triângulos que define uma forma
glTF formato de troca de modelos tridimensionais; .glb é a variante binária
imersão propriedade do sistema: o quanto ele substitui os estímulos do mundo
malha geometria mais material, como objeto de cena
mapa de normais textura que finge relevo alterando a direção das normais
material descrição de como a luz se comporta na superfície
normal direção para a qual um pedaço de superfície aponta
oclusão ambiente escurecimento em cantos e frestas, onde a luz do ambiente chega menos
pose posição mais orientação de algo, num dado espaço de referência
presença resposta da pessoa: a sensação de estar ali
quaternião forma de representar rotação sem trava de eixo
registro alinhamento entre o virtual e o real, quando o real está visível
teste de contato consulta que descobre onde um raio encontra superfície real
trava de eixo perda de um grau de liberdade ao compor giros por ângulos de Euler
vértice ponto no espaço, unidade de que a geometria é feita