Pular para o conteúdo
Fundamento · VetoresIntermediário

Reflexão e refração

Rebata uma direção numa superfície, ou curve-a através dela, a partir da normal e do índice de refração.

Como funciona

A luz que chega numa superfície ou volta ou atravessa, curvando-se ao entrar. O GLSL tem uma função embutida para cada caso: reflect e refract. As duas recebem uma direção de chegada e a normal da superfície e devolvem uma direção nova. Brilhos especulares, espelhos, consultas a mapas de ambiente, vidro e água começam todos por elas.

reflect

reflect(I, N) espelha a direção incidente I em relação à normal N. A definição é:

vec3 r = I - 2.0 * dot(N, I) * N; // o que reflect(I, N) devolve

O produto escalar mede quanto de I aponta para dentro da superfície; subtrair duas vezes essa parte a inverte, e a parte ao longo da superfície fica igual. N precisa estar normalizada para o resultado manter o comprimento de I.

O erro comum é o sentido de I. Ele aponta para a superfície, no sentido em que a luz viaja. O vetor de visão que a maioria dos shaders carrega (i.view nos cards deste site) aponta para o outro lado, da superfície para a câmera, então inverta o sinal:

vec3 n = normalize(i.normal);
vec3 v = normalize(i.view);   // superfície → câmera
vec3 r = reflect(-v, n);      // a direção que um espelho mostraria neste pixel

Passe r para um mapa de ambiente ou uma função de céu e você tem um espelho. Compare a direção refletida da luz com a direção de visão e você tem um brilho de Phong, forte onde a luz rebate direto para a câmera:

vec3 l = normalize(vec3(0.4, 1.0, 0.3));                  // em direção à luz
float spec = pow(max(dot(reflect(-l, n), v), 0.0), 32.0); // expoente maior, brilho menor e mais nítido

O Blinn-Phong troca o vetor refletido pelo vetor intermediário normalize(l + v) comparado com n, o que é mais barato e se comporta melhor em ângulos rasantes; a técnica de brilhos especulares parte dos dois.

refract

Quando a luz passa de um material para outro, ela muda de velocidade e se curva. Cada material tem um índice de refração (IOR): cerca de 1,0 no ar, 1,33 na água, 1,5 no vidro comum, 2,42 no diamante. A lei de Snell relaciona os ângulos dos dois lados com a razão entre os dois índices, e refract(I, N, eta) a aplica:

  • I é a direção incidente normalizada, em direção à superfície, como no reflect.
  • N é a normal normalizada do lado de onde I vem.
  • eta é a razão entre os índices: o do meio que a luz deixa dividido pelo do meio em que ela entra.
float eta = 1.0 / 1.5;           // do ar para o vidro
vec3 t = refract(-v, n, eta);    // o raio curvado dentro do vidro

Para sair, tudo se inverte: a normal vira para encarar o raio, e eta passa a ser 1.5 / 1.0. Indo de um índice maior para um menor, como do vidro para o ar, um raio num ângulo raso o bastante não consegue sair e reflete de volta para dentro. Isso é a reflexão interna total, e o refract a sinaliza devolvendo um vetor nulo, que você deve conferir antes de usar o resultado:

vec3 t = refract(dir, -n, 1.5);  // do vidro para o ar
if (dot(t, t) == 0.0) {
    t = reflect(dir, -n);        // reflexão interna total
}

Refração na tela

Um shader em tempo real raramente traça o raio refratado pela cena. A aproximação comum desloca a consulta a uma imagem do que está atrás da superfície pela direção curvada:

vec3 tv = mat3(uView) * t;                    // mundo → câmera, para x e y seguirem a tela
vec2 offset = tv.xy * 0.05;                   // intensidade em unidades de tela, ajustada no olho
vec3 behind = texture(uScene, screenUv + offset).rgb;

Não é fisicamente exato, mas o olho aceita. Refratar cada canal de cor com um eta um pouco diferente separa as cores como um prisma, um efeito de dispersão barato. Quanto da luz reflete e quanto refrata também depende do ângulo; o efeito Fresnel descreve essa divisão.

Leitura complementar