![Captura de tela 2025-08-25 153241 (1)](https://hackmd.io/_uploads/Sk68WEiKxe.png) <br/><br/> # Fundamentos Primeiro de tudo, você precisa saber que o **OpenGL** trabalha com dois processos, que são os **vértices** e os **pixels**. - Os ==vértices== servem pra quando você precisa pegar algum dado de um objeto, como: normais, coordenadas de texturas, etc. - Pra trabalhar com os vértices, escrevemos nosso código dentro do **Vertex Shader**. - Os ==pixels== servem pra você desenhar na tela, como os **Shaders**. - Pra trabalhar com os pixels, escrevemos nosso código dentro do **Fragment Shader**. ```mermaid graph LR Vertex-Shader --> Fragment-Shader ``` É basicamente duas seções dentro do código, ficou claro? Dito isto, vamos prosseguir... > [!Important] > Não se preocupe com items novos que forem surgindo, tudo será explicado à você no tempo certo, e por favor, não pule etapas se você é um iniciante. O conhecimento te leva longe e aprender exige paciência! <br/><br/> ## Tipos Vetoriais No **GLSL** (a linguagem de shaders do OpenGL), a maior parte dos cálculos é feita com vetores. Esses vetores representam desde **posições no espaço 3D**, até **cores**, **coordenadas de textura** e muito mais, veja abaixo. <br/><br/> ==float== É o tipo mais simples: um número decimal. **Exemplo:** ```c++ float intensidade = 0.8; ``` Pode representar brilho, intensidade de luz, opacidade, etc. <br/><br/> ## ==vec2== Um vetor com **2 componentes**: ```opengl vec2 coordenada = vec2(0.5, 0.8); ``` **Usos comuns:** - Coordenadas de textura (UV). - Posições em uma tela 2D. - Valores que precisam de dois números relacionados (ex: direção X e Y). <br/><br/> ## ==vec3== Um vetor com **3 componentes**: ```opengl vec3 cor = vec3(1.0, 0.0, 0.0); // vermelho ``` **Usos comuns:** - Cores RGB (vermelho, verde, azul). - Posições no espaço 3D (x, y, z). - Vetores de direção (ex: direção da luz). <br/><br/> ## ==vec4== Um vetor com **4 componentes**: ```opengl++ vec4 corComAlpha = vec4(1.0, 0.0, 0.0, 0.5); // vermelho 50% transparente ``` **Usos comuns:** - Cores RGBA (RGB + Alpha/Transparência). - Coordenadas homogêneas (x, y, z, w) para matrizes. - Passar dados extras (às vezes usamos o quarto valor só pra carregar informação). <br/><br/> > Depois entraremos mais a fundo em **matrizes**, não se preocupe. <br/><br/> ## Tipos de Qualificadores <br/><br/> No **GLSL**, as variáveis podem ter papéis diferentes dependendo de **onde vêm os dados** e **quando podem ser alterados**. Os dois mais comuns (e mais assustadores pra iniciantes) são ==uniform== e ==const==. <br/><br/> ==**const** (constante)== - Uma variável que **não pode mudar** durante a execução do shader. - O valor é fixo e já conhecido **quando você escreve o shader**. - Geralmente usado pra números mágicos ou valores que não vão ser alterados. <br/><br/> **Exemplo em GLSL:** ```opengl const float PI = 3.14159; void main() { float resultado = cos(PI); } ``` > [!Note]Resumo > **const** = imutável, fixo no código. <br/><br/> ## ==uniform== - É uma variável que vem **de fora do shader** (do programa OpenGL / UPBGE). - É a ponte entre o **Python** e o **shader GLSL**. - O valor é o mesmo para **todos os vértices ou fragmentos** de uma vez (não muda a cada pixel). **Exemplo em GLSL:** ```c+ uniform vec3 lightColor; void main() { gl_FragColor = vec4(lightColor, 1.0); } ``` E no Python (**UPBGE**), você poderia alterar esse valor: ```c shader.setUniformVec3("lightColor", [1.0, 0.8, 0.6]) ``` > [!Note]Resumo **uniform** = dado vindo de fora, igual pra todo mundo no shader. <br/><br/> ## **Outros que confundem.** Além de `uniform` e `const`, você ainda vai topar com: <br/><br/> ==attribute== (usado em **Vertex Shader**, até OpenGL 3.3) - Dados que mudam por vértice (posição, normal, UV). - No UPBGE moderno já virou `in`. <br/><br/> ==varying== - Variáveis que são interpoladas entre o **Vertex Shader** e o **Fragment Shader**. - Hoje em dia também virou `out` (no **vertex**) e `in` (no **fragment**) <br/><br/> > [color=#04B848]😮 Mas fica calmo que vou te explicar melhor, entendo que pode ser confuso pra entender de primeira, mas pense da seguinte forma: <br/><br/> Digamos que você precise de alguma informação de um **cubo**, talvez a posição dos vértices, as normais ou as coordenadas de textura. Somente quem possui essas informações pra lhe dar é o **Vertex Shader**, certo? Então pra obter esses atributos você pode chamar eles através do ==attribute==. <br/><br/> E se precisar desses dados mais tarde pra usar no **Fragment Shader**, use o ==varying== pra enviar esses dados de forma que ele entenda. <br/><br/> Agora ficou claro?! 😜 Veja os exemplos abaixo: <br/><br/> **Exemplo prático:** ```opengl // Vertex Shader attribute vec2 TexCoord; varying vec2 UV; void main() { UV = TexCoord.st; } ``` > [!Note] Comentando o código > No código acima estamos pegando o atributo de coordenada de textura com o ==attribute==. Em seguida enviamos uma variável chamada **UV** para o **Fragment Shader** através do ==varying==, mas essa variável ainda está vazia, então logo abaixo informamos dentro da função **main** que o valor dela será a coordenada da textura, legal né? ```opengl // Fragment Shader varying vec2 UV; void main() { vec4 texColor = texture2D(myTexture, UV); } ``` > [!Note] Comentando o código No código acima estamos trabalhando dentro do **Fragment Shader**, basicamente recebemos a variável **UV** que trabalhamos anteriormente com o ==varying== no **Vextex Shader** e utilizamos ela dentro de uma função qualquer, seja criativo ein! <br/><br/> ✅ **Comparando** | Tipo | Quem define? | Pode mudar? | Escopo | | :-------: | :--------: | :--------: | :--------: | | ==const== | O shader | ❌ Não | Fixo e imutável | | ==uniform== | Python/UPBGE | ✅ Sim | Igual pra todos os pixels | | ==attribute== | Blender/Engine | ✅ Sim | Muda a cada vértice | | ==varying== | Vertex Shader | ✅ Sim | Passa valores pros fragmentos (interpolados) | > Não tem problema se não entendeu a tabelinha acima, basta ter compreendido os exemplos nos códigos acima. Avante! <br/><br/> ## Acessando componentes Você pode acessar os valores do vetor de diferentes formas: ```c++ // Por posição vec3 pos = vec3(1.0, 2.0, 3.0); float x = pos.x; // 1.0 float y = pos.y; // 2.0 float z = pos.z; // 3.0 // Por cor vec3 cor = vec3(0.2, 0.5, 0.8); float r = cor.r; // 0.2 float g = cor.g; // 0.5 float b = cor.b; // 0.8 // Por textura vec2 uv = vec2(0.3, 0.7); float s = uv.s; // 0.3 float t = uv.t; // 0.7 ``` <br/><br/> **💡 Por que ==.st== ao invés de ==.xy== ?** <br/><br/> É somente para boas práticas: - ==.xyzw== → geralmente usado pra posições e vetores. - ==.rgba== → geralmente usado pra cores. - ==.stpq== → geralmente usado pra coordenadas de textura. Todos funcionam iguais, mas usar o sufixo certo deixa o código mais legível. **Exemplo:** ```opengl // Mais fácil de entender: vec2 uv = TexCoord.st; // Do que isso: vec2 uv = TexCoord.xy; ``` ## ✅ Resumindo > [!Note] **`float`** → número único. **`vec2`** → dois valores (UV, 2D). **`vec3`** → três valores (RGB, XYZ). **`vec4`** → quatro valores (RGBA, XYZ + W). ==.xy==, ==.rgb==, ==.st== são formas diferentes de acessar os mesmos dados, mas cada um tem um uso recomendado. <br/><br/> # Funções Pode parecer que não, mas diferente do python, toda a estrutura de um shader fica dentro de uma função principal que nomeamos por padrão de **main**. _Não tenha receio, não tem o que temer aqui_. Quando criamos essa função, declaramos ela como ==void== por que ela é a **função principal do shader**, `void` significa que ela não retorna nada, ou seja, não precisar usar `return` como é feito no python.<br/><br/> **Exemplo prático:** ```opengl //Fragment Shader float vec3 color = vec3(1.0, 0.0, 0.0); // Vermelho void main() { gl_FragColor = vec4(color, 1.0) // Tela vermelha } ``` Note que dentro do **Fragment Shader** toda saída final que deseja renderizar na tela deve utilizar variáveis de 4 componentes (**vec4**). <br/><br/> > [!Tip] Parabéns!! > Pode parecer que não, mas essa é a **base** e **estrutura** de todo o código em OpenGL, se você entendeu isso, já consegue ao menos entender 50% dos shaders. > > Mais pra frente você aprenderá mais sobre matrizes básicas (**mat2**, **mat3** e **mat4**) e modulos de equações básicos como: **dot**, **cross**, **lenght**, **normalize**, **distance**, **reflect**, **refract**. > > E também sobre funções de interpolação, como: **mix**, **step**, **smothstep**. <br/><br/> # Loop É muito importante sabermos trabalhar com **loops**, então vamos partir do pressuposto que você já conheça o loop utilizado no python. No OpenGL não tem segredo, veja: ```c //No python for i in range(8): print(i) // Ele imprime de 0 ao 8 //No OpenGL for (int i = 0; i < 9; i++) { // faz algo } ``` `int i = 0` significa que é um número inteiro e inicia em zero (0). `i < 9` significa que o loop vai acontecer enquanto o valor (i) for menor que 9; `i++` significa que a cada loop, será adicionado mais 1 no `i`; <br/><br/> # Integração ## Texturas Integradas O Blender/UPBGE nos fornece algumas texturas prontas para trabalhar em nossos shaders, são elas: ### bgl_RenderedTexture Nos fornece a imagem renderizada pelo Blender (framebuffer principal). > É a base pra quase todos os efeitos (correção de cor, distorções, pós-processamento em geral). ```opengl uniform sampler2D bgl_RenderedTexture; ``` <br/><br/> ### bgl_DepthTexture Nos fornece o mapa de profundidade da renderização. > Serve para efeitos que dependem da profundidade, tipo Depth of Field, Fog, SSAO. ```opengl uniform sampler2D bgl_DepthTexture; ``` <br/><br/> ## Funções Integradas Além das texturas, também tem funções específicas prontas para trabalhar com os shaders, são elas: <br/><br/> ### bgl_TextureCoordinateOffset Nos fornece um array com offsets prontos pros 9 pixels vizinhos (kernel 3x3) da coordenada atual. > Serve para convoluções e filtros como blur, sharpen, edge detection. ```opengl uniform vec2 bgl_TextureCoordinateOffset; ``` <br/><br/> ### bgl_RenderedTextureWidth Largura da tela (framebuffer). ```opengl uniform float bgl_RenderedTextureWidth; ``` <br/><br/> ### bgl_RenderedTextureHeight Altura da tela (framebuffer). ```opengl uniform float bgl_RenderedTextureHeight; ``` <br/><br/> ## Integrando variáveis Se quisermos transportar informações para o shader, podemos utilizar o python para esta tarefa, mas antes precisamos coletar o shader, veja: ```python python from bge import logic as g scene = g.getCurrentScene() variavel = 1.0 filter = scene.filterManager.getFilter(0) filter.setUniform1f("nome_da_variável_no_shader", variavel) ``` ```opengl glsl uniform float variavel; uniform vec2 variavel; uniform vec3 variavel; uniform vec4 variavel; uniform mat4 variavel; ``` O código acima basicamente importa os modulos do **blender engine**(bge), após isso setamos a cena e uma variável desejada. Com o básico feito, criamos uma variável ==filtro== que coletará os dados do nosso shader, para isso usamos o `filterManager.getFilter()` e passaremos o indice ==0== para informar que queremos o filtro do indice zero, cada shader ativo tem o seu indice. Agora podemos utilizar alguns parâmetros para escolher que tipo de informação queremos enviar, veja os demais logo abaixo: ```opengl setUniform1f("nome", variavel) //Envia um valor float setUniform2f("nome", variavel[0], variavel[1]) //Envia dois valores float(vec2) setUniform3f("nome", variavel[0], variavel[1], variavel[2]) //Envia três valores float(vec3) setUniformMatrix4("nome", matriz) //Envia uma Matriz(mat4) ``` Pode alterar o número que será enviado. <br/><br/> <br/><br/> ## Campo de Profundidade No **UPBGE** podemos obter o campo de profundidade atráves do ==bgl_DepthTexture==. <br/><br/> Mas pra que precisamos de uma **campo de profundidade**? É ele quem diz qual a distância cada pixel está em relação a camêra, mas de forma **comprimida** e não **linear**. Com ele você consegue fazer várias coisas em shaders e gráficos 3D, como: - Profundidade para efeitos de **pós-processamento** → desfoque de profundidade (Depth of Field), volumetria, neblina, etc. - **Mapa de profundidade visual** → debug, ver o que está perto e longe. - Calcular **normais** aproximadas → pegando vizinhos e fazendo cross(dx, dy). - **SSAO** (Ambient Occlusion) → sombrear cantos e buracos automaticamente, porque a distância entre pixels indica o quanto eles se escondem. - **SSR** (Screen Space Reflections) → reflexos na tela usando a posição real dos pixels. - Colisão ou efeitos físicos em shaders, tipo simular que objetos colidem com a cena sem precisar do mesh completo. <br/><br/> Então basicamente tudo que depende de “quanto cada pixel está distante da câmera”, passa pelo campo de produndidade (depth buffer). Se você utilizar o código abaixo no **UPBGE** através de um ==**filtro 2D**==, já consegue ter alguns resultados. ```opengl uniform sampler2D bgl_DepthTexture; void main() { float zdepth = texture2D(bgl_DepthTexture, gl_TexCoord[0].st).x; zdepth = pow(zdepth, 100.0); gl_FragColor = vec4(vec3(zdepth), 1.0); // mostra profundidade como cinza } ``` **Saída:** ![image](https://hackmd.io/_uploads/HyAig1Wcxl.png) > [!Note] Comentando o código linha por linha > ```opengl uniform sampler2D bgl_DepthTexture; ``` A linha acima é utilizada para importar a textura de profundidade, para isso utilizamos o ==bgl_DepthTexture==, e junto dele informamos ao OpenGL que é um ==uniform==, ou seja, que o sistema(**UPBGE**) quem vai nos fornecer a textura. **Mas o que é este** ==sampler2D== **?** **sampler2D** é um tipo de variável que representa uma textura 2D, como se fosse um “controle remoto” para uma textura. Ele não guarda a imagem em si, mas sabe onde a textura está na memória e permite que você pegue as cores dela. - `sampler` quer dizer que é uma variável usada para amostrar dados. - `2D` significa que essa textura tem duas dimensões (largura e altura). Pra usar um `sampler2D`, você chama a função texture2D() passando ele e as coordenadas UV, como faremos nas próximas linhas. <br/><br/> ```opengl void main() { ... } ``` Como já comentamos anteriormente, `void main()` é a nossa função principal que irá retornar nosso resultado. <br/><br/> ```c++ float zdepth = texture2D(bgl_DepthTexture, gl_TexCoord[0].st).x; ``` Acima, nesta linha criamos uma variável chamada **zdepth**. Dentro dela iremos utilizar o `texture2D()` para acessa a textura de profundidade, vamos passar **dois** argumentos: - **1° argumento:** A textura, no caso, ==bgl_DepthTexture== . - **2° argumento:** O mapeamento **UV**, para isso utilizaremos uma função do OpenGL chamada `gl_TexCoord` que é uma array com várias coordenadas de textura, pra usar a primeira coordenada informaremos que será `[0]` e também usaremos `.st` para coletar o X e Y. Ao final do `texture2D()` utilizaremos o ==.x==, ele é o responsável por coletar o canal `vermelho` da textura, ou pode usar ==.r==, dá na mesma. **Mas por que usar o canal VERMELHO?** Segundo as documentações da OpenGL, as informações de profundidade ficam hospedadas no canal vermelho da textura. **Resultado:** zdepth vai de 0 (perto) a 1 (longe), mas não é linear com a distância real da câmera. <br/><br/> ```opengl zdepth = pow(zdepth, 100.0); ``` A equação **pow** eleva o valor a uma potência, no caso utilizei `100`, pois ela “contrasta” a profundidade, deixando objetos distantes mais escuros ou mais visíveis, dependendo do que você quer mostrar. Sem isso, o Z pode parecer quase tudo branco ou escuro, dependendo do range da cena. <br/><br/> ```opengl gl_FragColor = vec4(vec3(zdepth), 1.0); ``` ==gl_FragColor== é a variável universal de renderização do OpenGL em todos os shaders, tudo o que passarmos dentro dela será renderizado pixel por pixel. :::danger **Atenção** O valor do `gl_FragColor` sempre deverá ser entregue em `vec4`, que significa os canais `vermelho`, `verde`, `azul` e `alpha`. ::: Então informamos o `vec4` que necessita de **4 componentes**, e, dentro dele informaremos a nossa variável `zdepth` para os **três** primeiros canais RGB, para isso utilizamos um `vec3()`. Ao final informamos o valor `1.0` para suprir o valor do `alpha`. <br/><br/> **Resultado final:** você vê a profundidade da cena como uma escala de preto e branco como visto na imagem acima. <br/><br/> **Como essa mágica acontece? Como o sistema entende o que deve ficar branco e o que deve ficar preto?** <br/><br/> Simples, o shader percorre em todos os pixels, cada pixel possuí 3 cores (RGB) e 1 alpha. Quando você coloca o mesmo valor no RGB, você está juntando todas as cores, então ficará branco ou preto, o que vai definir a intensidade será os diferentes valores entre 0 e 1, veja: (0.0, 0.0, 0.0) → Preto, pois está sem cor. (1.0, 1.0, 1.0) → Branco, pois juntou todas as cores. (0.5 0.5, 0.5) → Cinza. Então digamos que no pixel 1 a profudidade dele seja `0.4`, então esse valor será utilizado em todos os 3 canais, e isso percorre com valores diferentes do `zdepth` para todos os pixels, formando o mapa de profundidade que vocês aprenderam. <br/><br/> ![giphy](https://hackmd.io/_uploads/Sy6F2lbcxl.gif =30%x)<br/><br/> :::info **Observação** Apesar de termos agora o valor da profundidade de um pixel, não significa que temos a **distância** do pixel até o objeto. Isso impede que façamos outros shaders mais complexos, para pular este obstáculo devemos implementar o código, ou seja, simular a visão da câmera e calcular a distância. **A continuação do código será adicionada aqui no site tão logo, obrigado por chegar até aqui.** :::