Funções de modelagem

Starter pack

A lógica do shader é especial, é diferente do que estamos acostumados em JavaScript ou outras linguagens de programação. Embora a sintaxe seja semelhante, a maneira como implementamos a lógica é diferente. É um pouco como uma caixa preta, você insere alguns dados e obtém algumas cores para o fragment shader, ou algumas posições para o vertex shader.

Mas como você controla a saída? Como você faz para que pareça do jeito que você quer? É aqui que entram as funções de modelagem.

Nós vamos focar no fragment shader para esta lição, mas os mesmos princípios se aplicam ao vertex shader.

Zona de prática

Para dominar a arte da modelagem, você precisará praticar e experimentar. Vai levar tempo para se acostumar com a forma como o shader funciona, mas uma vez que pegar o jeito, você será capaz de criar efeitos incríveis.

Porque quero que você esteja na melhor posição para ter sucesso, preparei esta cena de Galeria de Arte 3D para você visualizar seus shaders em tempo real em um ambiente agradável.

Você consegue sentir sua criatividade fluindo? Todas essas molduras vazias estão esperando por suas obras-primas!

O modelo 3D usado é VR Gallery por Maxim Mavrichev e está licenciado sob Creative Commons Attribution.

Usei <CameraControls /> para criar uma câmera em primeira pessoa para navegar dentro da galeria e Squoosh para reduzir o tamanho das texturas.

SimpleShaderMaterial

Todas as molduras da galeria são cópias do SimpleShaderMaterial, é um shader customizado básico que preparei para você com:

  • uColor: uma cor
  • uTime: o tempo decorrido desde o início da aplicação
  • vUv: as coordenadas UV do fragment (0 a 1 em ambos os eixos)

Todos são estendidos no <App.jsx /> para serem usados de forma declarativa em nossa cena.

Eles são nomeados com base em sua posição na galeria, mas sinta-se à vontade para renomeá-los para algo mais significativo assim que criar obras-primas com eles!

Funções

Para cada fragmento do nosso shader, nosso mesmo código será executado com entradas diferentes.

Se quisermos desenhar uma linha, poderíamos usar declarações if para verificar se o pixel está dentro da linha ou não. Mas fazer formas mais complexas seria muito difícil e ineficiente.

Em vez disso, usamos funções para modelar nossa saída. Não se preocupe, você não precisa ser um especialista em matemática para usá-las. Você só precisa entender quais funções estão à sua disposição e o que elas fazem.

Pense em uma função como uma máquina que recebe uma entrada e fornece uma saída. Por exemplo, a função f(x) = x * 2 recebe um número x e retorna x * 2 como resultado.

Para visualizar o efeito das diferentes funções, usaremos o Graphtoy, uma ferramenta simples para digitar funções e ver seus resultados. Ela ajuda a validar nossa compreensão das funções quando estamos experimentando com elas em nossos shaders.

Vamos visualizar nossa função f(x) = x * 2 no Graphtoy:

Graphtoy

O Graphtoy rapidamente se tornará seu melhor amigo quando estiver experimentando com shaders.

Hora de experimentar com as diferentes funções à nossa disposição.

Step

A função step é uma função simples que retorna 0 se a entrada for menor que um limite, e 1 se a entrada for maior que o limite.

Ela recebe dois parâmetros:

  • edge: o limite
  • x: o valor de entrada

Vamos experimentar no fragment shader do frame frontal ArtFront02Material.jsx:

void main() {
  float pct = 1.0;
  pct = step(0.5, vUv.x);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Declaramos uma variável pct para determinar a porcentagem da cor que queremos exibir. Definimos como 1.0 por padrão e, em seguida, usamos a função step para definir como 0.0 se o vUv.x for menor que 0.5.

Representação da função Step

Podemos ver que a metade esquerda do frame é preta, e a metade direita é a cor definida no uniforme uColor.

Vamos aplicá-la ao eixo vertical com um limite diferente:

void main() {
  float pct = 1.0;
  pct = step(0.2, vUv.y);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Representação vertical da função Step

Podemos ver que 20% da parte inferior do frame é preta, e o resto é roxa.

As coordenadas UV têm origem no canto inferior esquerdo do frame, então [0, 0] é o canto inferior esquerdo, e [1, 1] é o canto superior direito.

Se você quiser reverter o efeito, pode simplesmente trocar os parâmetros da função step:

void main() {
  float pct = 1.0;
  pct = step(vUv.y, 0.2);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Representação vertical revertida da função Step

Agora temos o efeito oposto.

Mix

A função mix é uma função simples que retorna uma interpolação linear entre dois valores.

Ela leva três parâmetros:

  • x: o primeiro valor
  • y: o segundo valor
  • a: o valor para interpolar entre x e y

Vamos tentar:

void main() {
  float pct = 1.0;
  pct = mix(0.0, 1.0, vUv.x);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Representação da função Mix

Podemos ver um belo gradiente de preto para roxo.

Como muitas outras funções, você pode usá-la em outros tipos de dados, como os componentes do vetor. Vamos usá-la para criar um gradiente de branco para roxo no eixo vertical:

void main() {
  vec3 whiteColor = vec3(1.0);
  vec3 finalColor = mix(whiteColor, uColor, vUv.y);
  gl_FragColor = vec4(finalColor, 1.0);
}

Mix function two colors

Podemos ver um belo gradiente de branco para roxo.

Vamos mudar o valor de interpolação chamando a função mix uma primeira vez para obter o valor pct, e então usá-lo para interpolar entre o whiteColor e o uColor:

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = mix(0.0, 0.3, vUv.y);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

Mix function two colors with pct

O roxo é muito claro, pois o valor máximo de pct é 0.3.

Use o Graphtoy para experimentar com a função mix e entender como ela funciona.

Graphtoy mix function

3 interpolações diferentes com a função mix dão resultados completamente diferentes.

Smoothstep

A função smoothstep é uma função simples que retorna uma interpolação suave entre dois valores.

Ela recebe três parâmetros:

  • edge0: o limite inferior
  • edge1: o limite superior
  • x: o valor a ser interpolado entre edge0 e edge1

Ela produz três resultados diferentes:

  • 0.0 se x for menor que edge0
  • 1.0 se x for maior que edge1
  • uma interpolação suave entre 0.0 e 1.0 se x estiver entre edge0 e edge1

Vamos usá-la para alterar o valor de pct:

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = smoothstep(0.4, 0.6, vUv.y);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

Smoothstep function representation

A transição agora é apenas entre 0.4 e 0.6. Tudo abaixo é branco, e tudo acima é roxo.

Min & Max

min e max são funções simples que retornam o valor mínimo ou máximo entre duas entradas. Elas funcionam exatamente como as funções Math.min e Math.max em JavaScript.

Vamos usá-las para ajustar nosso valor pct. Primeiro, vamos garantir que ele nunca esteja abaixo de 0.4 (o que significa nunca totalmente branco):

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = smoothstep(0.4, 0.6, vUv.y);
  pct = max(pct, 0.4);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

E nunca acima de 0.6 (o que significa nunca totalmente roxo):

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = smoothstep(0.4, 0.6, vUv.y);
  pct = max(pct, 0.4);
  pct = min(pct, 0.6);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

Min & Max function representation

As cores estão desbotadas, e a transição é muito suave.

Vamos visualizar isso no Graphtoy:

Max graph

Representação de max(x, 0.4)

Min graph

Representação de min(x, 0.6)

E se combinarmos ambas:

Min & Max graph

Podemos ver que nossos valores nunca ficam abaixo de 0.4 e nunca acima de 0.6.

Nossa combinação das funções min e max pode ser substituída pela função clamp, que é uma abreviação para min(max(x, min), max).

Operações & Padrões

Antes de descobrirmos muitas outras funções úteis, vamos ver como podemos usar operações para criar padrões.

End of lesson preview

To get access to the entire lesson, you need to purchase the course.