0

Criação de um Campo Estelar - HTML com <canvas>

Maycon Fuzita
Maycon Fuzita

Semana passada tivemos o 4th May day, e postei um artigo de estudo demostrando como criar uma página ao estilo Star Wars!


Para os devs famintos por conhecimento segue o link do artigo:

https://web.digitalinnovation.one/articles/abertura-de-star-wars-em-html-com-canvas-css-3d-e-javascript?back=%2Fhome&page=1&order=oldest


Hoje venho complementar o primeiro artigo, falando sobre o plano de fundo, simulando uma viagem em um campo estelar! 


Será utilizado apenas uma tela HTML. Nenhuma biblioteca de terceiros ou matemática avançada necessária, apenas técnicas intuitivas para tornar o efeito fácil de entender e adaptar.


Neste exemplo teremos uma tela HTML com canvas onde são definidos e estilizados para se estenderem por toda a janela do navegador.


<! DOCTYPE html> 
 
 <meta charset = "utf-8" /> 
 
 <body 
 
  style = "position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; overflow: hidden; margin: 0; padding: 0;">  
 
  <canvas id = "canvas" style = "width: 100%; height: 100%; padding: 0; margin: 0;"></canvas> 
 
  <script> 
 
     ... 
 
   </script> 
 
 </body>



Controlando as coisas


Se quisermos desenhar qualquer coisa na tela, precisamos pegar seu elemento DOM e chamá-lo:


getContext("2d")


Isso nos dá acesso à API do canvas:


https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API


const canvas = document.getElementById ("canvas"); 
 
const c = canvas.getContext ("2d");



Quem quiser entender o que é o elemento DOM no HTML segue o link:


https://tableless.com.br/entendendo-o-dom-document-object-model/


Acompanhando o tamanho da janela


Uma tela HTML possui sua própria resolução interna que você define atribuindo valores às propriedades width e height.

Queremos que a tela sempre corresponda ao tamanho da janela, portanto, definimos a largura e a altura da tela de acordo. O tamanho da tela será útil em cálculos posteriores, então o manteremos nas variáveis wh.


let w; 
 
let h; 
 
 
 
 const setCanvasExtents = () => { 
 
  w = document.body.clientWidth; 
 
  h = document.body.clientHeight; 
 
  canvas.width = w; 
 
  canvas.height = h; 
 
 };
 
setCanvasExtents (); 
 
    
 
 window.onresize = () => { 
 
  setCanvasExtents (); 
 
 };



Enquanto isso, nós reagimos aos eventos de redimensionamento da janela, garantindo que a resolução da tela sempre corresponda ao tamanho da janela.


As estrelas


Cada estrela é representada por um objeto que detém xyz coordenadas. Criaremos 10k delas e as colocamos em uma matriz.

Nossas estrelas “vivem” em um sistema de coordenadas virtual semelhante a este:



O ponto central do sistema de coordenadas será desenhado no centro de nossa tela. Usamos o valor z para indicar a distância da estrela ao plano x e y.

Vamos utilizar a seguinte distribuição das estrelas no sistema de coordenadas:


·  X os valores variam de -800 a +800

·  Y os valores variam de -450 a +450

·  Z os valores variam de 0 a 1000


Esses intervalos não são totalmente arbitrários. Neste caso as estrelas serão geradas em um retângulo 16/9, próximo ao tamanho de monitores comuns - para que caiam no espaço visível quando visualizadas em tela cheia.

Distância máxima de 1000 unidades, porque? Isso apenas parecia certo em relação à largura e altura. Sinta-se à vontade para mexer nos números e observar as mudanças!



const makeStars = (count) => { 
 
  const out = []; 
 
    para (let i = 0; i <count; i ++) { 
 
      const s = { 
 
        x: Math.random () * 1600-800, 
 
        y: Math.random () * 900-450, 
 
        z: Math.random () * 1000 
 
      }; 
 
    out.push (s); 
 
 } 
 
  return out; 
 
 } 
  
 let stars = makeStars (10000);


Math.random () gera um número aleatório entre 0 (inclusivo) e 1 (exclusivo).

Portanto, Math.random() * 1600  irá gerar  um número entre 0 (inclusivo) e 1600 (exclusivo).

Ao compensá-lo, podemos mudar o alcance efetivo.

Assim, Math.random() * 1600–800 irá gerar um número aleatório entre -800 (inclusivo) e +800 (exclusivo).


Desenhando o fundo


Vamos desenhar as estrelas na tela. Queremos um fundo preto sobre o qual possamos colocar as estrelas.


 const clear = () => { 
 
 c.fillStyle = "black"; 
 
 c.fillRect (0, 0, canvas.width, canvas.height); 
 
};



A função clear apenas preenche toda a tela com a cor de fundo.


Desenhando uma única estrela


Também precisamos encontrar uma maneira de desenhar uma única estrela. Vamos colorir exatamente um pixel com um brilho entre 01. Quanto mais alto o valor, mais brilhante é o pixel.


 const putPixel = (x, y, brightness) => { 
 
 const intensity = brightness * 255; 
 
 const rgb = "rgb (" + intensity + "," + intensity + "," + intensity + ")"; 
 
 c.fillStyle = rgb; 
 
 c.fillRect (x, y, 1, 1); 
 
};


**Alterando os valores em c.fillRect (x, y, 1, 1) podemos visivelmente alterar o tamanho do ponto, neste caso temos um ponto de 1x1 pixel.


A função putPixel determina qual rgb deve ser a cor do brilho desejado e preenche um retângulo de um pixel com esse valor.

As cores RGB variam de 0–255 para cada componente de vermelho, verde e azul.

Quando todos os três componentes são iguais, estamos gerando uma cor cinza.

Ao dimensionar 255 com um valor entre 0.0 1.0 para todos os componentes, estamos efetivamente gerando uma cor em tons de cinza entre preto e branco.


Movendo as estrelas


Cada vez que desenhamos um quadro, queremos mover as estrelas em nosso campo estelar em nossa direção. Precisamos diminuir a coordenada z da estrela para isso.

Quando elas chegam perto de alcançar o plano da tela, nós as enviamos de volta, para que possam continuar vindo até nós novamente.


const moveStars = (distance) => { 

  const count = stars.length; 

  for (var i = 0; i <count; i++) { 

    const s = stars[i]; 

    s.z -= distance; 

    while (sz <= 1) { 

      s.z + = 1000; 
    } 
  } 
}



Velocidade


É importante também, controlar a velocidade com que a animação acontece. Poderíamos escolher setTimeout ou setInterval, mas a maneira mais conveniente de desenhar o próximo quadro é pedir ao navegador que nos chame na próxima atualização da tela, ao mesmo tempo que nos fornece um valor de clock de alta resolução.

O relógio nos diz quanto tempo passou desde o último quadro e, portanto, quanto tempo para progredir em nossa animação.

A função requestAnimationFrame faz exatamente o que precisamos. Para começar, pedimos ao navegador para chamar init e passar o valor do relógio de alta resolução.


let prevTime; 

const init = time => { 

  prevTime = time; 

  requestAnimationFrame (tick); 

}; 

... 

requestAnimationFrame (init);


Dentro de init é escondido o valor do clock em prevTime e solicitado que nossa função tick seja chamada em seguida.

A função tick determina quanto tempo passou desde a última atualização, move nossa animação para frente, desenha o estado atual de nossas estrelas na tela e em seguida, solicita ser chamada na próxima atualização de tela novamente.


const tick = time => { 

  let elapsed = time - prevTime; 

  prevTime = hora; 


  moveStars (elapsed * 0,1); 


  clear(); 


  const cx = w / 2; 

  const cy = h / 2; 


  const count = stars.length; 

  para (var i = 0; i <contagem; i ++) { 

    const star = stars [i]; 


    const x = cx + star.x / (star.z * 0,001); 

    const y = cy + star.y / (star.z * 0,001); 


    if (x <0 || x> = w || y <0 || y> = h) { 

      continue; 

    } 


    const d = (star.z / 1000.0) 

    const b = 1-d * d 



    putPixel (x, y, b); 

  } 


  requestAnimationFrame (tick); 

};


Movendo-se no tempo


A função tick determina quanto tempo passou desde que foi chamada pela última vez.

A quantidade de tempo que passou nos informa o quanto devemos mover as estrelas. Em um ambiente de 60 fps, serão chamados em incrementos de 1000/60 ~ 16.6 milissegundos, mais ou menos.


moveStars (elapsed * 0,1);


Pegamos esse número, escalando-o para 10% para que fique em torno de 1.6 e assim mova todas as estrelas para frente. Se você quiser estrelas mais rápidas ou mais lentas, dimensione de acordo! Teste alterado os valores!



Desenhando as estrelas em perspectiva


Em seguida, a tela é limpa e todas as estrelas são desenhadas em um loop. As coordenadas xy na tela são derivadas das coordenadas xyz da estrela.


Coordenadas


Precisamos fazer duas coisas para traduzir as coordenadas da estrela em coordenadas da tela.

Primeiro, precisamos introduzir a perspectiva, para que as estrelas mais distantes estejam mais perto do ponto de desaparecimento.

Em segundo lugar, precisamos traduzir nossas coordenadas virtuais em coordenadas de tela. As coordenadas da tela têm sua origem no canto superior esquerdo, não no centro da tela, portanto, precisamos levar isso em consideração.

Nossa perspectiva está centrada em nosso ponto de fuga. Uma estrela relativamente próxima de nós -200, -100, 100 deve aparecer no topo e à esquerda do centro. Uma estrela mais longe -200, -100, 900 também deve aparecer no topo e à esquerda, mas estar mais perto do ponto de fuga. O mesmo é verdade para todos os outros quadrantes: se uma estrela estiver mais perto, ela deve se traduzir em coordenadas visíveis mais perto de 0.


Você pode obter perspectiva escalando com amortecimento 1 / z *


Para alcançar esse efeito, podemos simplesmente dividir x e y por zQuanto mais alto z, mais perto chegamos de zero. Dividir por z direto causa uma queda bastante brusca. Mas precisamos facilitar um pouco para parecer convincentes. Portanto, dimensionamos o valor pelo qual dividimos. Utilizei 0.001Brinque com ele e escolha um valor de sua preferência.

Feito isso, deslocamos nossas coordenadas pela metade das dimensões da tela, empurrando efetivamente nossas coordenadas baseadas em zero para o centro da tela.


const x = cx + star.x / (star.z * 0,001); 

const y = cy + star.y / (star.z * 0,001); 



if (x <0 || x> = w || y <0 || y> = h) { 

  continue; 

}


Se a estrela cair fora da área visível, simplesmente continuamos com a próxima, efetivamente pulando a chamada de desenho para estrelas invisíveis.


Estrelas brilhantes


Queremos que as estrelas mais próximas de nós pareçam mais brilhantes e as distantes mais fracas. Sabemos que as estrelas mais distantes estão a z = 1000, portanto, podemos obter uma escala de brilho linear dividindo z por 1000. Vamos chamar esse valor d.

Quanto mais longe a estrela está, mais perto d está de 1.Quanto mais perto a estrela está de nós, mais perto d está de 0. Porque queremos que o brilho seja mais forte quando estiver perto de nós. Invertemos o valor calculando 1-d.

Isso nos dá um gradiente linear de brilho.

Um ajuste no cálculo, de modo que as estrelas pareçam mais brilhantes um pouco mais cedo do que linearmente. Em vez de subtrair d, subtraímos d².

1-d² é ligeiramente mais claro do que um gradiente linear.


const d = (star.z / 1000,0) 

const b = 1-d*d 



putPixel (x, y, b);


Resta chamar a função putPixel de desenho que coloca a estrela do pixel na tela.


Aí está. Um efeito 3D básico e nenhuma matemática 3D necessária. Divirta-se experimentando!


“May the force be with you, my friend!”


 

O código que desenvolvi estudando o artigo se encontra no meu GitHub:

 

https://github.com/Mayconfuzita86/star_wars_html


smiley+1 Gostou do artigo? Ajude-me clicando no ^ !!!! Obrigado!!!


rocketSUCESSO DEV!!!!


 

Referências:

Artigo original em inglês - https://betterprogramming.pub/fun-with-html-canvas-lets-create-a-star-field-a46b0fed5002


Sobre o 4th May: https://canaltech.com.br/entretenimento/por-que-o-star-wars-day-e-celebrado-no-dia-4-maio-40537/



 


0
0

Comentários (6)

1
Maycon Fuzita

Maycon Fuzita

18/05/2021 22:00

;) Muito obrigado galera!!!

1
Diego Silva

Diego Silva

13/05/2021 22:55

Maravilha cara! Valeu por compartilhar.

1
M

Marcelo Silva

13/05/2021 23:03

muito interessante, obrigado!

1
N

Neja

13/05/2021 23:23

Lembrando dos joguinhos espaciais dos anos 90....

1
Lucas Magalhães

Lucas Magalhães

14/05/2021 08:23

Bom dia. Que demais!

1
Leticia Alves

Leticia Alves

15/05/2021 00:55

Adorei,obrigada por compartilhar

Formado em Sistemas de Informação, buscando uma oportunidade, apaixonado por tecnologia, música e fotografia, tenho me empenhado e direcionado meus estudos para seguir a carreia de TI

Brasil