/*
por Uğur Güney. 8 de março de 2014.
tradução em português feito por Gold Skull. 09 de março de 2024
Olá! Comecei a aprender GLSL há um mês.
A aceleração obtida ao usar a GPU para renderizar gráficos
em tempo real me surpreendeu. Se você deseja aprender como
escrever shaders, este tutorial escrito por um iniciante pode
ser um bom ponto de partida.
Por favor, corrija meus erros de codificação e gramática. :-)
*/
// Escolha o tutorial alterando o número e compilando o shader novamente.
#define TUTORIAL 0
/* LISTA DE TUTORIAIS
1 VOID. TELA EM BRANCO.
2 COR SÓLIDA
3 VETORES GLSL
4 MODELO DE COR RGB E COMPONENTES DE VETORES
5 O SISTEMA DE COORDENADAS
6 RESOLUÇÃO, TAMANHO DO QUADRO
7 TRANSFORMAÇÃO DE COORDENADAS
8 LINHAS HORIZONTAIS E VERTICAIS
9 VISUALIZANDO O SISTEMA DE COORDENADAS
10 MOVENDO O CENTRO DE COORDENADAS PARA O CENTRO DO QUADRO
11 FAZENDO A RAZÃO DE ASPECTO DO SISTEMA DE COORDENADAS 1.0
12 DISCO
13 FUNÇÕES
14 FUNÇÕES EMBUTIDAS: STEP
15 FUNÇÕES EMBUTIDAS: CLAMP
16 FUNÇÕES EMBUTIDAS: SMOOTHSTEP
17 FUNÇÕES EMBUTIDAS: MIX
18 ANTI-ALIASING COM SMOOTHSTEP
19 TRAÇADO DE FUNÇÕES
20 ADIÇÃO E SUBTRAÇÃO DE CORES
21 TRANSFORMAÇÕES DE COORDENADAS: ROTAÇÃO
22 TRANSFORMAÇÕES DE COORDENADAS: ESCALA
23 TRANSFORMAÇÕES DE COORDENADAS SUCESSIVAS
24 TEMPO, MOVIMENTO E ANIMAÇÃO
25 EFEITO PLASMA
26 TEXTURAS
27 ENTRADA DO MOUSE
28 ALEATORIEDADE
*/
#define PI 3.14159265359
#define TWOPI 6.28318530718
#if TUTORIAL == 1
// VOID. TELA EM BRANCO.
//
// A função “main” é chamada várias vezes por segundo para produzir
// o efeito do shader.
// O sistema tem como objetivo gerar uma velocidade de 60 quadros por segundo (FPS).
// No entanto, se o script GLSL for computacionalmente pesado, a taxa de quadros
// cai. (Você pode verificar a taxa de quadros na barra de informações abaixo
// da tela.)
//
// Como não estamos fazendo nada na função
// este shader resultará em uma tela preta.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
}
#elif TUTORIAL == 2
// COR SÓLIDA
//
// “fragColor” é a variável de saída do shader.
// Seu valor determina a imagem na tela.
// Este shader define seu valor para ser a cor amarela.
//
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
fragColor = vec4(1.0, 1.0, 0.0 ,1.0);
}
#elif TUTORIAL == 3
// VETORES GLSL
//
// A variável “fragColor” deve ser atribuída a um objeto vec4, que é composto
// por quatro números entre 0 e 1.
// Os três primeiros números determinam a cor, e o quarto número
// determina a opacidade.
// (Por enquanto, alterar o valor de transparência não terá efeito.)
// Um objeto de dados “vec4” pode ser construído fornecendo 4 argumentos do tipo float,
// ou um vec3 e um argumento do tipo float.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Aqui estamos separando as partes de cor e transparência
// do vec4 que representa os pixels.
vec3 color = vec3(0.0, 1.0, 1.0);
float alpha = 1.0;
vec4 pixel = vec4(color, alpha);
fragColor = pixel;
}
#elif TUTORIAL == 4
// MODELO DE COR RGB E COMPONENTES DE VETORES
//
// Após serem inicializados, os componentes dos vetores podem ser acessados usando
// a notação de ponto (“.”).
//
// RGB: en.wikipedia.org/wiki/RGB_color_model
// Uma cor é representada por três números (aqui no intervalo de [0.0, 1.0])
// O modelo pressupõe a adição de luzes vermelhas, verdes e azuis puras
// com intensidades específicas
//
// Se você, assim como eu, não tem habilidades de design e está tendo dificuldade
// em escolher um conjunto de cores agradável e coerente
// você pode usar um desses sites para escolher paletas de cores.
// Neles, você pode explorar diferentes conjuntos de cores.
// kuler.adobe.com/create/color-wheel
// colourlovers.com/palettes
// colourlovers.com/colors
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Brinque com esses números:
float redAmount = 0.6; // quantidade de vermelho
float greenAmount = 0.2; // quantidade de verde
float blueAmount = 0.9; // quantidade de azul
vec3 color = vec3(0.0);
// Aqui, inserimos apenas um argumento. É uma terceira maneira
// de construir vetores.
// “vec3(x)” é equivalente a vec3(x, x, x);
// Este vetor é inicializado como
// color.x = 0.0, color.y = 0.0; color.z = 0.0;
color.x = redAmount;
color.y = greenAmount;
color.z = blueAmount;
float alpha = 1.0;
vec4 pixel = vec4(color, alpha);
fragColor = pixel;
}
#elif TUTORIAL == 5
// O SISTEMA DE COORDENADAS
//
// "fragCoord", "fragment coordinate" é uma variável de entrada.
//
// Isso nos diz em qual pixel estamos na tela. O centro das coordenadas
// é o canto inferior esquerdo, e os valores das coordenadas aumentam em direção
// à direita e para cima.
//
// A função principal é executada para cada pixel na tela. Em
// cada chamada, o ‘gl_FracCoord’ possui as coordenadas do pixel
// correspondente.
//
// GPUs têm muitos núcleos, portanto, as chamadas de função para diferentes pixels
// podem ser calculadas em paralelo ao mesmo tempo.
// Isso permite velocidades mais altas do que o cálculo das cores dos pixels um
// por um em série na CPU. No entanto, também impõe uma restrição importante:
// O valor de um pixel não pode depender do valor de outro pixel.
// (os cálculos são feitos em paralelo e é impossível saber qual
// deles terminará antes do outro)
// O resultado de um pixel pode depender apenas das coordenadas do pixel (e
// de algumas outras variáveis de entrada)
// Esta é a diferença mais importante da programação de shaders.
// Voltaremos a este ponto repetidamente
//
// Vamos desenhar algo que não seja uma cor sólida.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// escolha duas cores
vec3 color1 = vec3(0.886, 0.576, 0.898);
vec3 color2 = vec3(0.537, 0.741, 0.408);
vec3 pixel;
// se a coordenada x for maior que 100, então plote a color1
// caso contrário, plote a color2
float widthOfStrip = 100.0;
if( fragCoord.x > widthOfStrip ) {
pixel = color2;
} else {
pixel = color1;
}
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 6
// RESOLUÇÃO, TAMANHO DO QUADRO
//
// Se você redimensionar o seu navegador ou entrar no modo de tela cheia e voltar
// você verá que a proporção da largura da primeira cor para a
// segunda cor muda com o tamanho da tela.
// Isso ocorre porque definimos a largura da faixa em número absoluto de
// pixels, em vez de como uma proporção da largura e altura da tela.
//
// Digamos que queiramos pintar as metades esquerda e direita com cores diferentes.
// Sem saber o número de pixels na horizontal, não podemos criar
// um shader que funcione em todos os tamanhos de quadro.
//
// Como podemos obter o tamanho da tela (largura e altura) em termos de
// número de pixels? Essa informação é fornecida na variável "iResolution".
// "iResolution.x" representa a largura do quadro, e
// "iResolution.y" representa a altura do quadro
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec3 color1 = vec3(0.741, 0.635, 0.471);
vec3 color2 = vec3(0.192, 0.329, 0.439);
vec3 pixel;
// uma forma simplificada para expressar a condicional “if” diz o seguinte:
// “se a coordenada x de um pixel for maior que a metade da
// largura da tela, então use a color1, caso contrário, use
// color2."
pixel = ( fragCoord.x > iResolution.x / 2.0 ) ? color1 : color2;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 7
// TRANSFORMAÇÃO DE COORDENADAS
//
// Em vez de trabalhar com coordenadas de tela, usar nosso próprio
// sistema de coordenadas é mais conveniente na maioria das vezes.
//
// Aqui, criaremos e utilizaremos um novo sistema de coordenadas chamado “r”, em vez
// das coordenadas absolutas da tela, chamadas de “fragCoord”. No sistema “r”
// as coordenadas x e y variam de 0 a 1. Para o eixo x, 0 representa o lado
// esquerdo e 1 o lado direito. Para o eixo y, 0 representa a parte inferior e 1 a
// parte superior.
//
// Usando o sistema “r”, vamos dividir a tela em 3 partes.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = vec2(fragCoord.x / iResolution.x,
fragCoord.y / iResolution.y);
// r é um vec2. Seu primeiro componente é a coordenada x do pixel dividida pela
// largura do quadro. E o segundo componente é a coordenada y do pixel
// dividida pela altura do quadro.
//
// Por exemplo, no meu laptop, o tamanho completo do quadro da tela é
// 1440 x 900. Portanto, iResolution é (1440.0, 900.0).
// A função principal deve ser executada 1.296.000 vezes para
// gerar um quadro.
// fragCoord.x terá valores entre 0 e 1439, e
// fragCoord.y terá valores entre 0 e 899, enquanto
// r.x e r.y terão valores entre [0,1].
vec3 color1 = vec3(0.841, 0.582, 0.594);
vec3 color2 = vec3(0.884, 0.850, 0.648);
vec3 color3 = vec3(0.348, 0.555, 0.641);
vec3 pixel;
// uma forma simplificada para expressar a condicional “if” diz o seguinte:
// "Se a coordenada x de um pixel for maior que a metade da
// largura da tela, use color1; caso contrário, use
// color2."
if( r.x < 1.0/3.0) {
pixel = color1;
} else if( r.x < 2.0/3.0 ) {
pixel = color2;
} else {
pixel = color3;
}
// pixel = ( r.x < 1.0/3.0 ) ? color1 : (r.x<2.0/3.0) ? color2: color3;
// mesmo código, linha única.
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 8
// LINHAS HORIZONTAIS E VERTICAIS
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = vec2( fragCoord.xy / iResolution.xy );
// Versão mais curta da mesma transformação de coordenadas.
// Por exemplo, "aVector.xy" é um novo vec2 formado pelos dois primeiros
// componentes de "aVector".
// E, quando o operador de divisão é aplicado entre vetores,
// cada componente do primeiro vetor é dividido pelo componente correspondente
// do segundo vetor.
// Portanto, a primeira linha deste tutorial é igual à primeira linha
// do tutorial anterior.
vec3 backgroundColor = vec3(1.0);
vec3 color1 = vec3(0.216, 0.471, 0.698);
vec3 color2 = vec3(1.00, 0.329, 0.298);
vec3 color3 = vec3(0.867, 0.910, 0.247);
// Comece definindo a cor de fundo. Se o valor do pixel
// não for substituído posteriormente, essa cor será exibida.
vec3 pixel = backgroundColor;
// se a coordenada x do pixel atual estiver entre esses valores,
// então coloque a cor 1.
// A diferença entre 0,55 e 0,54 determina
// a largura da linha.
float leftCoord = 0.54;
float rightCoord = 0.55;
if( r.x < rightCoord && r.x > leftCoord ) pixel = color1;
// uma maneira diferente de expressar uma linha vertical
// em termos de sua coordenada x e sua espessura:
float lineCoordinate = 0.4;
float lineThickness = 0.003;
if(abs(r.x - lineCoordinate) < lineThickness) pixel = color2;
// uma linha horizontal
if(abs(r.y - 0.6)<0.01) pixel = color3;
// observe como a terceira linha passa por cima das duas primeiras linhas.
// porque é a última que define o valor do "pixel".
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 9
// VISUALIZANDO O SISTEMA DE COORDENADAS
//
// Vamos usar um loop "for" e linhas horizontais e verticais para desenhar
// uma grade no centro das coordenadas.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = vec2( fragCoord.xy / iResolution.xy );
vec3 backgroundColor = vec3(1.0);
vec3 axesColor = vec3(0.0, 0.0, 1.0);
vec3 gridColor = vec3(0.5);
// comece definindo a cor de fundo. Se o valor do pixel
// não for sobrescrito posteriormente, essa cor será exibida.
vec3 pixel = backgroundColor;
// Desenha as linhas da grade
// Usamos "const" porque as variáveis de loop só podem ser manipuladas
// por expressões constantes.
const float tickWidth = 0.1;
for(float i=0.0; i<1.0; i+=tickWidth) {
// "i" é a coordenada da linha.
if(abs(r.x - i)<0.002) pixel = gridColor;
if(abs(r.y - i)<0.002) pixel = gridColor;
}
// Desenha os eixos
if( abs(r.x)<0.005 ) pixel = axesColor;
if( abs(r.y)<0.006 ) pixel = axesColor;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 10
// MOVENDO O CENTRO DE COORDENADAS PARA O CENTRO DO QUADRO
//
// Em vez de mapear a região [0, iResolution.x]x[0, iResolution.y] para
// [0,1]x[0,1], vamos mapeá-la para [-1,1]x[-1,1]. Dessa forma, a coordenada
// (0,0) não estará no canto inferior esquerdo da tela, mas sim no
// centro da tela.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
// [0, iResolution.x] -> [-0.5*iResolution.x, 0.5*iResolution.x]
// [0, iResolution.y] -> [-0.5*iResolution.y, 0.5*iResolution.y]
r = 2.0 * r.xy / iResolution.xy;
// [-0.5*iResolution.x, 0.5*iResolution.x] -> [-1.0, 1.0]
vec3 backgroundColor = vec3(1.0);
vec3 axesColor = vec3(0.0, 0.0, 1.0);
vec3 gridColor = vec3(0.5);
// comece definindo a cor de fundo. Se o valor do pixel
// não for sobrescrito posteriormente, essa cor será exibida.
vec3 pixel = backgroundColor;
// Desenha as linhas da grade
// Desta vez, em vez de percorrer um loop para cada pixel,
// usaremos a operação de módulo para obter o mesmo resultado
// com um único cálculo (graças a mikatalk)
const float tickWidth = 0.1;
if( mod(r.x, tickWidth) < 0.008 ) pixel = gridColor;
if( mod(r.y, tickWidth) < 0.008 ) pixel = gridColor;
// Draw the axes
if( abs(r.x)<0.006 ) pixel = axesColor;
if( abs(r.y)<0.007 ) pixel = axesColor;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 11
// FAZENDO A RAZÃO DE ASPECTO DO SISTEMA DE COORDENADAS 1.0
//
// Como vimos nos exemplos anteriores, obtemos retângulos
// em vez de quadrados ao plotar os sistemas de coordenadas.
// Isso ocorre porque atribuímos o mesmo intervalo numérico, [0,1],
// a diferentes distâncias físicas. Na verdade, a largura do quadro
// é maior do que sua altura.
// Portanto, para manter a proporção, não devemos mapear as distâncias
// reais [0, iResolution.x] e [0, iResolution.y] para o mesmo intervalo.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = vec2( fragCoord.xy - 0.5*iResolution.xy );
r = 2.0 * r.xy / iResolution.y;
// Em vez de dividir r.x por iResolution.x e r.y por iResolution.y,
// divida ambos por iResolution.y.
// Dessa forma, r.y estará no intervalo [-1.0, 1.0]
// e r.x dependerá do tamanho do quadro. Eu suponho que no modo não tela cheia
// rx estará no intervalo [-1.78, 1.78], e no modo tela cheia
// para o meu laptop, será no intervalo [-1.6, 1.6] (1440./900.=1.6)
vec3 backgroundColor = vec3(1.0);
vec3 axesColor = vec3(0.0, 0.0, 1.0);
vec3 gridColor = vec3(0.5);
vec3 pixel = backgroundColor;
// Desenhe linhas de grade
const float tickWidth = 0.1;
for(float i=-2.0; i<2.0; i+=tickWidth) {
// "i" é a coordenada da linha.
if(abs(r.x - i)<0.004) pixel = gridColor;
if(abs(r.y - i)<0.004) pixel = gridColor;
}
// Desenhe os eixos
if( abs(r.x)<0.006 ) pixel = axesColor;
if( abs(r.y)<0.007 ) pixel = axesColor;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 12
// DISCO
//
// Vamos desenhar discos
//
// Portanto, em GLSL, não damos um comando do tipo "desenhe este disco aqui com aquela
// cor". Em vez disso, usamos um comando indireto como "se a coordenada do pixel
// estiver dentro deste disco, coloque aquela cor para o pixel"
// Os comandos indiretos são um pouco contra-intuitivos até que você
// se acostume com essa forma de pensar.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
vec3 bgCol = vec3(0.3);
vec3 colBlue = vec3(0.216, 0.471, 0.698);
vec3 colRed = vec3(1.00, 0.329, 0.298);
vec3 colYellow = vec3(0.867, 0.910, 0.247);
vec3 pixel = bgCol;
// Para desenhar uma forma, devemos conhecer a expressão geométrica analítica
// dessa forma.
// Um círculo é o conjunto de pontos que têm a mesma distância do
// seu centro. Essa distância é chamada de raio.
// A distância do centro de coordenadas é sqrt(x*x + y*y)
// Fixar a distância como o raio fornecerá a fórmula para
// um círculo no centro de coordenadas
// sqrt(x*x + y*y) = radius
// Os pontos dentro do círculo, o disco, são dados por
// sqrt(x*x + y*y) < radius
// Elevando ambos os lados ao quadrado, obtemos
// x*x + y*y < radius*radius
float radius = 0.8;
if( r.x*r.x + r.y*r.y < radius*radius ) {
pixel = colBlue;
}
// Existe uma expressão abreviada para sqrt(v.x*v.x + v.y*v.y)
// de um vetor dado "v", que é "length(v)"
if( length(r) < 0.3) {
pixel = colYellow;
}
// Desenhe um disco cujo centro não está em (0,0).
// Digamos que o centro esteja em c: (c.x, c.y).
// A distância de qualquer ponto r: (r.x, r.y) até c é
// sqrt((r.x-c.x)^2+(r.y-c.y)^2)
// Defina um vetor de distância d: (r.x - c.x, r.y - c.y)
// Em GLSL, d pode ser calculado como "d = r - c".
// Assim como na divisão, a subtração de dois vetores é feita
// componente por componente.
// Então, length(d) significa sqrt(d.x^2+d.y^2)
// que é a fórmula da distância que estamos procurando.
vec2 center = vec2(0.9, -0.4);
vec2 d = r - center;
if( length(d) < 0.6) {
pixel = colRed;
}
// Essa mudança do centro da forma funciona para qualquer
// tipo de forma. Se você tiver uma fórmula em termos de r
// f(r) = 0, então f(r-c)=0 expressa a mesma forma geométrica,
// mas suas coordenadas estão deslocadas por c.
fragColor = vec4(pixel, 1.0);
}
// Observe como o disco mais recente é exibido e os anteriores
// são deixados para trás. Isso ocorre porque a última condição if
// altera o valor do pixel no final.
// Se as coordenadas do pixel se encaixarem em várias condições if,
// a última manipulação permanecerá e fragColor será definido para essa.
#elif TUTORIAL == 13
// FUNÇÕES
//
// Funções são ótimas para reutilização de código. Vamos colocar o código para desenhar discos
// em uma função e usar a função para desenhar.
// Existem muitas maneiras diferentes de escrever uma função para desenhar uma forma.
//
// Aqui temos uma função void que não retorna nada. Em vez disso,
// "pixel" é tratado como uma expressão "inout". "inout" é uma palavra-chave única do GLSL.
// Por padrão, todos os argumentos são argumentos "in". Isso
// significa que o valor da variável é fornecido ao escopo da função
// a partir do escopo em que a função é chamada.
// Uma variável "out" fornece o valor da variável da função
// para o escopo em que a função é chamada.
// Um argumento "inout" faz ambos. Primeiro, o valor da variável é
// enviado para a função como seu argumento. Em seguida, essa variável é
// processada dentro da função. Quando a função termina, o valor
// da variável é atualizado onde a função é chamada.
//
//
// Aqui, a variável "pixel" é inicializada com a cor de fundo na função "main".
// Em seguida, "pixel" é passado para a função "disk".
// Quando a condição if é satisfeita, o valor do "pixel"
// é alterado com o argumento "color". Se não for satisfeita, o
// "pixel" permanece inalterado e mantém seu valor anterior (que era o
// "bgColor").
void disk(vec2 r, vec2 center, float radius, vec3 color, inout vec3 pixel) {
if( length(r-center) < radius) {
pixel = color;
}
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
vec3 bgCol = vec3(0.3);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 pixel = bgCol;
disk(r, vec2(0.1, 0.3), 0.5, col3, pixel);
disk(r, vec2(-0.8, -0.6), 1.5, col1, pixel);
disk(r, vec2(0.8, 0.0), .15, col2, pixel);
fragColor = vec4(pixel, 1.0);
}
// Como você pode ver, as bordas dos discos têm curvas "irregulares", onde
// pixels individuais podem ser vistos. Isso é chamado de "aliasing". Ele ocorre
// porque os pixels têm tamanho finito e queremos desenhar uma forma contínua
// em uma grade descontínua.
// Existe um método para reduzir o aliasing. Isso é feito misturando as
// cores internas e externas na borda. Para alcançar isso,
// precisamos aprender algumas funções embutidas.
// E, novamente, observe a ordem das chamadas de função de disco e como elas são
// desenhadas uma sobre a outra. Cada função de disco manipula
// a variável de pixel. Se um pixel for manipulado por várias funções de disco,
// o valor da última é enviado para fragColor.
// Neste caso, os valores anteriores são completamente sobrescritos.
// O valor final depende apenas da última função que manipulou o pixel.
// Não há misturas entre camadas.
#elif TUTORIAL == 14
// FUNÇÕES EMBUTIDAS: STEP
//
// A função "step" é a função degrau de Heaviside :-)
// en.wikipedia.org/wiki/Heaviside_step_function
//
// f(x0, x) = {1 x>x0,
// {0 x<x0
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(0.0); // preto
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 pixel = bgCol;
float edge, variable, ret;
// Divida a tela em cinco partes horizontalmente
// para diferentes exemplos
if(r.x < -0.6*xMax) { // Parte I
variable = r.y;
edge = 0.2;
if( variable > edge ) { // Se a "variable" for maior que "edge"
ret = 1.0; // retorne 1.0
} else { // Se a "variable" for menor que "edge"
ret = 0.0; // retorne 0.0
}
}
else if(r.x < -0.2*xMax) { // Parte II
variable = r.y;
edge = -0.2;
ret = step(edge, variable); // A função de passo é equivalente ao
// bloco "if" da Parte I
}
else if(r.x < 0.2*xMax) { // Parte III
// A função "step" retorna 0.0 ou 1.0.
// "1.0 - step" inverte o resultado.
ret = 1.0 - step(0.5, r.y); // Espelhe a função "step" em torno da borda.
}
else if(r.x < 0.6*xMax) { // Parte IV
// Se a coordenada y for menor que -0.4, o resultado é 0.3.
// Se a coordenada y for maior que -0.4, o resultado é 0.3 + 0.5 = 0.8.
ret = 0.3 + 0.5*step(-0.4, r.y);
}
else { // Parte V
// Combine duas funções "step" para criar uma lacuna.
ret = step(-0.3, r.y) * (1.0 - step(0.2, r.y));
// "1.0 - ret" criará uma lacuna.
}
pixel = vec3(ret); // Crie uma cor a partir do valor de retorno.
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 15
// FUNÇÕES EMBUTIDAS: CLAMP
//
// A função “clamp” satura a entrada abaixo e acima dos limites especificados.
// f(x, min, max) = { max x>max
// { x max>x>min
// { min min>x
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
vec2 p = vec2(fragCoord.xy / iResolution.xy);
// use [0,1] sistema de coordenadas para este exemplo
vec3 bgCol = vec3(0.0); // preto
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 pixel = bgCol;
float edge, variable, ret;
// divida a tela em quatro partes horizontalmente para diferentes
// exemplos
if(p.x < 0.25) { // Parte I
ret = p.y; // o valor de brilho é atribuído à coordenada y
// isso criará um gradiente
}
else if(p.x < 0.5) { // Parte II
float minVal = 0.3; // implementação do clamp
float maxVal = 0.6;
float variable = p.y;
if( variable<minVal ) {
ret = minVal;
}
if( variable>minVal && variable<maxVal ) {
ret = variable;
}
if( variable>maxVal ) {
ret = maxVal;
}
}
else if(p.x < 0.75) { // Parte III
float minVal = 0.6;
float maxVal = 0.8;
float variable = p.y;
ret = clamp(variable, minVal, maxVal);
}
else { // Parte IV
float y = cos(5.*TWOPI*p.y); // oscilar entre +1 e -1
// 5 vezes, verticalmente
y = (y+1.0)*0.5; // mapear [-1,1] para [0,1]
ret = clamp(y, 0.2, 0.8);
}
pixel = vec3(ret); // criar uma cor a partir do valor de retorno.
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 16
// FUNÇÕES EMBUTIDAS: SMOOTHSTEP
//
// A função "smoothstep" é semelhante à função "step", mas, em vez de um
// salto abrupto de 0 para 1 na borda, ela cria uma transição suave
// dentro de um intervalo dado.
// en.wikipedia.org/wiki/Smoothstep
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
vec2 p = vec2(fragCoord.xy / iResolution.xy);
// use o sistema de coordenadas [0,1] para este exemplo
vec3 bgCol = vec3(0.0); // preto
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // vermelho
vec3 col3 = vec3(0.867, 0.910, 0.247); // amarelo
vec3 pixel = bgCol;
float edge, variable, ret;
// divida a tela em quatro partes horizontalmente para diferentes
// exemplos
if(p.x < 1./5.) { // Parte I
float edge = 0.5;
ret = step(edge, p.y); // Função de etapa simples
}
else if(p.x < 2./5.) { // Parte II
// linearstep (não é uma função embutida)
float edge0 = 0.45;
float edge1 = 0.55;
float t = (p.y - edge0)/(edge1 - edge0);
// quando p.y == edge0 => t = 0.0
// quando p.y == edge1 => t = 1.0
// O lado direito da equação é uma função linear de y
// Portanto, entre edge0 e edge1, t tem uma transição linear
// entre 0.0 e 1.0
float t1 = clamp(t, 0.0, 1.0);
// t terá valores negativos quando t<edge0 e
// t terá valores maiores que 1.0 quando t>edge1
// mas queremos que ele esteja restrito entre 0.0 e 1.0
// portanto, limitamos!
ret = t1;
}
else if(p.x < 3./5.) { // Parte III
// Implementação do smoothstep
float edge0 = 0.45;
float edge1 = 0.55;
float t = clamp((p.y - edge0)/(edge1 - edge0), 0.0, 1.0);
float t1 = 3.0*t*t - 2.0*t*t*t;
// A interpolação anterior era linear. Visualmente, ela não
// proporciona uma transição suave atraente.
// Para obter suavidade, implemente um polinômio cúbico de Hermite.
// 3*t^2 - 2*t^3
ret = t1;
}
else if(p.x < 4./5.) { // Parte IV
ret = smoothstep(0.45, 0.55, p.y);
}
else if(p.x < 5./5.) { // Parte V
// smootherstep, uma sugestão de Ken Perlin
float edge0 = 0.45;
float edge1 = 0.55;
float t = clamp((p.y - edge0)/(edge1 - edge0), 0.0, 1.0);
// 6*t^5 - 15*t^4 + 10*t^3
float t1 = t*t*t*(t*(t*6. - 15.) + 10.);
ret = t1;
// transição mais rápida e ainda mais suave
// mas computacionalmente mais complexa.
}
pixel = vec3(ret); // crie uma cor a partir do valor de retorno.
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 17
// FUNÇÕES EMBUTIDAS: MIX
//
// Um *shader* pode ser criado primeiro construindo partes individuais
// e depois combinando-as.
// Existem diferentes maneiras de combinar partes diferentes.
// No exemplo anterior de disco, diferentes discos foram desenhados um sobre o outro.
// Não houve mistura de camadas. Quando os discos se sobrepõem,
// apenas o último é visível.
//
// Vamos aprender a misturar diferentes tipos de dados (neste caso, vec3's
// que representam cores)
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec3 bgCol = vec3(0.3);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // vermelho
vec3 col3 = vec3(0.867, 0.910, 0.247); // amarelo
vec3 ret;
// Divida a tela em quatro partes horizontalmente para diferentes
// exemplos
if(p.x < 1./5.) { // Parte I
// Implementação da função de mistura (mix)
float x0 = 0.2; // Primeiro item a ser misturado
float x1 = 0.7; // Segundo item a ser misturado
float m = 0.1; // Quantidade de mistura (entre 0.0 e 1.0)
// Experimente com esse número
// m = 0.0 significa que a saída é totalmente x0
// m = 1.0 significa que a saída é totalmente x1
// 0.0 < m < 1.0 é uma mistura linear de x0 e x1
float val = x0*(1.0-m) + x1*m;
ret = vec3(val);
}
else if(p.x < 2./5.) { // Parte II
// Teste todos os valores possíveis de mistura
float x0 = 0.2;
float x1 = 0.7;
float m = p.y;
float val = x0*(1.0-m) + x1*m;
ret = vec3(val);
}
else if(p.x < 3./5.) { // Parte III
// Use a função de mistura (mix)
float x0 = 0.2;
float x1 = 0.7;
float m = p.y;
float val = mix(x0, x1, m);
ret = vec3(val);
}
else if(p.x < 4./5.) { // Parte IV
// Misture cores em vez de números
float m = p.y;
ret = mix(col1, col2, m);
}
else if(p.x < 5./5.) { // Parte V
// Combine smoothstep e mix para transições de cor
float m = smoothstep(0.5, 0.6, p.y);
ret = mix(col1, col2, m);
}
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 18
// ANTI-ALIASING COM SMOOTHSTEP
//
float linearstep(float edge0, float edge1, float x) {
float t = (x - edge0)/(edge1 - edge0);
return clamp(t, 0.0, 1.0);
}
float smootherstep(float edge0, float edge1, float x) {
float t = (x - edge0)/(edge1 - edge0);
float t1 = t*t*t*(t*(t*6. - 15.) + 10.);
return clamp(t1, 0.0, 1.0);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(0.3);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 pixel = bgCol;
float m;
float radius = 0.4; // Aumente isso para ver o efeito melhor
if( r.x < -0.5*xMax ) { // Parte I
// Sem interpolação, mas com aliasing
m = step( radius, length(r - vec2(-0.5*xMax-0.4,0.0)) );
// Se a distância do centro for menor que o raio,
// então o valor de mistura (mix) é 0.0
// caso contrário, o valor de mistura (mix) é 1.0
pixel = mix(col1, bgCol, m);
}
else if( r.x < -0.0*xMax ) { // Parte II
// linearstep (interpolação de primeira ordem, linear)
m = linearstep( radius-0.005, radius+0.005, length(r - vec2(-0.0*xMax-0.4,0.0)) );
// O valor de mistura é interpolado linearmente quando a distância até o centro
// é 0,005 menor e maior que o raio.
pixel = mix(col1, bgCol, m);
}
else if( r.x < 0.5*xMax ) { // Parte III
// smoothstep (interpolação cúbica)
m = smoothstep( radius-0.005, radius+0.005, length(r - vec2(0.5*xMax-0.4,0.0)) );
pixel = mix(col1, bgCol, m);
}
else if( r.x < 1.0*xMax ) { // Parte IV
// smootherstep (interpolação de sexta ordem)
m = smootherstep( radius-0.005, radius+0.005, length(r - vec2(1.0*xMax-0.4,0.0)) );
pixel = mix(col1, bgCol, m);
}
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 19
// TRAÇADO DE FUNÇÕES
//
// Sempre é útil ver os gráficos de funções no sistema de coordenadas cartesianas
// para entender o que estão fazendo com precisão.
//
// Vamos plotar algumas funções unidimensionais!
//
// Se o valor de y é uma função f do valor de x, a expressão da relação entre eles é: y = f(x)
// Em outras palavras, o gráfico de uma função é o conjunto de todos os pontos
// que satisfazem a expressão: y - f(x) = 0
// Esse conjunto tem espessura zero e não pode ser visto.
// Em vez disso, usamos o conjunto de (x, y) que satisfaz: -d < y - f(x) < d
// ou seja, abs(y - f(x)) < d
// onde d é a espessura (a espessura é na direção y)
// Devido às propriedades da função absoluta, a condição
// abs(y - f(x)) < d é equivalente à condição:
// abs(f(x) - y) < d
// Usaremos esta última para traçar funções. (na anterior,
// precisávamos negar a função que queríamos plotar)
//
float linearstep(float edge0, float edge1, float x) {
float t = (x - edge0)/(edge1 - edge0);
return clamp(t, 0.0, 1.0);
}
float smootherstep(float edge0, float edge1, float x) {
float t = (x - edge0)/(edge1 - edge0);
float t1 = t*t*t*(t*(t*6. - 15.) + 10.);
return clamp(t1, 0.0, 1.0);
}
void plot(vec2 r, float y, float lineThickness, vec3 color, inout vec3 pixel) {
if( abs(y - r.y) < lineThickness ) pixel = color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
vec3 bgCol = vec3(1.0);
vec3 axesCol = vec3(0.0, 0.0, 1.0);
vec3 gridCol = vec3(0.5);
vec3 col1 = vec3(0.841, 0.582, 0.594);
vec3 col2 = vec3(0.884, 0.850, 0.648);
vec3 col3 = vec3(0.348, 0.555, 0.641);
vec3 pixel = bgCol;
// Desenha linhas da grade
const float tickWidth = 0.1;
for(float i=-2.0; i<2.0; i+=tickWidth) {
// "i" é a coordenada da linha.
if(abs(r.x - i)<0.004) pixel = gridCol;
if(abs(r.y - i)<0.004) pixel = gridCol;
}
// Desenhe os eixos
if( abs(r.x)<0.006 ) pixel = axesCol;
if( abs(r.y)<0.007 ) pixel = axesCol;
// Desenhe as funções
float x = r.x;
float y = r.y;
// Funções em rosa
// y = 2*x + 5
if( abs(2.*x + .5 - y) < 0.02 ) pixel = col1;
// y = x^2 - .2
if( abs(r.x*r.x-0.2 - y) < 0.01 ) pixel = col1;
// y = sin(PI x)
if( abs(sin(PI*r.x) - y) < 0.02 ) pixel = col1;
// Funções em azul, as variações da função degrau
// (as funções são escaladas e traduzidas verticalmente)
if( abs(0.25*step(0.0, x)+0.6 - y) < 0.01 ) pixel = col3;
if( abs(0.25*linearstep(-0.5, 0.5, x)+0.1 - y) < 0.01 ) pixel = col3;
if( abs(0.25*smoothstep(-0.5, 0.5, x)-0.4 - y) < 0.01 ) pixel = col3;
if( abs(0.25*smootherstep(-0.5, 0.5, x)-0.9 - y) < 0.01 ) pixel = col3;
// Funções amarelas
// Tenha uma função que trace funções :-)
plot(r, 0.5*clamp(sin(TWOPI*x), 0.0, 1.0)-0.7, 0.015, col2, pixel);
// Curva de sino em torno de -0.5
plot(r, 0.6*exp(-10.0*(x+0.8)*(x+0.8)) - 0.1, 0.015, col2, pixel);
fragColor = vec4(pixel, 1.0);
}
// No futuro, podemos usar este framework para visualizar gráficos de funções
// e projetar e encontrar funções de acordo com nossas preferências.
// Na verdade, usar ferramentas como Mathematica, Matlab, matplotlib, etc. para plotar funções
// é muito mais prático. No entanto, eles exigem uma tradução das funções
// do GLSL para a linguagem deles. Aqui, podemos plotar as implementações nativas
// das funções do GLSL.
#elif TUTORIAL == 20
// ADIÇÃO E SUBTRAÇÃO DE CORES
//
// Como desenhar uma forma sobre outra e como as camadas
// abaixo afetam as camadas superiores?
//
// Nas funções de desenho de formas anteriores, definimos o valor do pixel
// a partir da função. Desta vez, a função de forma apenas retornará um valor
// de ponto flutuante entre 0,0 e 1,0 para indicar a área da forma. Mais tarde,
// esse valor pode ser multiplicado por alguma cor e usado para determinar a cor
// final do pixel.
// Uma função que retorna 1,0 dentro da área do disco
// retorna 0,0 fora da área do disco
// e tem uma transição suave no raio
float disk(vec2 r, vec2 center, float radius) {
float distanceFromCenter = length(r-center);
float outsideOfDisk = smoothstep( radius-0.005, radius+0.005, distanceFromCenter);
float insideOfDisk = 1.0 - outsideOfDisk;
return insideOfDisk;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 black = vec3(0.0);
vec3 white = vec3(1.0);
vec3 gray = vec3(0.3);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // vermelho
vec3 col3 = vec3(0.867, 0.910, 0.247); // amarelo
vec3 ret;
float d;
if(p.x < 1./3.) { // Parte I
// camadas opacas sobrepostas
ret = gray;
// atribua um valor cinza ao pixel primeiro
d = disk(r, vec2(-1.1,0.3), 0.4);
ret = mix(ret, col1, d); // misture o valor de cor anterior com
// o novo valor de cor de acordo com
// a função da área da forma.
// nesta linha, a cor anterior é cinza.
d = disk(r, vec2(-1.3,0.0), 0.4);
ret = mix(ret, col2, d);
d = disk(r, vec2(-1.05,-0.3), 0.4);
ret = mix(ret, col3, d); // aqui, a cor anterior pode ser cinza,
// azul ou rosa.
}
else if(p.x < 2./3.) { // Parte II
// Adição de Cores
// Assim é como as luzes de diferentes cores se somam
// en.wikipedia.org/wiki/Additive_color
ret = black; // start with black pixels
ret += disk(r, vec2(0.1,0.3), 0.4)*col1; // Adicione a nova cor
// à cor anterior
ret += disk(r, vec2(-.1,0.0), 0.4)*col2;
ret += disk(r, vec2(.15,-0.3), 0.4)*col3;
// Quando todos os componentes de "ret" se tornam iguais ou maiores que 1.0
// ela se torna branca.
}
else if(p.x < 3./3.) { // Part III
// Subtração de Cores
// Assim é como os corantes de diferentes cores se somam
// en.wikipedia.org/wiki/Subtractive_color
ret = white; // start with white
ret -= disk(r, vec2(1.1,0.3), 0.4)*col1;
ret -= disk(r, vec2(1.05,0.0), 0.4)* col2;
ret -= disk(r, vec2(1.35,-0.25), 0.4)* col3;
// Quando todos os componentes de "ret" se tornam iguais ou menores que 0.0
// ela se torna preta.
}
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 21
// TRANSFORMAÇÕES DE COORDENADAS: ROTAÇÃO
//
// Até agora, traduzimos o centro de coordenadas para desenhar formas geométricas
// em diferentes partes da tela.
// Vamos aprender como rotacionar as formas.
// uma função que desenha uma grade (anti-aliasing) do sistema de coordenadas.
float coordinateGrid(vec2 r) {
vec3 axesCol = vec3(0.0, 0.0, 1.0);
vec3 gridCol = vec3(0.5);
float ret = 0.0;
// Desenha linhas da grade
const float tickWidth = 0.1;
for(float i=-2.0; i<2.0; i+=tickWidth) {
// "i" é a coordenada da linha.
ret += 1.-smoothstep(0.0, 0.008, abs(r.x-i));
ret += 1.-smoothstep(0.0, 0.008, abs(r.y-i));
}
// Desenha os eixos
ret += 1.-smoothstep(0.001, 0.015, abs(r.x));
ret += 1.-smoothstep(0.001, 0.015, abs(r.y));
return ret;
}
// retorna 1.0 se estiver dentro do círculo
float disk(vec2 r, vec2 center, float radius) {
return 1.0 - smoothstep( radius-0.005, radius+0.005, length(r-center));
}
// retorna 1.0 se estiver dentro do retângulo
float rectangle(vec2 r, vec2 topLeft, vec2 bottomRight) {
float ret;
float d = 0.005;
ret = smoothstep(topLeft.x-d, topLeft.x+d, r.x);
ret *= smoothstep(topLeft.y-d, topLeft.y+d, r.y);
ret *= 1.0 - smoothstep(bottomRight.y-d, bottomRight.y+d, r.y);
ret *= 1.0 - smoothstep(bottomRight.x-d, bottomRight.x+d, r.x);
return ret;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(1.0);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 ret;
vec2 q;
float angle;
angle = 0.2*PI; // ângulo em radianos (PI é 180 graus)
// q é o sistema de coordenadas rotacionado
q.x = cos(angle)*r.x + sin(angle)*r.y;
q.y = - sin(angle)*r.x + cos(angle)*r.y;
ret = bgCol;
// desenhe os sistemas de coordenadas antigo e novo
ret = mix(ret, col1, coordinateGrid(r)*0.4 );
ret = mix(ret, col2, coordinateGrid(q) );
// desenhe formas no sistema de coordenadas antigo, r, e no sistema de coordenadas novo, q
ret = mix(ret, col1, disk(r, vec2(1.0, 0.0), 0.2));
ret = mix(ret, col2, disk(q, vec2(1.0, 0.0), 0.2));
ret = mix(ret, col1, rectangle(r, vec2(-0.8, 0.2), vec2(-0.5, 0.4)) );
ret = mix(ret, col2, rectangle(q, vec2(-0.8, 0.2), vec2(-0.5, 0.4)) );
// como você pode ver, ambos os círculos são desenhados na mesma coordenada, (1,0),
// em seus respectivos sistemas de coordenadas. Mas eles aparecem
// em diferentes posições na tela.
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 22
// TRANSFORMAÇÕES DE COORDENADAS: ESCALA
//
// Escalando o sistema de coordenadas.
// Uma função que desenha uma grade (anti-aliasing) do sistema de coordenadas.
float coordinateGrid(vec2 r) {
vec3 axesCol = vec3(0.0, 0.0, 1.0);
vec3 gridCol = vec3(0.5);
float ret = 0.0;
// Desenha linhas da grade
const float tickWidth = 0.1;
for(float i=-2.0; i<2.0; i+=tickWidth) {
// "i" é a coordenada da linha.
ret += 1.-smoothstep(0.0, 0.008, abs(r.x-i));
ret += 1.-smoothstep(0.0, 0.008, abs(r.y-i));
}
// Desenha os eixos
ret += 1.-smoothstep(0.001, 0.015, abs(r.x));
ret += 1.-smoothstep(0.001, 0.015, abs(r.y));
return ret;
}
// retorna 1.0 se estiver dentro do círculo
float disk(vec2 r, vec2 center, float radius) {
return 1.0 - smoothstep( radius-0.005, radius+0.005, length(r-center));
}
// retorna 1.0 se estiver dentro do retângulo
float rectangle(vec2 r, vec2 topLeft, vec2 bottomRight) {
float ret;
float d = 0.005;
ret = smoothstep(topLeft.x-d, topLeft.x+d, r.x);
ret *= smoothstep(topLeft.y-d, topLeft.y+d, r.y);
ret *= 1.0 - smoothstep(bottomRight.y-d, bottomRight.y+d, r.y);
ret *= 1.0 - smoothstep(bottomRight.x-d, bottomRight.x+d, r.x);
return ret;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(1.0);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 ret = bgCol;
// original
ret = mix(ret, col1, coordinateGrid(r)/2.0);
// escalado
float scaleFactor = 3.3; // aproxime-se tanto assim
vec2 q = r / scaleFactor;
ret = mix(ret, col2, coordinateGrid(q)/2.0);
ret = mix(ret, col2, disk(q, vec2(0.0, 0.0), 0.1));
ret = mix(ret, col1, disk(r, vec2(0.0, 0.0), 0.1));
ret = mix(ret, col1, rectangle(r, vec2(-0.5, 0.0), vec2(-0.2, 0.2)) );
ret = mix(ret, col2, rectangle(q, vec2(-0.5, 0.0), vec2(-0.2, 0.2)) );
// observe como o retângulo que não está centrado na origem das coordenadas
// mudou sua localização após a escala, mas os discos no centro
// permaneceram onde estão.
// Isso ocorre porque a escala é feita multiplicando todas as coordenadas de pixel
// por uma constante.
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 23
// TRANSFORMAÇÕES DE COORDENADAS SUCESSIVAS
//
// Desenhar uma forma na localização desejada, com tamanho desejado e
// orientação desejada requer domínio na aplicação sucessiva de
// transformações.
//
// Em geral, transformações não comutam. Isso significa que
// se você alterar a ordem delas, obterá resultados diferentes.
//
// Vamos tentar aplicar transformações em diferentes ordens.
float coordinateGrid(vec2 r) {
vec3 axesCol = vec3(0.0, 0.0, 1.0);
vec3 gridCol = vec3(0.5);
float ret = 0.0;
// Desenhe linhas de grade
const float tickWidth = 0.1;
for(float i=-2.0; i<2.0; i+=tickWidth) {
// "i" é a coordenada da linha.
ret += 1.-smoothstep(0.0, 0.008, abs(r.x-i));
ret += 1.-smoothstep(0.0, 0.008, abs(r.y-i));
}
// Desenhe os eixos
ret += 1.-smoothstep(0.001, 0.015, abs(r.x));
ret += 1.-smoothstep(0.001, 0.015, abs(r.y));
return ret;
}
// retorna 1.0 se estiver dentro do círculo
float disk(vec2 r, vec2 center, float radius) {
return 1.0 - smoothstep( radius-0.005, radius+0.005, length(r-center));
}
// retorna 1.0 se estiver dentro do disco
float rectangle(vec2 r, vec2 topLeft, vec2 bottomRight) {
float ret;
float d = 0.005;
ret = smoothstep(topLeft.x-d, topLeft.x+d, r.x);
ret *= smoothstep(topLeft.y-d, topLeft.y+d, r.y);
ret *= 1.0 - smoothstep(bottomRight.y-d, bottomRight.y+d, r.y);
ret *= 1.0 - smoothstep(bottomRight.x-d, bottomRight.x+d, r.x);
return ret;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(1.0);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 ret = bgCol;
float angle = 0.6;
mat2 rotationMatrix = mat2(cos(angle), -sin(angle),
sin(angle), cos(angle));
if(p.x < 1./2.) { // Parte I
// Coloque a origem no centro da Parte I
r = r - vec2(-xMax/2.0, 0.0);
vec2 rotated = rotationMatrix*r;
vec2 rotatedTranslated = rotated - vec2(0.4, 0.5);
ret = mix(ret, col1, coordinateGrid(r)*0.3);
ret = mix(ret, col2, coordinateGrid(rotated)*0.3);
ret = mix(ret, col3, coordinateGrid(rotatedTranslated)*0.3);
ret = mix(ret, col1, rectangle(r, vec2(-.1, -.2), vec2(0.1, 0.2)) );
ret = mix(ret, col2, rectangle(rotated, vec2(-.1, -.2), vec2(0.1, 0.2)) );
ret = mix(ret, col3, rectangle(rotatedTranslated, vec2(-.1, -.2), vec2(0.1, 0.2)) );
}
else if(p.x < 2./2.) { // Parte II
r = r - vec2(xMax*0.5, 0.0);
vec2 translated = r - vec2(0.4, 0.5);
vec2 translatedRotated = rotationMatrix*translated;
ret = mix(ret, col1, coordinateGrid(r)*0.3);
ret = mix(ret, col2, coordinateGrid(translated)*0.3);
ret = mix(ret, col3, coordinateGrid(translatedRotated)*0.3);
ret = mix(ret, col1, rectangle(r, vec2(-.1, -.2), vec2(0.1, 0.2)) );
ret = mix(ret, col2, rectangle(translated, vec2(-.1, -.2), vec2(0.1, 0.2)) );
ret = mix(ret, col3, rectangle(translatedRotated, vec2(-.1, -.2), vec2(0.1, 0.2)) );
}
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 24
// TEMPO, MOVIMENTO E ANIMAÇÃO
//
// Um dos inputs que um shader recebe pode ser o tempo.
// No ShaderToy, a variável "iTime" armazena o valor do
// tempo em segundos desde que o shader foi iniciado.
//
// Vamos alterar algumas variáveis em relação ao tempo!
float disk(vec2 r, vec2 center, float radius) {
return 1.0 - smoothstep( radius-0.005, radius+0.005, length(r-center));
}
float rect(vec2 r, vec2 bottomLeft, vec2 topRight) {
float ret;
float d = 0.005;
ret = smoothstep(bottomLeft.x-d, bottomLeft.x+d, r.x);
ret *= smoothstep(bottomLeft.y-d, bottomLeft.y+d, r.y);
ret *= 1.0 - smoothstep(topRight.y-d, topRight.y+d, r.y);
ret *= 1.0 - smoothstep(topRight.x-d, topRight.x+d, r.x);
return ret;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 col1 = vec3(0.216, 0.471, 0.698); // blue
vec3 col2 = vec3(1.00, 0.329, 0.298); // yellow
vec3 col3 = vec3(0.867, 0.910, 0.247); // red
vec3 ret;
if(p.x < 1./5.) { // Part I
vec2 q = r + vec2(xMax*4./5.,0.);
ret = vec3(0.2);
// y coordinate depends on time
float y = iTime;
// mod constraints y to be between 0.0 and 2.0,
// and y jumps from 2.0 to 0.0
// substracting -1.0 makes why jump from 1.0 to -1.0
y = mod(y, 2.0) - 1.0;
ret = mix(ret, col1, disk(q, vec2(0.0, y), 0.1) );
}
else if(p.x < 2./5.) { // Part II
vec2 q = r + vec2(xMax*2./5.,0.);
ret = vec3(0.3);
// oscillation
float amplitude = 0.8;
// y coordinate oscillates with a period of 0.5 seconds
float y = 0.8*sin(0.5*iTime*TWOPI);
// radius oscillates too
float radius = 0.15 + 0.05*sin(iTime*8.0);
ret = mix(ret, col1, disk(q, vec2(0.0, y), radius) );
}
else if(p.x < 3./5.) { // Part III
vec2 q = r + vec2(xMax*0./5.,0.);
ret = vec3(0.4);
// booth coordinates oscillates
float x = 0.2*cos(iTime*5.0);
// but they have a phase difference of PI/2
float y = 0.3*cos(iTime*5.0 + PI/2.0);
float radius = 0.2 + 0.1*sin(iTime*2.0);
// make the color mixture time dependent
vec3 color = mix(col1, col2, sin(iTime)*0.5+0.5);
ret = mix(ret, color, rect(q, vec2(x-0.1, y-0.1), vec2(x+0.1, y+0.1)) );
// try different phases, different amplitudes and different frequencies
// for x and y coordinates
}
else if(p.x < 4./5.) { // Part IV
vec2 q = r + vec2(-xMax*2./5.,0.);
ret = vec3(0.3);
for(float i=-1.0; i<1.0; i+= 0.2) {
float x = 0.2*cos(iTime*5.0 + i*PI);
// y coordinate is the loop value
float y = i;
vec2 s = q - vec2(x, y);
// each box has a different phase
float angle = iTime*3. + i;
mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
s = rot*s;
ret = mix(ret, col1, rect(s, vec2(-0.06, -0.06), vec2(0.06, 0.06)) );
}
}
else if(p.x < 5./5.) { // Part V
vec2 q = r + vec2(-xMax*4./5., 0.);
ret = vec3(0.2);
// let stop and move again periodically
float speed = 2.0;
float t = iTime*speed;
float stopEveryAngle = PI/2.0;
float stopRatio = 0.5;
float t1 = (floor(t) + smoothstep(0.0, 1.0-stopRatio, fract(t)) )*stopEveryAngle;
float x = -0.2*cos(t1);
float y = 0.3*sin(t1);
float dx = 0.1 + 0.03*sin(t*10.0);
float dy = 0.1 + 0.03*sin(t*10.0+PI);
ret = mix(ret, col1, rect(q, vec2(x-dx, y-dy), vec2(x+dx, y+dy)) );
}
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 25
// EFEITO PLASMA
//
// Dissemos que a cor de um pixel depende apenas de suas coordenadas
// e de outras entradas (como o tempo)
//
// Existe um efeito chamado Plasma, que é baseado em uma mistura de
// funções complexas na forma de f(x,y).
//
// Vamos escrever um plasma!
//
// en.wikipedia.org/wiki/Plasma_effect
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float t = iTime;
r = r * 8.0;
float v1 = sin(r.x +t);
float v2 = sin(r.y +t);
float v3 = sin(r.x+r.y +t);
float v4 = sin(length(r) +1.7*t);
float v = v1+v2+v3+v4;
vec3 ret;
if(p.x < 1./10.) { // Parte I
// ondas verticais
ret = vec3(v1);
}
else if(p.x < 2./10.) { // Parte II
// ondas horizontais
ret = vec3(v2);
}
else if(p.x < 3./10.) { // Parte III
// ondas diagonais
ret = vec3(v3);
}
else if(p.x < 4./10.) { // Parte IV
// ondas circulares
ret = vec3(v4);
}
else if(p.x < 5./10.) { // Parte V
// a soma de todas as ondas
ret = vec3(v);
}
else if(p.x < 6./10.) { // Parte VI
// Adicionar periodicidade aos gradientes
ret = vec3(sin(2.*v));
}
else if(p.x < 10./10.) { // Parte VII
// misturar cores
v *= 1.0;
ret = vec3(sin(v), sin(v+0.5*PI), sin(v+1.0*PI));
}
ret = 0.5 + 0.5*ret;
vec3 pixel = ret;
fragColor = vec4(pixel, 1.);
}
#elif TUTORIAL == 26
// TEXTURAS
//
// ShaderToy pode usar até quatro texturas.
float rect(vec2 r, vec2 bottomLeft, vec2 topRight) {
float ret;
float d = 0.005;
ret = smoothstep(bottomLeft.x-d, bottomLeft.x+d, r.x);
ret *= smoothstep(bottomLeft.y-d, bottomLeft.y+d, r.y);
ret *= 1.0 - smoothstep(topRight.y-d, topRight.y+d, r.y);
ret *= 1.0 - smoothstep(topRight.x-d, topRight.x+d, r.x);
return ret;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(0.3);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 ret;
if(p.x < 1./3.) { // Parte I
ret = texture(iChannel1, p).xyz;
}
else if(p.x < 2./3.) { // Parte II
ret = texture(iChannel1, 4.*p+vec2(0.,iTime)).xyz;
}
else if(p.x < 3./3.) { // Parte III
r = r - vec2(xMax*2./3., 0.);
float angle = iTime;
mat2 rotMat = mat2(cos(angle), -sin(angle),
sin(angle), cos(angle));
vec2 q = rotMat*r;
vec3 texA = texture(iChannel1, q).xyz;
vec3 texB = texture(iChannel2, q).xyz;
angle = -iTime;
rotMat = mat2(cos(angle), -sin(angle),
sin(angle), cos(angle));
q = rotMat*r;
ret = mix(texA, texB, rect(q, vec2(-0.3, -0.3), vec2(.3, .3)) );
}
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 27
// ENTRADA DO MOUSE
//
// O ShaderToy fornece as coordenadas do cursor do mouse e cliques de botão
// como entrada via o vec4 iMouse.
//
// Vamos escrever um shader com funcionalidade básica de mouse.
// Quando clicado no quadro, o pequeno disco seguirá o
// cursor. A coordenada x do cursor altera a cor de fundo.
// E se o cursor estiver dentro do disco maior, sua cor mudará.
float disk(vec2 r, vec2 center, float radius) {
return 1.0 - smoothstep( radius-0.5, radius+0.5, length(r-center));
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
// A cor de fundo depende da coordenada x do cursor
vec3 bgCol = vec3(iMouse.x / iResolution.x);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 ret = bgCol;
vec2 center;
// Desenha o grande disco amarelo
center = vec2(100., iResolution.y/2.);
float radius = 60.;
// Se as coordenadas do cursor estiverem dentro do disco
if( length(iMouse.xy-center)>radius ) {
// use color3
ret = mix(ret, col3, disk(fragCoord.xy, center, radius));
}
else {
// caso contrário use color2
ret = mix(ret, col2, disk(fragCoord.xy, center, radius));
}
// Desenha o pequeno disco azul no cursor
center = iMouse.xy;
ret = mix(ret, col1, disk(fragCoord.xy, center, 20.));
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
#elif TUTORIAL == 28
// ALEATORIEDADE
//
// Não sei por que, mas o GLSL não possui geradores de números aleatórios.
// Isso não é um problema se você estiver escrevendo seu código em
// uma linguagem de programação que possui funções de geração de números aleatórios.
// Dessa forma, você pode gerar os valores aleatórios usando a linguagem e enviá-los
// para o shader por meio de uniformes.
//
// No entanto, se você estiver usando um sistema que permite apenas escrever
// o código do shader, como o ShaderToy, então você precisa escrever seus próprios
// geradores pseudo-aleatórios.
//
// Aqui está um padrão que vi repetidamente em muitos shaders diferentes
// no ShaderToy
// Vamos desenhar N discos diferentes em locais aleatórios usando esse padrão.
float hash(float seed)
{
// Retorne um número "random" com base na "seed"
return fract(sin(seed) * 43758.5453);
}
vec2 hashPosition(float x)
{
// Retorne uma posição "random" com base na "seed"
return vec2(hash(x), hash(x * 1.1));
}
float disk(vec2 r, vec2 center, float radius) {
return 1.0 - smoothstep( radius-0.005, radius+0.005, length(r-center));
}
float coordinateGrid(vec2 r) {
vec3 axesCol = vec3(0.0, 0.0, 1.0);
vec3 gridCol = vec3(0.5);
float ret = 0.0;
// Desenhe linhas de grade
const float tickWidth = 0.1;
for(float i=-2.0; i<2.0; i+=tickWidth) {
// "i" é a coordenada da linha.
ret += 1.-smoothstep(0.0, 0.005, abs(r.x-i));
ret += 1.-smoothstep(0.0, 0.01, abs(r.y-i));
}
// Desenhe os eixos
ret += 1.-smoothstep(0.001, 0.005, abs(r.x));
ret += 1.-smoothstep(0.001, 0.005, abs(r.y));
return ret;
}
float plot(vec2 r, float y, float thickness) {
return ( abs(y - r.y) < thickness ) ? 1.0 : 0.0;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = vec2(fragCoord.xy / iResolution.xy);
vec2 r = 2.0*vec2(fragCoord.xy - 0.5*iResolution.xy)/iResolution.y;
float xMax = iResolution.x/iResolution.y;
vec3 bgCol = vec3(0.3);
vec3 col1 = vec3(0.216, 0.471, 0.698); // azul
vec3 col2 = vec3(1.00, 0.329, 0.298); // amarelo
vec3 col3 = vec3(0.867, 0.910, 0.247); // vermelho
vec3 ret = bgCol;
vec3 white = vec3(1.);
vec3 gray = vec3(.3);
if(r.y > 0.7) {
// Sistema de coordenadas traduzido e rotacionado
vec2 q = (r-vec2(0.,0.9))*vec2(1.,20.);
ret = mix(white, gray, coordinateGrid(q));
// Apenas a função seno regular
float y = sin(5.*q.x) * 2.0 - 1.0;
ret = mix(ret, col1, plot(q, y, 0.1));
}
else if(r.y > 0.4) {
vec2 q = (r-vec2(0.,0.6))*vec2(1.,20.);
ret = mix(white, col1, coordinateGrid(q));
// Obtenha a parte decimal da função seno
float y = fract(sin(5.*q.x)) * 2.0 - 1.0;
ret = mix(ret, col2, plot(q, y, 0.1));
}
else if(r.y > 0.1) {
vec3 white = vec3(1.);
vec2 q = (r-vec2(0.,0.25))*vec2(1.,20.);
ret = mix(white, gray, coordinateGrid(q));
// amplie o resultado da função seno
// aumente a escala e observe a transição de
// um padrão periódico para um padrão caótico
float scale = 10.0;
float y = fract(sin(5.*q.x) * scale) * 2.0 - 1.0;
ret = mix(ret, col1, plot(q, y, 0.2));
}
else if(r.y > -0.2) {
vec3 white = vec3(1.);
vec2 q = (r-vec2(0., -0.0))*vec2(1.,10.);
ret = mix(white, col1, coordinateGrid(q));
float seed = q.x;
// Amplie com um número real grande
float y = fract(sin(seed) * 43758.5453) * 2.0 - 1.0;
// Isso pode ser usado como um valor pseudoaleatório
// Esse tipo de função, em que dois valores de entrada
// que estão próximos um do outro (como posições q.x próximas)
// retornam valores de saída muito diferentes, são chamadas de
// "funções hash".
ret = mix(ret, col2, plot(q, y, 0.1));
}
else {
vec2 q = (r-vec2(0., -0.6));
// use o índice do loop como a semente
// e varie diferentes quantidades de discos, como
// localização e raio
for(float i=0.0; i<6.0; i++) {
// altere a semente e obtenha diferentes distribuições
float seed = i + 0.0;
vec2 pos = (vec2(hash(seed), hash(seed + 0.5))-0.5)*3.;;
float radius = hash(seed + 3.5);
pos *= vec2(1.0,0.3);
ret = mix(ret, col1, disk(q, pos, 0.2*radius));
}
}
vec3 pixel = ret;
fragColor = vec4(pixel, 1.0);
}
/* Fim dos Tutoriais */
#elif TUTORIAL == 0
// TELA DE BOAS VINDAS
float square(vec2 r, vec2 bottomLeft, float side) {
vec2 p = r - bottomLeft;
return ( p.x > 0.0 && p.x < side && p.y>0.0 && p.y < side ) ? 1.0 : 0.0;
}
float character(vec2 r, vec2 bottomLeft, float charCode, float squareSide) {
vec2 p = r - bottomLeft;
float ret = 0.0;
float num, quotient, remainder, divider;
float x, y;
num = charCode;
for(int i=0; i<20; i++) {
float boxNo = float(19-i);
divider = pow(2., boxNo);
quotient = floor(num / divider);
remainder = num - quotient*divider;
num = remainder;
y = floor(boxNo/4.0);
x = boxNo - y*4.0;
if(quotient == 1.) {
ret += square( p, squareSide*vec2(x, y), squareSide );
}
}
return ret;
}
mat2 rot(float th) { return mat2(cos(th), -sin(th), sin(th), cos(th)); }
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
float G = 990623.; // Caracteres compactados :-)
float L = 69919.;
float S = 991119.;
float t = iTime;
vec2 r = (fragCoord.xy - 0.5*iResolution.xy) / iResolution.y;
//vec2 rL = rot(t)*r+0.0001*t;
//vec2 rL = r+vec2(cos(t*0.02),sin(t*0.02))*t*0.05;
float c = 0.05;//+0.03*sin(2.5*t);
vec2 pL = (mod(r+vec2(cos(0.3*t),sin(0.3*t)), 2.0*c)-c)/c;
float circ = 1.0-smoothstep(0.75, 0.8, length(pL));
vec2 rG = rot(2.*3.1415*smoothstep(0.,1.,mod(1.5*t,4.0)))*r;
vec2 rStripes = rot(0.2)*r;
float xMax = 0.5*iResolution.x/iResolution.y;
float letterWidth = 2.0*xMax*0.9/4.0;
float side = letterWidth/4.;
float space = 2.0*xMax*0.1/5.0;
r += 0.001; // Para eliminar a linha horizontal azul em y=0.
float maskGS = character(r, vec2(-xMax+space, -2.5*side)+vec2(letterWidth+space, 0.0)*0.0, G, side);
float maskG = character(rG, vec2(-xMax+space, -2.5*side)+vec2(letterWidth+space, 0.0)*0.0, G, side);
float maskL1 = character(r, vec2(-xMax+space, -2.5*side)+vec2(letterWidth+space, 0.0)*1.0, L, side);
float maskSS = character(r, vec2(-xMax+space, -2.5*side)+vec2(letterWidth+space, 0.0)*2.0, S, side);
float maskS = character(r, vec2(-xMax+space, -2.5*side)+vec2(letterWidth+space, 0.0)*2.0 + vec2(0.01*sin(2.1*t),0.012*cos(t)), S, side);
float maskL2 = character(r, vec2(-xMax+space, -2.5*side)+vec2(letterWidth+space, 0.0)*3.0, L, side);
float maskStripes = step(0.25, mod(rStripes.x - 0.5*t, 0.5));
float i255 = 0.00392156862;
vec3 blue = vec3(43., 172., 181.)*i255;
vec3 pink = vec3(232., 77., 91.)*i255;
vec3 dark = vec3(59., 59., 59.)*i255;
vec3 light = vec3(245., 236., 217.)*i255;
vec3 green = vec3(180., 204., 18.)*i255;
vec3 pixel = blue;
pixel = mix(pixel, light, maskGS);
pixel = mix(pixel, light, maskSS);
pixel -= 0.1*maskStripes;
pixel = mix(pixel, green, maskG);
pixel = mix(pixel, pink, maskL1*circ);
pixel = mix(pixel, green, maskS);
pixel = mix(pixel, pink, maskL2*(1.-circ));
float dirt = pow(texture(iChannel0, 4.0*r).x, 4.0);
pixel -= (0.2*dirt - 0.1)*(maskG+maskS); // dirt
pixel -= smoothstep(0.45, 2.5, length(r));
fragColor = vec4(pixel, 1.0);
}
#endif