Conceitos Básicos

Agora que já sabemos como criar nossa cena básica, vamos trabalhar mais alguns conceitos básicos. Vamos trabalhar com o mesmo código aprensentado no tutorial passado. Só vamos renomear nosso arquivo JavaScript para exemplo2.js.

Página 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/exemplo2.js"></script>
</body>
</html>

Arquivo exemplo2.js inicial

var scene;
var camera;
var renderer;

var cube;


var init = function() {

    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

    renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );

    this.createACube();

    camera.position.z = 5;

    this.render();

};

var render = function() {
    requestAnimationFrame( render );

    this.animateCube();

    renderer.render( scene, camera );
};

var createACube = function() {
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshBasicMaterial( { color: "red" } );
    cube = new THREE.Mesh( geometry, material );
    scene.add( cube );
};

var animateCube = function() {
    cube.rotation.x += 0.1;
    cube.rotation.y += 0.1;
};

window.onload = this.init;

Elementos Básicos da Cena

No tutorial passado começamos nosso exemplo definindo três elementos básicos: a cena, a câmera e o render. Vamos explorar um pouco mais estes elementos.

Scene

A cena permite que você configure o ambiente em que os objetos vão ser renderizados. É nesse cenário que são inseridos os objetos, as luzes e as câmeras da cena. Na documentação é possível verificar a lista de propriedades que está associada a este objeto. Por enquanto não vamos trabalhar como nenhuma destas propriedades.

A imagem a seguir mostra como está posicionado o sistema de coordenadas:

Alt Coordinates (Fonte: Basic 3D graphics using Three.js)

A tela do computador está centralizada na origem dos eixos (0,0,0). As coordenadas positivas do eixo z são direcionadas para fora da tela. Os eixos x e y possuem os valores de coordenada padrões (x - positivo direita e y - positivo para cima). Qualquer objeto inserido na cena é colocado na coordenada (0, 0, 0). É por isso que quando adicionamos a câmera no exemplo passado não conseguíamos visualizar o objeto. Na verdade estávamos enxergando a cena de dentro do objeto. Para corrigir esse problema, mexemos a câmera sobre o eixo z em 5 positivo. Ou seja, afastamos a câmera para trás.

Se quisermos modificar a posição dos objetos na cena devemos mudar os valores de x, y e z. O exemplo a seguir movimenta o objeto sobre os 3 eixos.

Vamos alterar o método createACube().

var createACube = function() {
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshBasicMaterial( { color: "red"} );
    cube = new THREE.Mesh( geometry, material );

    cube.position.x = 2;
    cube.position.y = 1;
    cube.position.z = -1;

    scene.add( cube );
};

O código acima move o cubo para direita, para cima e para "dentro" da tela. Esses três comandos podem ser substituídos por um único comando: cube.position.set(2, 1, -1). Modifique os valores e veja o resultado na cena.

Camera

O outro elemento básico que deve ser definido para a construção da nossa cena 3D é a câmera. A câmera define o ponto de vista da cena. O Three.js possui três tipos de câmeras: CubeCamera, PerspectiveCamera e OrthographicCamera. A priori vamos trabalhar apenas com os conceitos da PerspectiveCamera e OrthographicCamera.

A diferença básica está na noção de profundidade. Na PerspectiveCamera a medida que os objetos se afastam em direção ao fundo da cena, eles vão modificando de tamanho (ficando menores). Esse modo de visão é muito mais próximo da forma como enxergamos. É difícil precisar o tamanho dos objetos quando estes se encontram a uma certa distância. Diferente da OrthographicCamera que mostra a idéia de um fundo fixo. Mesmo objetos distantes na cena apresentam a mesma proporções de objetos do mesmo tamanho que estejam próximos na cena. Esse tipo de câmera é muito uitilizada em jogos 2D como SimCity, Diablo, Civilization.

Neste link: https://www.script-tutorials.com/webgl-with-three-js-lesson-9/ vocês podem acessar um breve exemplo de como essas duas perspectivas funcionam. A imagem a seguir mostra como as camêras estão posicionadas em relação a cena:

(Fonte: https://www.script-tutorials.com/webgl-with-three-js-lesson-9/)

Os parâmetros também mudam de acordo com o tipo de câmera que estamos utilizando.

Na PerspectiveCamera os parâmetros são:

PerspectiveCamera( a, b, c, d)

A imagem a seguir ilustra bem tais parâmetros:

(Fonte: Basic 3D graphics using Three.js)

OrthographicCamera(a, b, c, d, e, f)

As imagens a seguir mostram o efeito de cada câmera sobre a mesma cena.

Perspective

Orthographic

No nosso exemplo, estamos utilizando a PerspectiveCamera para visualização da cena. Para ilustrar vamos fazer uma pequena modificação no nosso exemplo. Vamos fazer com que o cubo de tempos e tempos se afaste da tela. Para isso, irei modificar o método animateCube que é chamado dentro do render.

var animateCube = function() {
    cube.rotation.x += 0.1;
    cube.rotation.y += 0.1;
    cube.position.z -= 0.1;
};

Esse alteração vai fazer com que o cubo se afaste da tela. Observe que cada vez que ele se afasta da tela, ele sofre alteração na noção de tamanho do objeto.

Para ilustrar o efeito da OrthographicCamera vamos manter a atualização, mas vamos alterar a câmera da cena. Para isso devemos alterar a variável camera no método init.

var init = function() {

    scene = new THREE.Scene();

    camera = new THREE.OrthographicCamera( -window.innerWidth/512, window.innerWidth/512, window.innerHeight/512, -window.innerHeight/512, 0.1, 1000 )
    renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );

    this.createACube();

    camera.position.z = 5;

    this.render();

};

Perceba que mesmo o objeto se afastando da cena, as propoções dele se mantém.

Renderer

O último elemento básico que definimos foi o renderizador identificado pela variável renderer. O renderizador utilizado é o WebGL. Se o navegador não suportar WebGL, um outro tipo de renderizador (SVG ou HTML5-based) é criado. No entanto, a aplicação perde em desempenho. No nosso exemplo, informamos que iremos renderizar os objetos utilizando o WebGL e definimos o tamanho do espaço para isto. Em seguida, adicionamos ao body da nossa página HTML o renderizador. Demais aspectos deste elemento podem ser abordados ao longo dos tutoriais.

Incrementando a cena

Vamos apresentar mais alguns conceitos fazendo modificações no projeto desenvolvido até aqui. Para esse exemplo, vamos manter a câmera como PerspectiveCamera e retira a animação do cubo (basta comentar a chamada da função animateCube).

A primeira modificação que vamos fazer é mudar a forma como os objetos são redenrizados. Até aqui usamos o MeshBasicMaterial para especificar o material com o qual os objetos são criados. Isso inclui como os objetos se relacionam com as luzes. Perceba que até agora não especificando nenhuma informação de luz do ambiente. Por conta disso, vamos utilizar um material que necessite de luz para ser exibido.

Vamos utilizar o MeshLambertMaterial que é próprio para superfícies não brilhantes. Para isto, basta modificar o método createACube.

var createACube = function() {
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshLambertMaterial( { color: "red"} );
    cube = new THREE.Mesh( geometry, material );

    cube.position.x = 0;
    cube.position.y = 0;
    cube.position.z = 0;

    scene.add( cube );
};

Se fizermos esta alteração e atualizarmos a página, vamos notar que o cubo "desapareceu". Na verdade, ele está na cena. No entanto, não está sendo iluminado. Para corrigir este problema vamos adicionar uma luz à cena. Vamos criar o método createLight.

var createLight = function () {
    var spotLight = new THREE.SpotLight(0xffffff);
    spotLight.position.set(10, 20, 20);
    spotLight.castShadow = true;
    scene.add(spotLight);
};

Esse método está especificando uma luz do tipo SpotLight na posição (10, 20, 20). Essa é uma luz pontual que atua sobre objetos criados a partir do MeshLambertMaterial. Para funcionar basta chamar esse método dentro do init. Observe que o objeto é iluminado de acordo com a posição da luz na cena. Existem vários tipos de luz. Iremos explorar outros tipos mais a frente. Para dar um efeito melhor, alteramos o método animação:

varanimateCube = function() {
    cube.rotation.y += 0.01;
};

A próxima alteração de cena que vamos fazer é adicionar um plano. Desta forma podemos mostrar alguns efeitos de sombra dos objetos. Vamos criar uma função createPlane que vai criar e adicionar um plano logo abaixo do cubo.

var createPlane = function() {
    var planeGeometry = new THREE.PlaneGeometry(20, 20);
    var planeMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc});
    plane = new THREE.Mesh(planeGeometry, planeMaterial);

    plane.rotation.x = -0.5 * Math.PI;
    plane.position.y = -1;

    scene.add(plane);
};

A função createPlane utiliza o método planeGeometry para especificar a forma geométrica que será criada e seu tamanho. Neste caso, 20x20. Em seguida definimos o material juntamente com a cor e criamos o objeto plane que será inserido na cena.

Assim como todos os objetos que são inseridos, o plano será inserido na posição (0, 0, 0). No caso específico do plano, ele será inserido na posição vertical, o que causaria um efeito inadequado para nosso exemplo, já que queremos que o plano se localize abaixo do cubo. Por conta disso, precisamos fazer duas operações de movimentação: a primeira de rotação para que ele fique na posição horizontal e a segunda de deslocamento no eixo y para que ele fique abaixo do cubo e não cortando-o na horizontal.

Para funcionar, basta chamar o método createPlane no método init.

Aplicando sombras

Um aspecto importante para a visualização de objetos em uma cena é a inclusão de sombras. Na cena que criamos, temos um objeto, um plano e uma luz agindo sobre esse plano. No entanto, precisamos inserir a noção de sombra de acordo com a aplicação da luz.

No Three.js essa característica é informada a partir de dois atributos do objeto: castShadow e receiveShadow. O primeiro informa ao objeto que ele deve "gerar" sombra e o segundo infoma que o objeto deve "receber" sombra. Para o nosso problema faz sentido que o plano seja o objeto que receba a sombra e os demais emitam sombra. Sendo assim, vamos alterar os métodos de criação de objetos com as informações sobre sombra.

var createLight = function () {
    var spotLight = new THREE.SpotLight(0xffffff);
    spotLight.position.set(10, 20, 20);
    spotLight.castShadow = true;
    scene.add(spotLight);
};

var createPlane = function() {
    var planeGeometry = new THREE.PlaneGeometry(20, 20);
    var planeMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc});
    plane = new THREE.Mesh(planeGeometry, planeMaterial);

    plane.rotation.x = -0.5 * Math.PI;
    plane.position.y = -1;

    plane.receiveShadow = true;

    scene.add(plane);
};

var createACube = function() {
    var geometry = new THREE.BoxGeometry( 1, 1, 1 );
    var material = new THREE.MeshLambertMaterial( { color: "red"} );
    cube = new THREE.Mesh( geometry, material );

    cube.position.x = 0;
    cube.position.y = 0;
    cube.position.z = 0;

    cube.castShadow = true;

    scene.add( cube );
};

Para que a sombra seja visível, deve-se alterar o método de renderização setando as propriedades shadowMap.enabled e shadowMap.type do objeto renderer.

var render = function() {
    requestAnimationFrame( render );

    this.animateCube();

    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.render( scene, camera );
};

Existem vários tipos de sombra. A PCFSoftShadowMap é a que atendeu melhor aos objetivos do problema.

Com isso fechamos nosso segundo tutorial. No próximo vamos trabalhar mais alguns conceitos básicos de objetos e mostrar como podemos aplicar textura a eles.

Até o próximo tutorial ;)

D2L