Fundamentals
Core
Master
Shaders
Funções de modelagem
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 coruTime
: o tempo decorrido desde o início da aplicaçãovUv
: 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úmerox
e retornax * 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:
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 limitex
: 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
.
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); }
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); }
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 valory
: o segundo valora
: o valor para interpolar entrex
ey
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); }
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); }
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); }
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.
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 inferioredge1
: o limite superiorx
: o valor a ser interpolado entreedge0
eedge1
Ela produz três resultados diferentes:
0.0
sex
for menor queedge0
1.0
sex
for maior queedge1
- uma interpolação suave entre
0.0
e1.0
sex
estiver entreedge0
eedge1
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); }
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); }
As cores estão desbotadas, e a transição é muito suave.
Vamos visualizar isso no Graphtoy:
Representação de max(x, 0.4)
Representação de min(x, 0.6)
E se combinarmos ambas:
Podemos ver que nossos valores nunca ficam abaixo de 0.4
e nunca acima de 0.6
.
Nossa combinação das funções
min
emax
pode ser substituída pela função clamp, que é uma abreviação paramin(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.