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.

Alt text

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:

Alt text

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):

Alt text

E agora aplicamos earthMaterial.normalScale.set(1, 1):

Alt text

Para trabalhar com a refletividade das áreas vamos utilizar o seguinte mapa:

Alt text

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;