quinta-feira, 8 de janeiro de 2015

Exportando do Blender para o Web GL usando Collada e three.js (Parte 3)


Essa é a terceira parte do tutorial onde eu ensino como criar no visualizador básico Web GL. Nós vamos usar o Blender para criar os modelos em 3D. E a codificação Web GL em si será feita com o three.js.

Na primeira parte falamos sobre o os programas necessários e os scripts. Na segunda parte foi tudo o que precisavamos para carregar o modelo via Collada e as configurações para o cenário. Caso necessite rever as postagens anteriores desse tutorial, segue os links abaixo.

Criando a SkyDome

Podemos criar um fundo de cor única com a seguinte linha de código.

renderer.setClearColorHex(0x262626, 1)
Se você quiser mudar a cor pesquise por uma HTML online. O Colorizer.org e o Color Hexa são sites que vão ajudar você a converter cores do RGB para cores Hexadecimais. O trio hexa dessa cor deve ser 262626 por isso certifique-se que você mantenha os dois primeiros dígitos. Porém um fundo de cor única é muito chato não?! Por isso decidi criar uma gradiente usando os recursos do three.js vamos precisar de um fragment shader, um vertex shader e um pouco mais de código. Observe a maior parte do código para gradiente usado foi retirado de exemplos do three.js. Visite o threejs.org se desejar mais informações a respeito.

Vamos começar com o Vertez Shader...

Cole o código do Fragment Shader logo após o Vertex Shader.

<script type="x-shader/x-vertex" id="vertexShader">

   varying vec3 vWorldPosition;

   void main() {

    vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
    vWorldPosition = worldPosition.xyz;

    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

   }

  </script>
A SkyDome devemos criar excluindo todos os calculos de sombreamento exceto as relativas localizações (vertex shader) e de cores simples (fragment shader), sem fontes de luz e outros recursos.

Criando a SkyDome em si

Uma boa inclinação preta para acizentada deve aparecer agora. Se quiser alterar as cores, cole o código hexadecimal no lugar dos atuais em topColor e bottomColor. Cores mais contrastantes deixa o degradê mais exposto.

Script completo

Aqui está o script completo para acompanharmos.

 <!doctype html>
<html lang="en">
<head>
  <title>Test von Web GL</title>
  <meta charset="utf-8">
</head>
<body style="margin: 0;">

  <script src="js/three.min.js"></script>
  <script src="js/OrbitControls.js"></script>
<script src="js/ColladaLoader.js"></script>


 <script type="x-shader/x-vertex" id="vertexShader">

   varying vec3 vWorldPosition;

   void main() {

    vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
    vWorldPosition = worldPosition.xyz;

    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

   }

  </script>

  <script type="x-shader/x-fragment" id="fragmentShader">

   uniform vec3 topColor;
   uniform vec3 bottomColor;
   uniform float offset;
   uniform float exponent;

   varying vec3 vWorldPosition;

   void main() {

    float h = normalize( vWorldPosition + offset ).y;
    gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( h, exponent ), 0.0 ) ), 1.0 );

   }

  </script>
  <script>

   
    var scene, camera, renderer;

    init();
    animate();

  
    function init() {

     
      scene = new THREE.Scene();
      var WIDTH = window.innerWidth,
          HEIGHT = window.innerHeight;

    
      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);


      camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 10000);
      camera.position.set(50,150,100);
      scene.add(camera);

      window.addEventListener('resize', function() {
        var WIDTH = window.innerWidth,
            HEIGHT = window.innerHeight;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
      });

  

    var vertexShader = document.getElementById( 'vertexShader' ).textContent;
    var fragmentShader = document.getElementById( 'fragmentShader' ).textContent;
    var uniforms = {
     topColor:   { type: "c", value: new THREE.Color(0x000000) },
     bottomColor: { type: "c", value: new THREE.Color( 0x262626 ) },
     offset:   { type: "f", value: 100 },
     exponent:  { type: "f", value: 0.7 }
    }
   

    

    var skyGeo = new THREE.SphereGeometry( 2000, 32, 15 );
    var skyMat = new THREE.ShaderMaterial( { vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: uniforms, side: THREE.BackSide } );

    var sky = new THREE.Mesh( skyGeo, skyMat );
    scene.add( sky );


    
      var light = new THREE.PointLight(0xfffff3, 0.8);
      light.position.set(-100,200,100);
      scene.add(light);

var sphereSize = 1; 
var pointLightHelper = new THREE.PointLightHelper( light, sphereSize ); 
scene.add( pointLightHelper );


      var light2 = new THREE.PointLight(0xd7f0ff, 0.2);
      light2.position.set(200,200,100);
      scene.add(light2);

var sphereSize = 1; 
var pointLightHelper2 = new THREE.PointLightHelper( light2, sphereSize ); 
scene.add( pointLightHelper2 );


var light3 = new THREE.PointLight(0xFFFFFF, 0.5);
      light3.position.set(150,200,-100);
      scene.add(light3);

var sphereSize = 1; 
var pointLightHelper3 = new THREE.PointLightHelper( light3, sphereSize ); 
scene.add( pointLightHelper3 );

  var loader = new THREE.ColladaLoader();

 
  loader.options.convertUpAxis = true;

 
  loader.load( 'models/dummy1.dae', function ( collada ) {
 //dummy1.dae
   
    var dae = collada.scene;
 
 
    var skin = collada.skins[ 0 ];
 
  
dae.position.set(0,0,0);//x,z,y- if you think in blender dimensions ;)
dae.scale.set(1.5,1.5,1.5);

scene.add(dae);






var axes = new THREE.AxisHelper(50);
 axes.position = dae.position;
 scene.add(axes);
 
 var gridXZ = new THREE.GridHelper(100, 10);
 gridXZ.setColors( new THREE.Color(0x8f8f8f), new THREE.Color(0x8f8f8f) );
 gridXZ.position.set(0,0,0 );
 scene.add(gridXZ);
 
 
      });

      
      controls = new THREE.OrbitControls(camera, renderer.domElement);

    }


  
    function animate() {

      
      requestAnimationFrame(animate);
      
 
      renderer.render(scene, camera);
      controls.update();

    }

  </script>
 
</body>
</html>

Limitando o zoom

Por padrão o zoom é infinito. O problema é que depois de uma certa distância vamos perder o nosso modelo de vista.

Para resolver essa questão, acesse o script OrbitControls.js e localize o código abaixo. Os desenvolvedores do three.js oferecem um script bem documentado, isso facilita bastante para mudar os valores no código. Você pode alterar o zoom e a rotação. No meu caso bastou alterar o this.minDistance e o this.maxDistance.

Com algumas tentativas e erros consegui o resultado certo.

// This option actually enables dollying in and out; left as "zoom" for
// backwards compatibility
this.noZoom = false;
this.zoomSpeed = 1.0;
// Limits to how far you can dolly in and out
this.minDistance = 0;
this.maxDistance = Infinity;
Eu deixei algo assim:

 
// This option actually enables dollying in and out; left as "zoom" for
// backwards compatibility
this.noZoom = false;
this.zoomSpeed = 1.0;
// Limits to how far you can dolly in and out
this.minDistance = 20;
this.maxDistance = 300;


Publicando os arquivos no servidor

Então tudo que fizemos até aqui é um desenvolvimento offline. O próximo passo será publicar os seus arquivos no servidor web. Eu estou usando o fillezilla para publicar meus arquivos.

Existem servidores internacionais bastante baratos no mercado. Já vi servidores que custam $ 2,99 por mês mas devemos ficar atentos pois os serviços de hospedagens baratos não suportam muitos recursos e tecnologias e se precisar que um script funcione eles cobram a parte. E também não tem um suporte adequado.

O servidor que estou usando é Locaweb, como sou fornecedora do serviço pago um valor relativamente alto para oferecer aos meus clientes o serviço de hospedagem.

Levando em consideração que tenho um número de clientes que mantém meu servidor auto sustentável posso cobrar um valor muito baixo e oferecer todos recursos, segurança e suporte. Caso interessar é só entrar em contato. :)



Você pode publicar seus arquivos abrindo o Filezilla e conectando no seu web server. Tudo que você são os dados ftp, usuário e senha, em alguns caso a porta. Todas essas informações são fornecidas pelo seu provedor de hospedagem.


Eu criei uma subpasta chamada "_3D" e copiei todo o conteúdo da minha pasta de desenvolvimento incluindo a pasta models e js e colei na dentro da minha pasta "_3D". Se todo esse procedimento for feito corretamente e seu provedor de hospedagem oferecer suporte para a renderização 3D vai funcionar perfeitamente.

Clique aqui para ver o resultado final. 

Boa parte do conteúdo desse tutorial foi traduzida do site Quaint.

Qualquer dúvida estou a disposição. Deixem um comentário e irei responder o mais breve possível.

Abraço!!

Jey


quarta-feira, 7 de janeiro de 2015

Exportando do Blender para o Web GL usando Collada e three.js (Parte 2)


Essa é a segunda parte do tutorial onde vou mostrar como criar nosso próprio visualizador Web GL.

Na primeira parte eu falei sobre os programas necessários e scripts.


Configurando o cenário básico para o three.js

Eu vou começar meu código para Web GL com dois scripts de suporte. Nós podíamos criar um script externo mas trabalhar com o HTML e o Javascript juntos fica melhor para entender.

Criando o cenário

A sequencia para renderizar a cena no three.js. Vamos precisar da própria cena, a câmera e o renderizador.


var scene, camera, renderer;
    init();
    animate();
    function init() {

      scene = new THREE.Scene();
      var WIDTH = window.innerWidth,
          HEIGHT = window.innerHeight;

      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);  
      camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 10000);
      camera.position.set(50,150,100);
      scene.add(camera);
      window.addEventListener('resize', function() {
        var WIDTH = window.innerWidth,
            HEIGHT = window.innerHeight;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
      });
Primeiro, vamos criar a cena...

scene = new THREE.Scene();
      var WIDTH = window.innerWidth,
          HEIGHT = window.innerHeight;
Em seguida vamos criar o renderizador...

renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);
Three.js também oferece outros motores de renderização além do Web GL que podem ser usados em navegadores mais defasados que não suportam o Web GL. Porém tenha em mente que o Web GL é um dos métodos mais rápidos uma vez que ele usa a sua placa de vídeo.

Para obter o máximo de resolução eu defino o tamanho em window.innerWidth e no window.innerHeight respectivamente. Você pode também cortar calculando as vezes que quer diminuir a resolução por exemplo window.innerHeight/2. O resultado final pode parecer um pouco embaçado quando ampliado. Finally podemos adicionar o elemento renderizador em nosso documento HTML.

Eu defini o antialias (antiserrilhamento) para "true". É "false" por padrão. Ao ativar o antialias obtemos bordas um pouco mais suaves.

O próximo passo é criar a câmera.

Câmera e ação..!! 

Criar a câmera é realmente muito importante, caso contrário não vamos enxergar nada. Three.js oferece maneiras de criar uma câmera em perspectivas ou ortogonal, que pode ser interessante especialmente para animações em 2D. No nosso caso vamos usar a câmera em perspectiva.

Existem algumas configurações que você queira ajustar. A primeira é a configuração do campo de visão. Eu defini o valor para 45º (˜50 mm). O próximo item que devemos definir é a proporção da tela para Width/Height. Geralmente se você definir a proporção da tela de outra maneira pode aparentar esmagado. O próximo está definido para um número muito pequeno e a base para câmera para um número bem elevado. Em seguida vamos definir a posição e adicionar ao nosso cenário. Se desejar aprender mais sobre a câmera do three.js clique aqui. Cole esse código dentro da função init.

       camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 10000);
      camera.position.set(50,150,100);
      scene.add(camera);

Atualizar o Viewport sobre o redimensionamento

Vamos adicionar um pequeno trecho de código dentro da function init() para que sempre que o usuário redimensionar a janela de seu navegador a projeção do modelo seja atualizada.

window.addEventListener('resize', function() {
        var WIDTH = window.innerWidth,
            HEIGHT = window.innerHeight;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
      });

//more code to come...
}
Adicionando as funcionalidades do OrbitControls

Primeiramente temos que importar os scripts do OrbitControls. Cole o seguinte código depois de invocar o arquivo do three.min.js.

Adicione a linha de código a fim de rotacionar, agarrar or dar zoom dentro do view port. Cole este código dentro da função init() a fim de permitir as interações.

controls = new THREE.OrbitControls(camera, renderer.domElement);

Adicionando a função animate()

A fim de redesenhar a cena conforme rotacionamos a câmera vamos precisar adicionar o seguinte comando. Cole depois de acrescentar nossos controles e fora da função init().

  function animate() {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
      controls.update();
    }

Luzes

Agora é a hora de adicionar as luzes no nosso cenário. Considero o ideal iluminar em 3 pontos. Incluíndo a iluminação chave, a luz de fundo e uma luz de contorno para fazer o posicionamento mais facilmente. Eu adicionei uma pequena esfera para indicar onde as luzes irão ficar no espaço 3D. Sua opacidade muda de acordo com a força da luz. Tenha certeza de aplicar cores diferentes, para ter alguma variação na sua iluminação. Insira esse código depois do event listener e dentro da função init(). Os valores das cores nos código define a força dos pontos de luz.

  var light = new THREE.PointLight(0xfffff3, 0.8);
      light.position.set(-100,200,100);
      scene.add(light);

var sphereSize = 1; 
var pointLightHelper = new THREE.PointLightHelper( light, sphereSize ); 
scene.add( pointLightHelper );

      var light2 = new THREE.PointLight(0xd7f0ff, 0.2);
      light2.position.set(200,200,100);
      scene.add(light2);

var sphereSize = 1; 
var pointLightHelper2 = new THREE.PointLightHelper( light2, sphereSize ); 
scene.add( pointLightHelper2 );

var light3 = new THREE.PointLight(0xFFFFFF, 0.5);
      light3.position.set(150,200,-100);
      scene.add(light3);

var sphereSize = 1; 
var pointLightHelper3 = new THREE.PointLightHelper( light3, sphereSize ); 
scene.add( pointLightHelper3 );

Resultado provisório para um cenário básico criado em three.js

Tudo que temos é uma tela branca. Isso porquê se você não aplicar nenhuma cor de fundo específica o fundo será exibido branco como padrão. Futuramente vamos adicionar uma cúpula de céu com gradiente para deixar tudo mais bonito. Ainda não existe um modelo 3D para ser exibido, então vamos adicionar um.


Exportando um modelo Collada 3D do Blender

Primeiro vamos precisar de um modelo 3D que queremos ver. Se você não tiver nenhum fique a vontade para baixar o labirinto http://quaintproject.wordpress.com/download/. Se você estiver usando um modelo 3D próprio, certifique-se que o modelo esteja preparado para um Blender interno ao invés de ciclos. Isso garante que fique muito mais fácil exportar. Selecione o modelo que deseja exportar para o seu cenário. Certifique-se de sempre selecionar a superficie texturizada ao invés de colisão ou spec map. Também verifique se o seu "mapping" no UV-Channel está definido, a não ser que você esteja usando outros métodos de cartografia no lugar do UV-Map. Observe que há várias maneiras de mapear um objeto. Se você não sabe como fazer, assista alguns tutoriais do Blender Cookie.


Selecione o quadro e o mesh pressionando shift+botão direito, agora vamos no menu arquivo em seguida exportar, Collada. O Collada Exporter deve ser ativado em todas as últimas versões do Blender.

Collada é um formato de arquivo desenvolvido pela Khronos Group, assim como o Web GL. O consórcio industrial Khronos Group foi fundado em 2000, teve como objetivo estipular padrões para a área de multimídia. Entre seus famosos membros estão NVIDIA, Google, Apple, Unity 3D, Mozilla entre outros.

Dizem que o instituto Blender contratou um desenvolvedor pago para implementar o Collada Exporter no programa. Esta pode ser a razão por esse exportador ter tantas opções.

Estamos usando um Collada exportador. Você pode também experimentar usar o addon three.js para o Blender. O exportador está incluso na pasta de utilitários do three.js. Se você pretende usar o three.js para Blender, você vai precisar de um importador jason no seu javascript ao invés de Collada. Veja como exportar do Blender para o three.js aqui.


Salve o seu modelo dentro da pasta "models". Podemos chama-lo de dummy1. Isso vai facilitar nosso aprendizado. Claro que você pode escolher diferentes nomes, apenas lembre de mudar o nome escolhido no script a fim de exibir o mesh.

Selecione "Apply Modifiers" e "Selection Only". Logo abaixo em Textures Options escolha "Only Active UV layer", "include UV-Texture e "copy". Salve o arquivo ".dae".



Agora vamos codificar mais um pouco...

Vamos importar o script ColladaLoader, aquele que colocamos na pasta anteriormente. Cole o seguinte comando logo depois do script OrbitControls.

<script src="js/ColladaLoader.js"></script>
Adicione o seguinte código para carregar seu modela Collada incluindo as texturas. Você pode inserir o código depois das lâmpadas.

var loader = new THREE.ColladaLoader();

  loader.options.convertUpAxis = true;

  loader.load( 'models/dummy1.dae', function ( collada ) {
 //dummy1.dae

    var dae = collada.scene;

    var skin = collada.skins[ 0 ];

dae.position.set(0,0,0);//x,z,y- if you think in blender dimensions ;)
dae.scale.set(1.5,1.5,1.5);

scene.add(dae);

});
Atualize seu navegador e você verá algo semelhante a imagem abaixo.



Até o momento vamos ter o seguinte código:

<!--doctype html>


  Test von Web GL
  



  
  


  

    var scene, camera, renderer;

    init();
    animate();

    function init() {

      scene = new THREE.Scene();
      var WIDTH = window.innerWidth,
          HEIGHT = window.innerHeight;

      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);

      camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 10000);
      camera.position.set(50,150,100);
      scene.add(camera);

      window.addEventListener('resize', function() {
        var WIDTH = window.innerWidth,
            HEIGHT = window.innerHeight;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
      });

      var light = new THREE.PointLight(0xfffff3, 0.8);
      light.position.set(-100,200,100);
      scene.add(light);

var sphereSize = 1; 
var pointLightHelper = new THREE.PointLightHelper( light, sphereSize ); 
scene.add( pointLightHelper );

      var light2 = new THREE.PointLight(0xd7f0ff, 0.2);
      light2.position.set(200,200,100);
      scene.add(light2);

var sphereSize = 1; 
var pointLightHelper2 = new THREE.PointLightHelper( light2, sphereSize ); 
scene.add( pointLightHelper2 );

var light3 = new THREE.PointLight(0xFFFFFF, 0.5);
      light3.position.set(150,200,-100);
      scene.add(light3);

var sphereSize = 1; 
var pointLightHelper3 = new THREE.PointLightHelper( light3, sphereSize ); 
scene.add( pointLightHelper3 );

  var loader = new THREE.ColladaLoader();

  loader.options.convertUpAxis = true;

  loader.load( 'models/dummy1.dae', function ( collada ) {
 //dummy1.dae

    var dae = collada.scene;

    var skin = collada.skins[ 0 ];

dae.position.set(0,0,0);//x,z,y- if you think in blender dimensions ;)
dae.scale.set(1.5,1.5,1.5);

scene.add(dae);

      });

      controls = new THREE.OrbitControls(camera, renderer.domElement);

    }

    function animate() {

      requestAnimationFrame(animate);

      renderer.render(scene, camera);
      controls.update();

    }

  


-->

Solucionando problemas do three.js

Caso não renderize o modelo conforme o esperado, você pode resolver o problema limpando o histórico, cache e cookies do seu navegador.


Adicionando a grade e os eixos auxiliares no three.js

Adicione o código para a grade e os eixos auxiliares logo após o código do importador Collada. Os eixos auxiliares vai nos ajudar a nos orientar no espaço 3D. Basicamente apenas criamos uma nova variável e definimos o comprimento para 50. A posição dos eixos será definida pelo seu modelo que foi importado. Você pode também usar o arrow helper como alternativa. Depois vamos adicionar uma grade horizontal em nosso cenário. Você pode também adicionar grades verticais mas talvez precisem ficar mais altas. Crie mais uma variável. O tamanho da grade deve ser definido 100 para 10 passos. A posição é definida como centro do mundo. Podemos deixar de fazer essa linha de código, porque por padrão sua grade está posicionada (0/0/0) ao menos que você aplique outra posição.

Há duas cores que precisamos estabelecer. A primeira é para as linhas de grade que cruzam o marco zero. Eu decidi usar o mesmo tom acizentado. Veja o resultado na imagem abaixo.

var axes = new THREE.AxisHelper(50);
 axes.position = dae.position;
 scene.add(axes);

 var gridXZ = new THREE.GridHelper(100, 10);
 gridXZ.setColors( new THREE.Color(0xFFC0CB), new THREE.Color(0x8f8f8f) );
 gridXZ.position.set(0,0,0 );
 scene.add(gridXZ);


Encerro a segunda parte. Em

Qualquer dúvida fique a vontade para deixar um comentário.