A Essência das Geometrias 3D
No Three.js, geometrias prontas como BoxGeometry ou SphereGeometry são, no fundo, abstrações de conjuntos de vértices e faces. Um cubo, por exemplo, é composto por 6 faces, onde cada face quadrada é formada por pelo menos dois triângulos. Cada triângulo, por sua vez, é definido por três pontos no espaço tridimensional.
Quanto maior o número de triângulos em uma malha (mesh), mais suave será a superfície, como no caso de esferas que parecem perfeitamente curvas mas são, na verdade, poliedros complexos.
Vetores Normais e Iluminação
Um vetor normal é um vetor tridimensional perpendicular à superfície de um plano. Em computação gráfica, as normais são cruciais para o cálculo de iluminação. Sem dados de normais, fontes de luz direcionais (como PointLight ou DirectionalLight) não interagem corretamente com o objeto, resutlando em uma renderização plana e sem profundidade.
As normais determinam como a luz "bate" e reflete na face, permitindo que o motor de renderziação diferencie áreas claras de áreas sombreadas.
Imlpementação Prática com BufferGeometry
Para criar uma geometria do zero, utilizamos a classe BufferGeometry. Ela é mais eficiente em termos de memória e processamento, pois envia os dados diretamente para a GPU.
// Inicialização do cenário básico
const cena = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderizador = new THREE.WebGLRenderer({ antialias: true });
renderizador.setSize(window.innerWidth, window.innerHeight);
renderizador.setClearColor(0xeeeeee);
document.body.appendChild(renderizador.domElement);
// Definição manual de vértices (Triângulos que formam faces)
const geometria = new THREE.BufferGeometry();
const listaVertices = new Float32Array([
-25, -25, 25, // Face frontal
25, -25, 25,
25, 25, 25,
-25, -25, 25,
25, 25, 25,
-25, 25, 25,
// ... repetir para outras faces
]);
// Adicionando os vértices à geometria
geometria.setAttribute('position', new THREE.BufferAttribute(listaVertices, 3));
// Configurando as Normais (apontando para o eixo Z positivo nesta face)
const listaNormais = new Float32Array([
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
]);
geometria.setAttribute('normal', new THREE.BufferAttribute(listaNormais, 3));
// Material e Mesh
const material = new THREE.MeshLambertMaterial({ color: 0x2196f3, side: THREE.DoubleSide });
const cuboCustom = new THREE.Mesh(geometria, material);
cena.add(cuboCustom);
// Luz para destacar a geometria
const luzPonto = new THREE.PointLight(0xffffff, 1);
luzPonto.position.set(50, 50, 100);
cena.add(luzPonto);
camera.position.z = 100;
function animar() {
requestAnimationFrame(animar);
cuboCustom.rotation.y += 0.01;
renderizador.render(cena, camera);
}
animar();
Otimização com Indexação de Vértices
Em um cubo padrão, muitos triângulos compartilham os mesmos vértices. Em vez de repetir as coordenadas no array de posições, podemos definir cada vértice único apenas uma vez e usar um array de índices para dizer ao Three.js em qual ordem conectá-los.
Isso reduz drasticamente o consumo de memória, especialmente em modelos complexos.
const geometriaIndexada = new THREE.BufferGeometry();
// Apenas 8 pontos definem os cantos de um cubo
const pontosUnicos = new Float32Array([
-10, -10, 10, // 0
10, -10, 10, // 1
10, 10, 10, // 2
-10, 10, 10, // 3
-10, -10, -10, // 4
10, -10, -10, // 5
10, 10, -10, // 6
-10, 10, -10 // 7
]);
// Mapeamento de quais pontos formam quais triângulos
const indicesVértices = new Uint16Array([
0, 1, 2, 0, 2, 3, // Frente
1, 5, 6, 1, 6, 2, // Direita
4, 0, 3, 4, 3, 7, // Esquerda
4, 5, 1, 4, 1, 0, // Base
3, 2, 6, 3, 6, 7, // Topo
5, 4, 7, 5, 7, 6 // Verso
]);
geometriaIndexada.setAttribute('position', new THREE.BufferAttribute(pontosUnicos, 3));
geometriaIndexada.setIndex(new THREE.BufferAttribute(indicesVértices, 1));
// Calcula as normais automaticamente com base na posição dos triângulos
geometriaIndexada.computeVertexNormals();
const malhaOtimizada = new THREE.Mesh(geometriaIndexada, material);
cena.add(malhaOtimizada);
Manipulando Geometrias
Uma vez definida a geometria, o Three.js permite aplicar transformações diretamente na estrutura de dados, o que altera as coordenadas dos vértices permanentemente (diferente de transformar o objeto Mesh):
geometria.scale(x, y, z): Altera a escala dos dados dos vértices.geometria.translate(x, y, z): Move a origem da geometria.geometria.rotateX(ângulo): Rotaciona a estrutura de dados no eixo X.