.. _sec-transformacoes2: Desenhando com WebGL usando transformações ========================================== Em nossa introdução à programação com shaders usando WebGL desenhamos apenas formas básicas para introduzir os princípios de programação usando shaders. Para desenhar cenas mais complexas vamos fazer uso de transformações, como já começamos a explorar usando o canvas 2D do HTML5. Nessa aula, vamos continuar usando exemplos no espaço 2D para aplicação e composição de transformações afins na forma matricial, como introduzimos na aula anterior. Você deve se lembrar que, para gerar desenhos de objetos, em geral, é conveniente desenhar usando coordenadas centradas no objeto, por exemplo, um cubo ou uma esfera com seus vértices ao redor da origem. A ideia é, assim como em várias outras áreas da computação, utilizar modelos disponíveis em bibliotecas contendo vários objetos complexos e utilizar esses objetos para compor cenas aplicando transformações adequadas a cada objeto. Assim, ao invés de "desenhar uma cena", a geração de desenhos em computação gráfica se parece mais como um processo de composição de uma cena em 3D, como a montagem de um cenário fotográfico real antes de tirarmos uma foto. Isso permite também reutilizar um mesmo objeto e simplesmente "transformá-lo" para vários lugares da cena, como usar o mesmo modelo de automóvel em lugares diferentes em uma cena de trânsito, ou usar um modelo de árvore na composição de uma floresta ou parque. .. Outra vantagem dessa solução é aproveitar o buffer de vértices, que precisa ser carregado apenas uma vez pela GPU, em animações onde as transformações podem ser atualizadas pela própria GPU. Caso cada vértice precise ser recalculado pela CPU, mais tempo precisa ser gasto retransmitindo os dados para a GPU. Transformações afins: revisão ----------------------------- As transformações lineares e afins são essenciais na computação gráfica. Lembre-se que uma transformação linear é um mapeamento em um espaço vetorial que **preserva combinações lineares**. Tais transformações incluem rotações, escalas, cisalhamento (que "estica" retângulos em paralelogramos) e suas combinações. Da mesma forma, as transformações afins são transformações que **preservam as combinações afins**. Por exemplo, se :math:`P` e :math:`Q` são dois pontos e :math:`m` é seu ponto médio, e :math:`\mathbf{T}` é uma transformação afim, então o ponto médio de :math:`\mathbf{T}(p)` e :math:`\mathbf{T}(q)` é :math:`\mathbf{T}(m)`. Uma propriedade importante das transformações afins é que elas preservam linhas retas e paralelismo, embora não preservem ângulos. Vimos na aula passada como cada transformação básica pode ser representada de forma matricial. Hoje vamos ver que como essas transformações básicas podem ser compostas por meio de multiplicação de matrizes para realizar transformações complexas. Vamos começar vendo como realizar a animação de bolas transferindo o cálculo de translações para o vertex shader. Animação de círculos usando translação no vertex shader ------------------------------------------------------- Nesse exemplo vamos modificar o código descrito na `aula 8 - Interação e animação com WebGL `__ que ilustrou a `animação de bolas usando WebGL `__ recalculando a posição dos vértices para gerar cada imagem. Por exemplo, o trecho de código .. code:: JavaScript // desenha vertices gPosicoes = []; for (let i = 0; i < gObjetos.length; i++) gObjetos[i].atualize(delta); mostra que o buffer ``gPosicoes`` é limpo para que as novas posições sejam recalculadas e recarregadas no buffer durante a atualização de cada objeto. Depois de recalcular os vértices na CPU, esse novo array precisa ser enviado a GPU para serem desenhados. Uma alternativa seria, caso a gente possa representar os vértices de uma forma normalizada, por exemplo, centrados ao redor da origem, então podemos manter o buffer de vértices na GPU e, ao desenhar, pedir para o vertex shader transladar o centro para a posição desejada. A CPU então apenas precisa atualizar a nova posição de cada bola, e passar esse único ponto para a GPU redesenhar toda a bola usando os mesmos vértices normalizados! Vamos fazer isso introduzindo o uniforme ``uTranslation`` no código do vertex shader, como no trecho a seguir: .. code:: JavaScript var gVertexShaderSrc = `#version 300 es // aPosition é um buffer de entrada in vec2 aPosition; uniform vec2 uResolution; uniform vec2 uTranslation; in vec4 aColor; // buffer com a cor de cada vértice out vec4 vColor; // varying -> passado ao fShader void main() { vec2 escala1 = (aPosition + uTranslation) / uResolution; vec2 escala2 = escala1 * 2.0; vec2 clipSpace = escala2 - 1.0; gl_Position = vec4(clipSpace, 0, 1); vColor = aColor; } `; Observe que a GPU calcula a translação modificando a posição de cada vértice na função ``main`` pelo comando ``(aPosition + uTranslation)``. Isso poupa um pouco do trabalho da CPU. Com isso, a função de atualização dos vértices agora só precisa atualizar o centro do disco definido em ``this.pos`` e sua velocidade em ``this.vel``, como abaixo: .. code:: JavaScript this.atualize = function (delta) { this.pos = add(this.pos, mult(delta, this.vel)); let x, y; let vx, vy; [x, y] = this.pos; [vx, vy] = this.vel; // bateu? Altere o trecho abaixo para considerar o raio! if (x < 0) { x = -x; vx = -vx; }; if (y < 0) { y = -y; vy = -vy; }; if (x >= gCanvas.width) { x = gCanvas.width; vx = -vx; }; if (y >= gCanvas.height) { y = gCanvas.height; vy = -vy; }; // console.log(x, y, vx, vy); this.pos = vec2(x, y); this.vel = vec2(vx, vy); } Finalmente, a função de desenho precisa ser modificada para que, ao invés de atualizar os vértices, apenas modifique o uniforme ``uTranslate`` com a translação de cada objeto, como: .. code:: JavaScript function desenhe() { // calcula a diferença de tempo da última atualização let now = Date.now(); let delta = (now - gUltimoT) / 1000; // velocidade em segundos gUltimoT = now; // limpa o canvas gl.clear(gl.COLOR_BUFFER_BIT); // desenha posicoes, mas mantem os vértices for (let i = 0; i < gObjetos.length; i++) { let obj = gObjetos[i]; obj.atualize(delta); gl.uniform2f(gShader.uTranslation, obj.pos[0], obj.pos[1]); gl.drawArrays(gl.TRIANGLES, 3*obj.nv*i, 3*obj.nv); } window.requestAnimationFrame(desenhe); } O código completo desse exemplo, disponível abaixo, pode ser visto no `JSitor `__: .. raw:: html Mais animação: usando escala ---------------------------- Vamos dar mais um passo para transferir mais trabalho para a GPU e simplificar o trabalho da CPU. A função ``aproximeDisco()`` usada anteriormente desenha um disco de **raio arbitrário** (qualquer tamanho). Considere agora que essa função é capaz de aproximar um disco de raio **unitário** apenas, ao redor da origem, como abaixo: .. code:: JavaScript function aproximeDisco(ref=4) { let raio = 1.0; // primeiro um quadrado ao redor da origem let vertices = [ vec2(raio, 0), vec2(0, raio), vec2(-raio, 0), vec2(0, -raio), ]; // refinamento: adiciona 1 vértice em cada lado for (let i=1; i`__ e, utilizando essa nova função ``aproximeDisco()``, modifique o código restante para fazer cada disco ser renderizado usando transformações de escala realizadas pelo vertex shader. .. admonition:: Pausa para pensar ... Antes de prosseguir sua leitura, procure imaginar como seria o novo vertex shader. Em particular, como aplicar a transformação de escala ao buffer ``aPosition``. Para incluir uma transformação de escala não uniforme no vertex shader podemos utilizar mais um uniforme do tipo vec2, ``uScale``, como: .. code:: JavaScript var gVertexShaderSrc = `#version 300 es // aPosition é um buffer de entrada in vec2 aPosition; uniform vec2 uResolution; uniform vec2 uTranslation; uniform vec2 uScale; in vec4 aColor; // buffer com a cor de cada vértice out vec4 vColor; // varying -> passado ao fShader void main() { vec2 sPos = aPosition * uScale; vec2 escala1 = (sPos + uTranslation) / uResolution; vec2 escala2 = escala1 * 2.0; vec2 clipSpace = escala2 - 1.0; gl_Position = vec4(clipSpace, 0, 1); vColor = aColor; } `; Observe que a transformação de escala deve ser aplicada **antes** da translação. Após registrar o novo uniforme na criação dos shaders, além de arrumar alguns outros detalhes nas chamadas dessas funções, a nova função de desenho precisa passar a escala: .. code:: JavaScript function desenhe() { // atualiza o relógio let now = Date.now(); let delta = (now - gUltimoT) / 1000; gUltimoT = now; // limpa gl.clear(gl.COLOR_BUFFER_BIT); // atualiza uniformes sem modificar os vértices for (let i = 0; i < gObjetos.length; i++) { let obj = gObjetos[i]; obj.atualize(delta); gl.uniform2f(gShader.uTranslation, obj.pos[0], obj.pos[1]); gl.uniform2f(gShader.uScale, obj.raio * obj.sx, obj.raio * obj.sy); gl.drawArrays(gl.TRIANGLES, obj.nv * i * 3, 3 * obj.nv); } window.requestAnimationFrame(desenhe); } Observe que nesse trecho de código, o fator de escala aplicado é proporcional ao raio, mas também uma escala (sx, sy) independente em cada eixo que permite distorcer o disco como um ovo ou elipse. O código completo desse exemplo pode ser acessado no `JSitor `__. Mais animação: usando rotação ------------------------------ Desejamos agora simular um efeito de rotação do disco, introduzindo uma velocidade de rotação ``vrz`` e um ângulo ``theta`` que corresponde à orientação do disco. Vamos modificar o vertex shader para receber um ``uniform float uTheta`` que define um ângulo de rotação do disco. Como vimos na aula anterior, para aplicar uma rotação por ``uTheta`` ao redor da origem sobre um ponto (x, y), podemos aplicar a seguinte formula: :math:`novo_x = x * cos( uTheta ) + y * sin( uTheta )` :math:`novo_y = y * cos( uTheta ) - x * sin( uTheta )` Novamente, faça um *fork* do código do exemplo anterior para incluir rotação na nossa animação dos discos. .. admonition:: Outra pausa para pensar ... Antes de prosseguir sua leitura, procure imaginar como seria o novo vertex shader. Em particular, como aplicar a transformação de **rotação** a cada vértice do buffer ``aPosition``. O seguinte trecho de código define nosso vertex shader que recebe essas transformações de translação, escala e rotação e as aplica sobre cada vértice em ``aPosition``: .. code:: JavaScript var gVertexShaderSrc = `#version 300 es // aPosition é um buffer de entrada in vec2 aPosition; uniform vec2 uResolution; uniform vec2 uTranslation; uniform vec2 uScale; uniform float uTheta; in vec4 aColor; // buffer com a cor de cada vértice out vec4 vColor; // varying -> passado ao fShader void main() { float s = sin(uTheta); float c = cos(uTheta); vec2 sPos = aPosition * uScale; vec2 rPos = vec2( sPos.x * c + sPos.y * s, sPos.y * c - sPos.x * s ); vec2 escala1 = (rPos + uTranslation) / uResolution; vec2 escala2 = escala1 * 2.0; vec2 clipSpace = escala2 - 1.0; gl_Position = vec4(clipSpace, 0, 1); vColor = aColor; } `; Dessa vez, o vertex shader recebe um ``uniform float uTheta`` e usa suas próprias funções ``sin()`` e ``cos()`` para calcular a rotação de cada vértice. Observe nesse trecho de código que a rotação está sendo aplicada **após** o escalonamento e **antes** da translação. Procure imaginar o que acontece com o desenhe se essa ordem fosse diferente. Por exemplo, o que aconteceria se a rotação fosse aplicada antes do escalonamento? Experimente!! O restante do código é semelhante ao anterior. A velocidade angular precisa ser definida e armazenada em cada disco, além de ser atualizada também na geração de cada novo desenho. Observe que escolhemos mudar a direção de rotação toda vez que o disco bate em uma das paredes do canvas. O código completo desse exemplo é mostrado abaixo e pode também ser acessado no `JSitor `__. .. raw:: html Matrizes de transformação no WebGL ----------------------------------- Vimos na aula anterior que essas transformações básicas de translação, rotação, escala etc., podem ser representadas de forma matricial de forma muito mais compacta e elegante que a apresentada nos exemplos anteriores. Tipicamente, vamos utilizar matrizes de transformação para realizar as seguintes operações: * **Movimentação de objetos**: como acabamos de ver, mas combinando translações, rotações e outras transformações em um matriz. * **Mudança de coordenadas**: a mudança é usada quando objetos armazenados em relação a um sistema de referência precisam ser acessados em um outro sistema. Um caso importante disso é o de mapear objetos armazenados em um sistema de coordenadas padrão para um sistema de coordenadas associado à câmera (ou observador). * **Projeção**: Tais transformações são usadas para projetar objetos do espaço de desenho normalizado para a viewport e mapear o viewport para o canvas (ou janela). Veremos que as projeções perspectiva são mais gerais do que as transformações afins, pois podem não preservar o paralelismo. * **Mapeamento entre superfícies**: isso é útil quando texturas são mapeadas para superfícies de objetos como parte do processo de mapeamento de textura. A maneira de aplicação dessas transformações no WebGL é similar ao modelo de pen-plotter, onde os atributos do desenho (com cor e grossura da pena) precisam ser definidos **antes** do comando para desenhar. Assim as transformações também precisam ser definidas antes e, ao desenhar, o atual estado de transformações é aplicado automaticamente a todos os objetos desenhados. Nos exemplos anteriores, isso foi realizado atribuindo valores aos uniformes ``uTranslation``, ``uTheta`` e ``uScale`` antes de desenhar cada disco. Como as transformações são usadas para diferentes propósitos, programas gráficos usando o WebGL costumam trabalhar com três conjuntos de matrizes para organizar os seguintes tipos de transformação: * **Matriz Modelview**: Usada para transformar objetos na cena e para alterar as coordenadas em um forma mais fácil de trabalhar para o WebGL. É tipicamente usada para as tarefas de movimentação de objetos e mudanças de coordenadas. Pense em usar a matriz modelview para configurar a cena, definindo as posições, tamanhos e orientações dos objetos na cena. Nos exemplos anteriores, essa matriz combinaria as transformações para animação dos discos. * **Matriz de projeção**: lida com projeções paralela e perspectiva. Como o próprio nome indica, é utilizada para tarefas de projeção. Pense em usar a matriz de projeção para configurar a câmera, como se você fosse tirar uma foto da cena. Nos exemplos anteriores, poderíamos incluir na matriz de projeção a transformação das coordenadas normalizadas para o canvas. * **Matriz de textura**: Isso é usado para especificar como as texturas são mapeadas em objetos. Vamos adiar a discussão dessa matriz para o final do semestre, quando tratarmos de mapeamento de texturas. Como já vimos, há ainda mais uma transformação que não é tratada por essas matrizes. Esta é a transformação que mapeia o espaço normalizado para uma região do canvas, definido por ``gl.viewport()``, onde ``gl`` é o contexto gráfico ``webgl2`` do canvas do HTML5. É essencial entender como trabalhar e manipular essas matrizes de transformação para entender como os programas e sistemas gráficos que trabalham no modo imediato funcionam. Desenhando no WebGL usando matrizes ----------------------------------- Como último exemplo vamos agora juntar todas as transformações na forma de uma única matriz. Observe no exemplo com rotação que o cálculo do seno e cosseno é feito para cada vértice. Isso pode ser computacionalmente custoso, principalmente para objetos complexos com um grande número de vértices. Para balancear melhor essa carga computacional, pode ser conveniente passar apenas uma matriz de transformação para o vertex shader que combine todas as transformações. Para facilitar o processamento dessas matrizes utilizaremos a biblioteca ``MVnew.js``, proveniente do livro ``Interactive Computer Graphics``, de Angel e Shneier, que também oferece operações com vetores. Vamos também começar a usar pontos e vetores em 3D com coordenadas homogêneas. Assim, cada ponto ou vetor é do tipo ``vec4``. Para trabalhar no plano ``xy``, vamos considerar a coordenada ``z=0``. Isso implica também que as nossas matrizes de transformação serão do tipo ``mat4``, com :math:`4\times4` elementos. Essa forma também é mais próxima a representação padrão do GLSL. O trecho de código abaixo ilustra o vertex shader usando uma única matrix de transformação, passada à GPU pelo ``uniform mat4 uMatrix``. .. code:: JavaScript gVertexShaderSrc = `#version 300 es // aPosition é um buffer de entrada in vec2 aPosition; uniform mat4 uMatrix; in vec4 aColor; // buffer com a cor de cada vértice out vec4 vColor; // varying -> passado ao fShader void main() { gl_Position = vec4( uMatrix * vec4(aPosition,0,1) ); vColor = aColor; } `; .. Pilhas de matrizes ------------------ Na prática, para cada tipo de matriz, é conveniente manter uma pilha de matrizes. A matriz atual é a que está no topo da pilha. É a matriz que deve ser aplicada para desenhar os vértices. Esse mecanismo permite que você **salve a matriz atual** na pilha e a restaure posteriormente (desempilhando). Discutiremos o processo de implementação de transformações afins e de projeção mais tarde. Por enquanto, vamos apenas mostrar como trabalhar com essas matrizes no WebGL. Observe que o GLSL possui o tipo ``mat4`` (e ``mat3``) que facilita a manipulação de matrizes. O uniforme ``uMatrix`` é uma matriz que combina a matriz de projeção e a matriz modelView. A matriz de projeção mapeia pontos no canvas para o sistema de coordenadas normalizadas e pode ser definida como: .. code:: JavaScript let w = gCanvas.width; let h = gCanvas.height; let projection = mat4( 2/w, 0, 0, -1, 0, -2/h, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1 ); Observe que ``projection`` corresponde a representação matricial da transformação que mapeia um ponto no canvas para um ponto no espaço normalizado de cantos [-1,-1] e [+1,+1]. Por exemplo, calcule o resultado da multiplicação dessa matriz pelos pontos: * ``vec4(w/2, h/2, 0, 1)``, no centro do canvas; * ``vec4(0, 0, 0, 1)``, no canto superior esquerdo do canvas; e * ``vec4(w, h, 0, 1)``, no canto inferior direito do canvas. Para fazer a multiplicação, considere cada ponto como um vetor coluna. .. admonition:: Representação em ordem de colunas Na matemática e na computação, estamos acostumados a representar matrizes ordenadas por linhas, ou seja, na matriz [ [1,2], [3,4] ] o elemento [1,2] corresponde a 1a linha. O OpenGL (e o WegGL) adota uma representação de matrizes ordenadas por colunas. Nesse caso, o elemento [1, 2], corresponde à primeira coluna. Usando a biblioteca ``MVnew.js``, ao criar um ``mat4`` como nesse exemplo, passamos os elementos na ordem que estamos acostumados (por linhas) e a própria biblioteca transforma para a representação do WebGL. Resumindo, ao trabalhar com elementos do tipo ``vec`` e ``mat`` da biblioteca, você não precisa se preocupar com essa ordem. Mas se você utilizar arrays do JavaScript, então deve tomar cuidado! O seguinte trecho calcula a matriz de transformação modelView: .. code:: JavaScript var modelView = translate( obj.pos[0], obj.pos[1], 0 ); modelView = mult(modelView, rotateZ(obj.theta)); modelView = mult(modelView, scale(obj.sx*obj.raio, obj.sy*obj.raio, 1)); // combina projection e modelveiw var uMatrix = mult(projection, modelView); Assim como no exercício que fizemos usando transformações no canvas, a ordem para compor as transformações na matriz parece invertida. Mas por se tratar de multiplicação de matrizes, observe no código do vertex shader que :math:`gl_Position = uMatrix * aPosition` Podemos decompor ``uMatrix`` como: :math:`projection * T * R * S * Position`, onde ``T``, ``R`` e ``S`` correspondem as matrizes de translação, rotação e escala, respectivamente. Assim, a primeira transformação aplicada a um vértice é a de escala, seguida por rotação, translação e projeção, que é a ordem mais comum para pensar nesse problema. O código completo desse exemplo, disponível abaixo, pode ser visto no `JSitor `__: .. raw:: html Desenhando objetos diferentes usando VAO (opcional) --------------------------------------------------- Um objeto VAO (*Vertex Array Object*) é um recurso oferecido pelo WebGL 2.0 que facilita a organização dos buffers e seus atributos usados para desenhar. Uma aplicação típica de VAOs é quando temos objetos muitos distintos ou que, por algum motivo, escolhemos desenhar separadamente, usando um outro conjunto de buffers. Para dar um exemplo de aplicação de VAOs, vamos estender o exemplo anterior incluindo também quadrados em nossa animação. Um quadrado de lado unitário centrado na origem do sistema de coordenadas normalizado padrão do WebGL pode ser desenhado nos arrays ``gPosicoesQuads`` e ``gCoresQuads`` como ilustrado no trecho abaixo: .. code:: JavaScript function desenheQuad(cor) { let vt = [ // quadrado de lado 1 vec2( 0.5, 0.5), vec2(-0.5, 0.5), vec2(-0.5, -0.5), vec2( 0.5, -0.5) ]; let i, j, k; [i, j, k] = [0,1,2] gPosicoesQuads.push( vt[i] ); gPosicoesQuads.push( vt[j] ); gPosicoesQuads.push( vt[k] ); [i, j, k] = [0,2,3] gPosicoesQuads.push( vt[i] ); gPosicoesQuads.push( vt[j] ); gPosicoesQuads.push( vt[k] ); for (let i=0; i<6; i++) gCoresQuads.push(cor); } Um objeto ``Quad`` pode ser representado como: .. code:: JavaScript function Quad (x, y, vx, vy, vrz, sx, sy, cor) { desenheQuad(cor); this.pos = vec2(x, y); // posição this.vel = vec2(vx, vy); // velocidade this.theta = 0; // orientação this.vrz = vrz; // velocidade de rotação this.sx = sx; // escala em x this.sy = sy; // escala em y } Apesar de podermos usar os mesmos shaders do exemplo de animação com discos, desejamos criar buffers de posições e cores distintos para armazenar os quadrados, para desenhar discos e quadrados usando os mesmos shaders. Uma solução é criar VAOs para cada conjunto de buffers, como ilustrado no trecho a seguir. .. code: JavaScript // na função para crieShaders, antes de criar os buffers // vamos criar um VAO para organizar as informações dos buffers // VAO para os discos gShader.discosVAO = gl.createVertexArray(); gl.bindVertexArray( gShader.discosVAO ); // crie e configure os buffers e atributos para discos var bufPosDiscos = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, bufPosDiscos ); gl.bufferData(gl.ARRAY_BUFFER, flatten(gPosicoesDiscos), gl.STATIC_DRAW); var aPosDiscos = gl.getAttribLocation(gShader.program, "aPosition"); gl.vertexAttribPointer(aPosDiscos, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(aPosDiscos); // idem para o buffer de cores dos discos // ... // Criar o VAO para os quadrados gShader.quadsVAO = gl.createVertexArray(); gl.bindVertexArray( gShader.quadsVAO ); // carrega dados dos quads var bufPosQuads = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, bufPosQuads ); gl.bufferData(gl.ARRAY_BUFFER, flatten(gPosicoesQuads), gl.STATIC_DRAW); var aPosQuads = gl.getAttribLocation(gShader.program, "aPosition"); gl.vertexAttribPointer(aPosQuads, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(aPosQuads); // buffer de cores // // resolve os uniforms gShader.uMatrix = gl.getUniformLocation(gShader.program, "uMatrix"); gl.bindVertexArray(null); // apenas boa prática Observe que a parte de código para criação dos buffers para armazenar as cores e posições dos discos é muito semelhante ao código usado para armazenar as informações dos quadrados. A novidade desse trecho é o uso de VAOs, que são criados usando ``gl.createVertexArray()`` e habilitados por ``gl.bindVertexArray()``. Uma vez habilitado, os buffers e atributos criados em seguida são associados a esse VAO. Observe que, ao final do uso de um VAO, é considerado uma boa prática "limpar" o VAO fazendo ``gl.bindVertexArray(null)``. O uso de VAO facilita muito o desenho. Agora que os buffers foram criados e carregados, a função de desenho dos discos apenas precisa chamar ``gl.bindVertexArray(gShader.discosVAO)`` antes de desenhar, assim como a função de desenho dos quadrados só precisa chamar ``gl.bindVertexArray(gShader.quadsVAO)``. Por exemplo, podemos usar a seguinte função de desenho .. code:: JavaScript function desenheQuads(delta, projection) { // atualiza e desenha quads gl.bindVertexArray(gShader.quadsVAO); for (let i=0; i`__. .. raw:: html Onde estamos e para onde vamos? ------------------------------- Nessa aula vimos como podemos usar transformações para simplificar o processo de criar desenhos e animações usando WebGL. Aplicando as noções de geometria e álgebra linear que vimos nas últimas aulas vimos que essas transformações podem ser representadas de forma compacta e elegante por meio de matrizes. Na próxima aula, vamos continuar nossa jornada para aumentar o realismo de nossos desenhos introduzindo conceitos de geometria projetiva para tratar o problema de visualização de objetos em 3D. Exercícios ---------- 1. Baseado no `Problema 4 da 1a lista de exercícios do Prof. Mount `__. No distante planeta de Omicron Persei 8 (OP8), o viewport é definido com origem no canto superior direito. O eixo x aponta para baixo e o eixo y aponta para a esquerda. Seja :math:`w`` e :math:`h` a largura e altura como mostrado na figura abaixo. Considere a região retangular do desenho, em coordenadas normalizadas, com lados esquerdo e direito definidos por :math:`x_{min}` e :math:`x_{max}` e os lados inferior e superior definidos por :math:`y_{min}` e :math:`y_{max}`. .. figure:: ./figuras/a12/ex-omicron.png :alt: exercício Viewport do Omicron Persei :name: fig-Omicron :width: 80.0% :align: center * a) Defina a transformação para o viewport que mapeia um ponto :math:`P=(p_x, p_y)` na região de desenho normalizada para o ponto correspondente :math:`V = (v_x, v_y)` do viewport de OP8. Expresse sua transformação em duas equações :math:`v_x` = alguma função de :math:`p_x` e/ou :math:`p_y`. :math:`v_y` = alguma função de :math:`p_x` e/ou :math:`p_y`. mostre os passos da sua solução, não apenas a fórmula final. * b) Suponha que os pontos :math:`P` e :math:`V` sejam expressos em coordenadas homogêneas :math:`P = (p_x, p_y, 1)^T` e :math:`V = (v_x, v_y, 1)^T`. Expresse a transformação do item anterior com uma matrix :math:`M` de dimensão :math:`3 \times 3`, tal que :math:`V = M P`. 2. Baseado no `Problema 2 do Homework 2 do Prof. Mount `__. Considere os dois sistemas de coordenadas da figura abaixo. * a) expresse :math:`P` e :math:`\vec{w}` em coordenadas homogêneas em relação ao sistema :math:`F`. * b) expresse :math:`P` e :math:`\vec{w}` em coordenadas homogêneas em relação ao sistema :math:`G`. * c) forneça uma matrix :math:`M` de dimensão :math:`3 \times 3` que transforma um ponto em coordenadas homogêneas expressas em :math:`G` para coordenadas homogêneas em :math:`F`. Se desejar, pode expressar sua resposta em função de uma matriz inversa, sem necessidade de calcular a inversa. .. figure:: ./figuras/a12/ex-coords.png :alt: exercício Mudança de coordenadas. :name: fig-exCoordenadas :width: 45.0% :align: center 3. Baseado no `Problema 3 do Homework 2 do Prof. Mount `__. Vimos que o WebGL interpola linearmente as cores dos vértices para pintar o interior de um triângulo (conhecido como *Gouraud shading*). Considere o triângulo da figura abaixo, com vértices em (0,0), (1,0) e (1,1). Seja :math:`C_0`, :math:`C_1` e :math:`C_2` as cores RGB correspondentes para cada um desses vértices. Derive uma função :math:`C(x, y)` que interpola as cores linearmente e que possa ser usada para pintar um ponto :math:`Q = (x, y)` no interior do triângulo. Você pode expressar sua solução como uma fórmula ou na forma de pseudo-código. A cor deve ser uma função de x, y, e as 3 cores :math:`C_0`, :math:`C_1` e :math:`C_2`. No caso de usar fórmulas, mostre o seu trabalho intermediário para chegar na solução. .. figure:: ./figuras/a12/ex-afim.png :alt: exercício combinação afim de 3 cores. :name: fig-exCombinacaoAfim3Cores :width: 35.0% :align: center 4. Considere o código da animação ilustrada `nesse exercício no JSitor `__. Ao invés de discos, considere agora o seguinte triângulo em coordenadas normalizadas [(0.5, 0), (-0.5,-0.5), (-0.5, 0.5)]. Esse triângulo corresponde a uma forma que pode ser orientada na direção do movimento. Digamos que, em sua orientação inicial, definida pelos vértices, o bico do triângulo (vértice em (0.5, 0)) "aponta" para a direita. Escreva um programa que anime esse triângulo com uma velocidade constante aleatória (vx, vy), e que essa velocidade é refletida quando o objeto bate em uma parede do canvas. O triângulo deve ser rotacionado para que seu "bico" sempre aponte na direção do movimento. Use um fator de escala como (0.3, 0.2) para diminuir o tamanho do triângulo na animação. Para saber mais --------------- * Capítulo 4 do livro "Interactive Computer Graphics, 8th edition" de Angel e Shreiner. * Sobre o uso de matrizes: `WebGL2 2D Matrices `__. * Sobre VAO: `WegGL2 Fundamentals `__