Aplicando Textura
Nosso terceiro exemplo vai mostrar como podemos trabalhar com textura em objetos 3D. Para demonstrar isso, vamos criar um novo exemplo utilizando conceitos já vistos aqui anteriormente. O nosso objetivo final é criar uma esfera, colocar uma textura da terra e aplicar movimento de rotação. Esse tutorial foi tirado do Capítulo 2 do livro Three.js Essentials.
Criando os objetos essenciais
Assim como nos exemplos anteriores, vamos fazer uso de um arquivo index.html e de um novo arquivo JavaScript: exemplo3.js. A seguir os dois arquivos iniciais.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Computação Visual - Three.js - Exemplo 2</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="js/three.min.js"></script>
<script src="js/exemplo3.js"></script>
</body>
</html>
exemplo3.js
var scene;
var camera;
var renderer;
var init = function() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
this.render();
};
var render = function() {
requestAnimationFrame( render );
renderer.render( scene, camera );
};
window.onload = this.init;
Foram mantidas algumas configurações iniciais. Elas serão alteradas quando for necessário.
A primeira parte consiste em construir um esfera, adiciona-la à cena e posicionar a câmera. Vamos primeiro criar um método createASphere.
var createASphere = function() {
var sphereGeometry = new THREE.SphereGeometry(15, 60, 60);
var sphereMaterial = new THREE.MeshNormalMaterial();
earthMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
earthMesh.name = 'earth';
scene.add(earthMesh);
};
O método SphereGeometry recebe 3 parâmetros: o raio da esfera, o número de segmentos na largura e o número de segmentos na altura. Estes dois últimos parâmetros determinam o número de quadrados utilizados para formar a esfera. Quanto maior o núemero, mas "arredondado" será o aspecto da esfera.
Foi utilizado também um outro tipo de material para gerar a esfera. Esse material define as cores independete da luz que esteja incidindo sobre ele. As cores são determinadas pelo ângulo de cada face. Para nós, o uso deste tipo de material é meramente didático. Ele será substituído por um que é determinado pela cor inserida sobre os objetos.
Em seguida devemos posicionar a câmera:
camera.position.x = 25;
camera.position.y = 10;
camera.position.z = 40;
camera.lookAt(scene.position);
O createSphere e os comandos de cmaera devem ser inseridos na função init:
var init = function() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
this.createASphere();
camera.position.x = 25;
camera.position.y = 10;
camera.position.z = 40;
camera.lookAt(scene.position);
document.body.appendChild( renderer.domElement );
this.render();
};
Aplicar a Textura sobre a esfera
O passo seguinte é aplicar a textura sobre a esfera para que fique com a aparência da terra. O primeiro passo é fazer os downloads das imagens. Vamos trabalhar com duas texturas: uma que representa a terra e a outra que representa as nuvens que serão colocadas. Ambas foram salvas na pasta "images" na raiz do projeto.
Vamos implementar o método createEarthMaterial que criar a textura baseada na imagem earthmap4k.jpg .
var createEarthMaterial = function() {
var earthTexture = new THREE.TextureLoader().load("images/earthmap4k.jpg");
var earthMaterial = new THREE.MeshBasicMaterial();
earthMaterial.map = earthTexture;
return earthMaterial;
};
Esse método basicamente muda a forma como vamos trabalhar com o material que envolve a esfera. Para funcionar devemos alterar o método que cria a esfera.
var createASphere = function() {
var sphereGeometry = new THREE.SphereGeometry(15, 60, 60);
var sphereMaterial = this.createEarthMaterial();
earthMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
earthMesh.name = 'earth';
scene.add(earthMesh);
};
De forma semelhante vamos adicionar as nuvens sobre a textura da terra.
Criamos o método createCloudMaterial:
var createCloudMaterial = function() {
var cloudTexture = new THREE.TextureLoader().load("images/fair_clouds_4k.png");
var cloudMaterial = new THREE.MeshBasicMaterial();
cloudMaterial.map = cloudTexture;
cloudMaterial.transparent = true;
return cloudMaterial;
};
e modificamos o método createASphere adicionando os trechos de código para incluir uma nova esfera transparente em cima da que já tinha sido criado:
var createASphere = function() {
var sphereGeometry = new THREE.SphereGeometry(15, 60, 60);
var sphereMaterial = this.createEarthMaterial();
earthMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
earthMesh.name = 'earth';
scene.add(earthMesh);
var cloudGeometry = new THREE.SphereGeometry(15.2, 60, 60);
var cloudMaterial = this.createCloudMaterial();
cloudMesh = new THREE.Mesh(cloudGeometry, cloudMaterial);
cloudMesh.name = 'clouds';
scene.add(cloudMesh);
};
Por fim, vamos adicionar a rotação da terra alterando o método render:
var render = function() {
earthMesh.rotation.y += 0.001;
cloudMesh.rotation.y += 0.001*1.1;
requestAnimationFrame( render );
renderer.render( scene, camera );
};
Aplicando Iluminação
O Three.js oferece uma lista de luzes, cada qual com sua finalidade específica. Para nosso exemplo, vamos utilizar somente a AmbientLight e a DirectionLight. O materia que estamos utilizando na esfera é independente da luz. Observe que mesmo sem colocar uma luz no ambiente, conseguimos exengar o objeto. No entanto, o ideal fosse que só conseguíssemos ver o objeto se tivéssemos uma luz atuando sobre ele. Para isso, iremos mudar o material do objeto de MeshBasicMaterial para MeshPhongMaterial.
As funções createEarthMaterial e creteCloudMaterial ficam da seguinte forma:
var createEarthMaterial = function() {
var earthTexture = new THREE.TextureLoader().load("images/earthmap4k.jpg");
var earthMaterial = new THREE.MeshPhongMaterial();
earthMaterial.map = earthTexture;
return earthMaterial;
};
var createCloudMaterial = function() {
var cloudTexture = new THREE.TextureLoader().load("images/fair_clouds_4k.png");
var cloudMaterial = new THREE.MeshPhongMaterial();
cloudMaterial.map = cloudTexture;
cloudMaterial.transparent = true;
return cloudMaterial;
};
Se atualizarmos a página não devemos mais enxergar a Terra. Vamos criar uma função que adiciona a luz no nosso ambiente. Vamos fazer uma função para cada tipo de de luz.
var createDirectionalLight = function() {
var directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(100,10,-50);
directionalLight.name='directional';
scene.add(directionalLight);
};
var createAmbientLight = function() {
var ambientLight = new THREE.AmbientLight(0x111111);
ambientLight.name='ambient';
scene.add(ambientLight);
};
Estes métodos devem ser chamados dentro de init. Ao aplicar a luz devemos ter a cena da terra parcialmente ilumidade e girando.
Trabalhando com outras Texturas
Podemos melhorar a qualidade de nosso objeto trabalhando com diferentes texturas. Por exemplo, podíamos colocar a noção de elevações no mapa. Outra idéia que pode ser aplicada é relacionada a reflexão da luz. A luz ela reflete muito mais nos oceanos do que no continente. Isso pode ser feito aplicando duas diferentes texturas. Essas texturas estão representadas pelas imagens earth_normalmap_flat4k.jpg e earth_normalmap_flat4k.jpg.
Para colocar as elevações vamos usar uma abordagem chamada de normal map. Em um normal map não são armazenadas as intensidade dos pixels e sim a direção (orientação) de cada pixel. O mapa é mostrado a seguir:
Para colocar esse tipo de textura é preciso alterar o nosso método createEarthMaterial acidionando as seguintes linhas:
var normalMap = new THREE.TextureLoader().load("images/earth_normalmap_flat4k.jpg");
earthMaterial.normalMap = normalMap;
earthMaterial.normalScale.set(0.5, 0.7);
O parâmetro normalScale serve para calibrar a intensidade do efeito. A primeira propriedade define a escala no eixo x e a segunda no eixo y. Podemos ver a diferença nas duas imagens a seguir.
Nesta imagem aplicamos earthMaterial.normalScale.set(0.5, 0.7):
E agora aplicamos earthMaterial.normalScale.set(1, 1):
Para trabalhar com a refletividade das áreas vamos utilizar o seguinte mapa:
Como pode ser observado, os continentes são pretos. Isso indica que estas regiões não irão refletir a luz direcional. Já os oceanos são brancos. Isso indica que eles irão refletir a luz direcional.
Para colocar esse tipo de textura também é preciso alterar o nosso método createEarthMaterial acidionando as seguintes linhas:
var specularMap = new THREE.TextureLoader().load("images/earth_normalmap_flat4k.jpg");
earthMaterial.specularMap = specularMap;
earthMaterial.specular = new THREE.Color(0x262626);
A propriedade earthMaterial.specular define a cor que será refletida.
Sendo assim, nosso método createEarthMaterial fica da seguinte forma após a aplicação das duas texturas.
var createEarthMaterial = function() {
var earthTexture = new THREE.TextureLoader().load("images/earthmap4k.jpg");
var normalMap = new THREE.TextureLoader().load("images/earth_normalmap_flat4k.jpg");
var specularMap = new THREE.TextureLoader().load("images/earthspec4k.jpg");
var earthMaterial = new THREE.MeshPhongMaterial();
earthMaterial.map = earthTexture;
earthMaterial.normalMap = normalMap;
earthMaterial.normalScale.set(0.5, 0.7);
earthMaterial.specularMap = specularMap;
earthMaterial.specular = new THREE.Color(0x262626);
return earthMaterial;
};
Observe na cena gerada que a luz direcional ela reflete bem nos oceanos. No entanto, ela não é refletida no continente.
O arquivo exemplo3.js até aqui fica da seguinte maneira:
var scene;
var camera;
var renderer;
var earthMesh;
var cloudMesh;
var createDirectionalLight = function() {
var directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(100,0,0);
directionalLight.name='directional';
scene.add(directionalLight);
};
var createAmbientLight = function() {
var ambientLight = new THREE.AmbientLight(0x111111, 1.0);
ambientLight.name='ambient';
scene.add(ambientLight);
};
var createEarthMaterial = function() {
var earthTexture = new THREE.TextureLoader().load("images/earthmap4k.jpg");
var normalMap = new THREE.TextureLoader().load("images/earth_normalmap_flat4k.jpg");
var specularMap = new THREE.TextureLoader().load("images/earthspec4k.jpg");
var earthMaterial = new THREE.MeshPhongMaterial();
earthMaterial.map = earthTexture;
earthMaterial.normalMap = normalMap;
earthMaterial.normalScale.set(0.5, 0.7);
earthMaterial.specularMap = specularMap;
earthMaterial.specular = new THREE.Color(0x262626);
return earthMaterial;
};
var createCloudMaterial = function() {
var cloudTexture = new THREE.TextureLoader().load("images/fair_clouds_4k.png");
var cloudMaterial = new THREE.MeshPhongMaterial();
cloudMaterial.map = cloudTexture;
cloudMaterial.transparent = true;
return cloudMaterial;
};
var createASphere = function() {
var sphereGeometry = new THREE.SphereGeometry(15, 60, 60);
var sphereMaterial = this.createEarthMaterial();
earthMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
earthMesh.name = 'earth';
scene.add(earthMesh);
var cloudGeometry = new THREE.SphereGeometry(15.2, 60, 60);
var cloudMaterial = this.createCloudMaterial();
cloudMesh = new THREE.Mesh(cloudGeometry, cloudMaterial);
cloudMesh.name = 'clouds';
scene.add(cloudMesh);
};
var init = function() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
this.createASphere();
this.createAmbientLight();
this.createDirectionalLight();
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 40;
camera.lookAt(scene.position);
document.body.appendChild( renderer.domElement );
this.render();
};
var render = function() {
earthMesh.rotation.y += 0.001;
cloudMesh.rotation.y += 0.001*1.1;
requestAnimationFrame( render );
renderer.render( scene, camera );
};
window.onload = this.init;