JavaScript em 3D: uma introdução ao Three.js

Olá Habr! Apresento a você a tradução do artigo "JavaScript in 3D: a Introduction to Three.js", de Bret Cameron.

1. Introdução


Three.js é uma ferramenta poderosa. Ajuda a usar o design 3D em um navegador com desempenho aceitável. Introdução O Three.js pode ser complicado, especialmente se você nunca mergulhou no mundo da programação 3D antes.

Eu tenho alguma experiência básica com o Unity e o mecanismo de jogo em C #, mas ainda assim, muitos dos conceitos são novos para mim. Cheguei à conclusão de que agora existem muito poucos recursos para desenvolvedores iniciantes, por isso decidi escrever este artigo. Nele, consideraremos os elementos básicos das cenas Three.js, desde malhas e materiais poligonais até geometria, carregadeiras e muito mais.

No final deste artigo, você terá uma sólida compreensão dos aspectos básicos necessários para adicionar uma dimensão extra ao seu futuro projeto da web.

Exemplos de Three.js de Ben Houston , Thomas Diewald e StrykerDoesAnimation .

Vetores e contêineres - blocos de construção básicos


Geralmente, existem duas classes principais no Three.js - Vector3 e Box3 . Se você é novo no 3D, isso pode parecer um pouco abstrato, mas você os encontrará muitas vezes.

Vector3


A classe 3D mais básica que contém três números: x, ye z . Os números são as coordenadas de um ponto no espaço 3D ou a direção e o comprimento. Por exemplo:

const vect = new THREE.Vector3(1, 1, 1); 

A maioria dos construtores no Three.js aceita objetos do tipo Vector3 como argumentos de entrada, por exemplo, Box3

Box3


Esta classe representa cuboide (contêiner 3D). Sua principal tarefa é criar um contêiner em torno de outros objetos - e isso é tudo, o menor cubóide no qual um objeto 3D se ajustará. Cada Box3 está alinhado sobre os eixos x, ye z. Um exemplo de como criar um contêiner usando Vector3 :

 const vect = new THREE.Vector3(1, 1, 1); const box = new THREE.Box3(vect); 

Um exemplo de como criar um contêiner em torno de um objeto 3D existente:

 const box = new THREE.Box3(); box.setFromObject(object); 

Você pode criar grades sem esse conhecimento aprofundado, mas assim que começar a criar ou alterar seus modelos, essas classes definitivamente serão úteis. Agora vamos nos afastar das abstrações para coisas mais visíveis.

Malha de polígono


No Three.js, o principal elemento visual no palco é o Mesh . Este é um objeto 3D composto por retângulos triangulares (malha poligonal). É construído usando dois objetos:
Geometria - determina sua forma, Material - determina a aparência.

Suas definições podem parecer um pouco confusas (por exemplo, a classe Geometry pode conter informações sobre cores), mas a principal diferença é exatamente isso.

Geometria


Com base na tarefa que você deseja realizar, você pode definir a geometria dentro do Three.js ou importar outra de um arquivo.

Usando funções como THREE.TorusKnotGeometry , podemos criar objetos complexos com uma única linha de código. Chegaremos a isso em breve, mas primeiro considere formas mais simples.
A figura 3D mais simples, cubóide ou contêiner, pode ser especificada pelos parâmetros de largura , altura e profundidade .

 const geometry = new THREE.BoxGeometry( 20, 20, 20 ); 


Para uma esfera, o valor dos parâmetros radius , widthSegments e heightSegments é mínimo . As duas últimas variáveis ​​indicam quantos triângulos o modelo deve usar para representar a esfera: quanto maior o número, mais suave ele será.

 const geometry = new THREE.SphereGeometry( 20, 64, 64 ); 


Se queremos fazer formas nítidas ou triangulares, podemos usar um cone. Seus argumentos são uma combinação dos argumentos das duas figuras anteriores. Abaixo, prescrevemos radius , widthSegments e radialSegments .

  const geometry = new THREE.ConeBufferGeometry( 5, 20, 32 ); 


Isso é apenas parte das figuras mais comuns. O Three.js possui muitas formas dentro da caixa, que podem ser encontradas na documentação. Neste artigo, veremos formas mais interessantes criadas com base no método TorusKnotGeometry .

Por que essas formas têm exatamente a aparência? Esta questão está além do escopo deste artigo, mas peço que você experimente os valores dos parâmetros, porque você pode obter formas muito interessantes com uma linha de código!

 const geometry = new THREE.TorusKnotGeometry(10, 1.3, 500, 6, 6, 20); 

https://codepen.io/BretCameron/pen/gOYqORg

Materiais


A geometria define a forma dos nossos objetos 3D, mas não a aparência. Para consertar isso, precisamos de materiais.

O Three.js oferece 10 materiais prontos para uso, cada um com suas próprias vantagens e parâmetros personalizáveis. Consideraremos apenas parte dos mais úteis.



MalhaNormalMaterial


Útil para início e início rápidos.

Começaremos com MeshNormalMaterial , o material multicolorido que usamos nos exemplos acima. Corresponde aos vetores normais no painel RGB, ou seja, as cores são usadas para determinar a posição do vetor no espaço 3D.

 const material = new THREE.MeshNormalMaterial(); 

Observe que se você deseja alterar a cor do material, pode usar o filtro CSS e alterar a saturação:
  filter: hue-rotate(90deg) . 


Na minha experiência, esse material é mais útil para um estrato e lançamento rápidos. Para maior controle de seus objetos, é melhor usar outra coisa.

Meshbasicmaterial


Útil ao exibir apenas o esqueleto.

Se você quiser dar uma cor à figura, poderá usar o MeshBasicMaterial apenas se a iluminação não for aplicada. Achei útil usar esse material para renderizar o esqueleto do modelo. Para desenhar apenas o esqueleto, você precisa passar {wireframe: true} como parâmetro.

 const material = new THREE.MeshBasicMaterial({ wireframe: true, color: 0xdaa520 }); 

A principal desvantagem deste material é que as informações sobre a profundidade do material são completamente perdidas. Cada material tem a opção de exibir apenas o esqueleto, mas apenas um material resolve o problema de falta de profundidade - MeshDepthMaterial


Meshhambertmaterial


Útil para alto desempenho, mas baixa precisão.

Este é o primeiro material que leva em consideração a luz, então você precisa adicionar um pouco de luz à nossa cena. No código abaixo, adicionamos holofotes com uma tonalidade amarela para criar um efeito mais quente.

 const scene = new THREE.Scene(); const frontSpot = new THREE.SpotLight(0xeeeece); frontSpot.position.set(1000, 1000, 1000); scene.add(frontSpot); const frontSpot2 = new THREE.SpotLight(0xddddce); frontSpot2.position.set(-500, -500, -500); scene.add(frontSpot2); 

Agora adicione material para a nossa figura. Como nossa figura é como uma decoração, sugiro adicionar uma cor mais dourada. Outro parâmetro, emissivo , é a cor do objeto proveniente do próprio objeto (sem fonte de luz). Geralmente isso funciona melhor como uma cor escura - por exemplo, como tons escuros de cinza, como no exemplo abaixo

 const material = new THREE.MeshLambertMaterial({ color: 0xdaa520, emissive: 0x111111, }); 


Como você pode ver no exemplo abaixo, a cor é mais ou menos correta, mas a maneira como interage com a luz não adiciona realismo. Para corrigir isso, precisamos usar o MeshPhongMaterial ou o MeshStandardMaterial.

MeshPhongMaterial


Útil para desempenho médio e precisão média.

Esse material oferece uma troca entre desempenho e precisão de renderização, portanto, este material é uma boa opção para um aplicativo que precisa ser produtivo junto com uma renderização mais precisa do que com o MeshLambertMaterial.

Agora podemos alterar a propriedade especular que afeta o brilho e a cor da reflexão da superfície. Se a propriedade emissiva é geralmente escura, o especular funciona melhor para cores claras. Abaixo usamos cinza claro.

 const material = new THREE.MeshPhongMaterial({ color: 0xdaa520, emissive: 0x000000, specular: 0xbcbcbc, }); 


Visualmente, a imagem de cima reflete a luz de forma mais convincente, mas ainda não perfeita. A luz branca é muito brilhante e o material parece mais estriado do que metálico (e nos esforçamos para isso). Podemos obter um resultado melhor usando o MeshStandardMaterial.

MeshStandartMaterial


Útil para alta precisão, mas baixa produtividade.

Este é o material mais preciso de todos, embora seu uso acarrete os custos do uso de mais energia. MeshStandartMaterial é usado com parâmetros adicionais de metalicidade e rugosidade , cada um com valor entre 0 e 1.

O parâmetro metalness afeta como o objeto reflete, aproximando-se da natureza do metal. Isso ocorre porque materiais condutores como metais têm diferentes propriedades refletivas, diferentemente dos dielétricos, como a cerâmica.

A rugosidade adiciona uma camada extra para personalização. Você pode imaginar isso como o oposto do brilho: 0 - muito brilhante, 1 - muito fosco.

 const material = new THREE.MeshStandardMaterial({ color: 0xfcc742, emissive: 0x111111, specular: 0xffffff, metalness: 1, roughness: 0.55, }); 


Este é o material mais realista de todos os apresentados em Three.js, mas também o que consome mais recursos

Os materiais que discutimos abaixo são os que encontrei com mais frequência e você pode ver todas as opções no banco dos réus.

Carregadeiras


Como discutimos acima, você pode definir manualmente malhas de geometria e polígono. Na prática, as pessoas frequentemente carregam suas geometrias a partir de arquivos. Felizmente, o Three.js possui poucos downloads que suportam muitos formatos 3D.

O ObjectLoader principal carrega o arquivo JSON usando o formato JSON Object / Scene . A maioria dos gerenciadores de inicialização precisa ser importada manualmente. Você pode encontrar uma lista completa dos gerenciadores de inicialização suportados aqui e importá-los. Abaixo está uma pequena lista do que você pode importar.

 // GLTF import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; // OBJ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; // STL import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'; // FBX import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'; // 3MF import { 3MFLoader } from 'three/examples/jsm/loaders/3MFLoader.js'; 

O formato recomendado para visualização on-line é GLTF, porque o formato é "destinado a fornecer ativos em tempo de execução, compacto para transferência e rápido para download".

Obviamente, pode haver muitos motivos para preferir um determinado tipo de arquivo (por exemplo, se a qualidade for uma prioridade ou se for necessária uma precisão para a impressão 3D). O melhor desempenho online será ao importar o GLTF.

 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import model from '../models/sample.gltf'; let loader = new GLTFLoader(); loader.load(model, function (geometry) { // if the model is loaded successfully, add it to your scene here }, undefined, function (err) { console.error(err); }); 

Juntando tudo


Uma das razões pelas quais o Three.js pode parecer intimidador é que você pode criar algo do zero com apenas algumas linhas de código. Em cada exemplo acima, precisávamos criar uma cena e uma câmera. Para simplificar, mantive esse código fora do escopo da revisão, mas, por enquanto, veremos como ele ficará juntos.

A maneira como você organiza seu código é com você. Em exemplos mais simples, como neste artigo, faz sentido escrever todo o código em um só lugar. Mas, na prática, é útil separar os elementos individuais para a possibilidade de expandir a base de código e seu gerenciamento.

Para simplificar, consideraremos os elementos que são desenhados como um único objeto, portanto, colocaremos todo o código em um arquivo.

 // Import dependencies import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; //   const scene = new THREE.Scene(); scene.background = new THREE.Color(0x282c34); //  ,       const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); camera.position.z = 5; //  ""      const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); //   DOM   renderer.domElement   document.getElementById('threejs').appendChild(renderer.domElement); //  ,      DOM  let controls = new OrbitControls(camera, document.getElementById('threejs')); controls.target.set(0, 0, 0); controls.rotateSpeed = 0.5; controls.update(); //  ( )   const geometry = new THREE.TorusKnotGeometry(10, 1.3, 500, 6, 6, 20); //    const material = new THREE.MeshStandardMaterial({ color: 0xfcc742, emissive: 0x111111, specular: 0xffffff, metalness: 1, roughness: 0.55, }); //   ,       const mesh = new THREE.Mesh(geometry, material); mesh.scale.x = 0.1; mesh.scale.y = 0.1; mesh.scale.z = 0.1; scene.add(mesh); //  ,       const frontSpot = new THREE.SpotLight(0xeeeece); const frontSpot2 = new THREE.SpotLight(0xddddce); frontSpot.position.set(1000, 1000, 1000); frontSpot2.position.set(-500, -500, -500); scene.add(frontSpot); scene.add(frontSpot2); //   ,           const animate = function () { requestAnimationFrame(animate); mesh.rotation.x += 0.005; mesh.rotation.y += 0.005; mesh.rotation.z += 0.005; renderer.render(scene, camera); }; //    animate(); 

Preciso usar uma estrutura?


Finalmente, é hora de discutir se o Three.js deve ser usado com sua estrutura favorita? No momento, há um bom pacote react -three-fiber para o React. Para usuários do React, há vantagens óbvias em usar um pacote como este - você mantém uma estrutura para trabalhar com componentes que permite reutilizar o código.

Para iniciantes, aconselho você a começar com o Vanila JS usual, porque a maioria dos materiais on-line escritos sobre o Three.js se relacionam ao Three.js no Vanila JS. Com base na minha experiência de aprendizado, isso pode ser confuso e difícil de aprender por meio de um pacote - por exemplo, você terá que converter objetos e métodos Three.js. em componentes e acessórios. (assim que você aprender o Three.js, poderá usar qualquer pacote).

Como adicionar o Three.js à estrutura


O Three.js fornece um objeto HTML (geralmente chamado renderer.domElement ) que pode ser adicionado a qualquer objeto HTML no seu aplicativo. Por exemplo, se você tem uma div com id = ”threejs”, pode simplesmente incluir o seguinte código no seu código Three.js.

 document.getElementById('threejs').appendChild(renderer.domElement); 

Algumas estruturas têm caminhos preferenciais para acessar os nós DOM da árvore. Por exemplo, ref em React, $ ref em Vue ou ngRef em Angular e parece uma enorme vantagem no contexto do acesso direto aos elementos DOM. Como exemplo, vejamos uma implementação rápida do React.

Estratégia para reagir


Se você usa o React, existe uma maneira de incorporar os arquivos Three.js. em um de seus componentes. No arquivo ThreeEntryPoint.js , escreveremos o seguinte código:

 export default function ThreeEntryPoint(sceneRef) { let renderer = new THREE.WebGLRenderer(); // ... sceneRef.appendChild(renderer.domElement); } 

Exportamos isso como uma função que recebe um argumento: uma referência a um elemento em nosso componente. Agora podemos criar nosso componente

 import React, { Component } from 'react'; import ThreeEntryPoint from './threejs/ThreeEntryPoint'; export default class ThreeContainer extends Component { componentDidMount() { ThreeEntryPoint(this.scene); } render() { return ( <> <div ref={element => this.scene = element} /> </> ); } } 

A função ThreeEntryPoint importada deve ser chamada no método componentDidMount e passar a nova div como argumento usando referências
Como exemplo dessa abordagem em ação, você pode clonar o repositório e tentar você mesmo: https://github.com/BretCameron/three-js-sample .

Conclusão


Ainda posso falar muito sobre o Three.js, mas espero que este artigo tenha lhe dado informações suficientes para começar a usar essa poderosa tecnologia. Quando comecei a aprender o Three.js, não consegui encontrar um único recurso como este artigo, por isso espero ter ajudado a tornar essa tecnologia mais acessível para iniciantes.

Source: https://habr.com/ru/post/pt477956/


All Articles