Blinn Phong com o Efeito de Fresnel

Seguimento do Artigo de Metallic, agora com o Efeito de Fresnel.

0004

2024/10/08 14:46

EN-US | PT-BR

1 Introdução

Esse é o seguimento de Blinn Phong com Textures de Metallic e esperadamente, o último, tenho que dizer, eu nunca escrevi artigos antes, eu sei que algumas coisas aqui pode ser um pouco confusas ou estranhas mas em minha opinião é melhor escrever um artigo ruim sobre algo interessante que você descobriu do que dizer no chat do discord e deixar que se perca para sempre.

2 Efeito de Fresnel

O Efeito de Fresnel é uma daquelas coisas que uma vez que você perceber que ele existe você sempre vai ver ele em todo lugar, de plásticos brilhantes a pisos cerâmicos ou grandes corpos de água, fresnel normalmente não é visível em metais e em renderização 3D normalmente se assume que metais não tem fresnel.

Ao invés de te mostrar um gráfico chato, irei the mostrar fotos reais do efeito.

Low Reflection Medium Reflection High Reflection

Você entendeu? se não, vou dizer então:

O efeito de fresnel é um efeito de luz que você normalmente vê em superfície não metálicas onde o reflexo se torna mais intenso quanto maior o ângulo entre o normal da superfície e a direção de view se torna, como pode ver na primeira foto, nós quase não conseguimos ver um reflexo, mas ainda tem um, fresnel não remove completamente os reflexos quando se olha de cima, isso é normalmente assumido de ser próximo a 5% para grande parte das coisas mas você pode encontrar tabelas com mais valores na internet e como pode ver na segunda e terceira foto, o reflexo fica mais intenso conforme o ângulo aumenta.

3 Em código

Se você sabe o que é um produto escalar (dot product) você provavelmente já adivinhou como isso vai se parecer.

  1. #define FRESNEL_MIN 0.05
  2. #define FRESNEL_MAX 0.95
  3. float fresnelFactor(vec3 viewDirection, vec3 normal) {
  4. float fresnelDot = 1.0 - max(dot(-viewDirection, normal), 0.0);
  5. return FRESNEL_MIN + (FRESNEL_MAX * pow(fresnelDot, 5.0));
  6. }

Aqui nós criamos uma função para calcular o fator de fresnel para a direção de view (na minha configuração, isso aponta da câmera para o fragmento, eu acho mais intuitivo desse jeito) e o normal da superfície.

Nós então calculamos o produto escalar entre o oposto da direção de view e o normal da superfície (isso nos dá o quão "próximo" a direção de view está do normal) e então limitamos o valor para que não se torne negativo e espelhamos ele pois queremos um valor que fique maior conforme o ângulo aumenta.

Como fresnel não é linear (só vemos reflexos fortes quando o ângulo está bem grande), nós precisamos aplicar uma função exponencial (pow) para ele, que normalmente é 5.0, nós então multiplicamos pelo valor máximo do efeito de fresnel e adicionamos o valor mínimo.

Já que o mínimo e máximo sempre somam para um, pode ser simplificado para uma única constante.

  1. #define FRESNEL_BALANCE 0.05
  2. float fresnelFactor(vec3 viewDirection, vec3 normal) {
  3. float fresnelDot = 1.0 - max(dot(-viewDirection, normal), 0.0);
  4. return FRESNEL_BALANCE + ((1.0 - FRESNEL_BALANCE) * pow(fresnelDot, 5.0));
  5. }

Aqui nós chamamos de "FRESNEL_BALANCE" mas é normalmente chamado de "F0" (Fresnel no ângulo 0).

Agora nós só precisamos adicionar ele para a nossa função de reflexo e também multiplicar pela roughness para que o reflexo diminua conforme a roughness aumente.

  1. vec3 computeReflection(
  2. samplerCube cube,
  3. vec3 viewDirection,
  4. vec3 normal,
  5. vec3 diffuseColor,
  6. float metallic,
  7. float roughness
  8. ) {
  9. ivec2 cubemapSize = textureSize(cube, 0);
  10. float mipLevels = 1.0 + floor(log2(max(float(cubemapSize.x), float(cubemapSize.y))));
  11. float lodLevel = mipLevels * sqrt(roughness);
  12. vec3 reflectedColor = textureLod(cube, reflect(viewDirection, normal), lodLevel).rgb;
  13. return mix(
  14. reflectedColor * fresnelFactor(viewDirection, normal) * pow(1.0 - roughness, 2.0),
  15. reflectedColor * diffuseColor,
  16. metallic
  17. );
  18. }

E é assim que se parece:

Red Shiny Plastic

4 Blinn Phong

Depois de pensar sobre o que eu fiz, eu eventualmente simplifiquei toda a conversão de roughness para shininess de blinn phong em uma única função que converte um material de PBR de Metallic Roughness para um Material de Blinn Phong no shader, você só vai precisar usar isso como material nos seus cálculos de luz.

  1. #define PI 3.14159265359
  2. #define MAX_SHININESS 2048.0
  3. #define DIFFUSE_BALANCE 0.50
  4. #define FRESNEL_BALANCE 0.05
  5. struct BlinnPhongMaterial {
  6. float shininess;
  7. vec3 diffuse;
  8. vec3 specular;
  9. vec3 ambient;
  10. };
  11. float fresnelFactor(vec3 viewDirection, vec3 normal) {
  12. float fresnelDot = 1.0 - max(dot(-viewDirection, normal), 0.0);
  13. return FRESNEL_BALANCE + ((1.0 - FRESNEL_BALANCE) * pow(fresnelDot, 5.0));
  14. }
  15. BlinnPhongMaterial convertPBRMaterialToBlinnPhong(
  16. vec3 viewDirection, vec3 normal,
  17. vec3 color, float metallic, float roughness, float ambientOcclusion
  18. ) {
  19. float shininess = pow(MAX_SHININESS, 1.0 - roughness);
  20. float specular = ((shininess + 2.0) * (shininess + 4.0)) / (8.0 * PI * (pow(2.0, -shininess * 0.5) + shininess));
  21. float fresnel = fresnelFactor(viewDirection, normal);
  22. return BlinnPhongMaterial(
  23. shininess,
  24. mix(
  25. (color * DIFFUSE_BALANCE) / PI,
  26. vec3(0.0),
  27. metallic
  28. ),
  29. mix(
  30. vec3(max(specular - 0.3496155267919281, 0.0)) * (1.0 - DIFFUSE_BALANCE) * fresnel * PI,
  31. vec3(specular) * color,
  32. metallic
  33. ),
  34. mix(
  35. vec3(ambientOcclusion) * color,
  36. vec3(0.0),
  37. metallic
  38. )
  39. );
  40. }
  1. vec3 directionalLight(
  2. vec3 lightDirection,
  3. vec3 viewDirection,
  4. vec3 normal,
  5. BlinnPhongMaterial material
  6. ) {
  7. vec3 halfwayDirection = -normalize(lightDirection + viewDirection);
  8. float diffuseFactor = max(dot(normal, -lightDirection), 0.0);
  9. float specularFactor = pow(max(dot(normal, halfwayDirection), 0.0), material.shininess) * diffuseFactor;
  10. float lightDiffuse = 1.90;
  11. float lightSpecular = 1.0;
  12. float lightAmbient = 0.10;
  13. vec3 diffuse = lightDiffuse * diffuseFactor * material.diffuse;
  14. vec3 specular = lightSpecular * specularFactor * material.specular;
  15. vec3 ambient = lightAmbient * material.ambient;
  16. return diffuse + specular + ambient;
  17. }

Dessa vez eu dividi o diffuse por PI então vai se parecer escuro, eu também adicionei um fresnel nele, então deve parecer mais realista quando olhando de altos ângulos, é assim que se parece naquela textura de couro sem reflexos:

Leather

Eu também dividi a função de reflexo em três funções mais genéricas.

  1. float cubemapMipLevels(samplerCube cube) {
  2. ivec2 cubemapSize = textureSize(cube, 0);
  3. float mipLevels = 1.0 + floor(log2(max(float(cubemapSize.x), float(cubemapSize.y))));
  4. return mipLevels;
  5. }
  6. vec4 cubemapRoughnessColor(samplerCube cube, float roughness, vec3 direction) {
  7. float mipLevels = cubemapMipLevels(cube);
  8. float lodLevel = mipLevels * sqrt(roughness);
  9. return textureLod(cube, direction, lodLevel);
  10. }
  11. vec3 cubemapReflection(
  12. vec3 cubemapColor,
  13. vec3 viewDirection, vec3 normal,
  14. vec3 color, float metallic, float roughness
  15. ) {
  16. return mix(
  17. cubemapColor * fresnelFactor(viewDirection, normal) * pow(1.0 - roughness, 2.0),
  18. cubemapColor * color,
  19. metallic
  20. );
  21. }

5 Resultados Finais

Dessa vez essas texturas vem de múltiplos lugares.

Monitor Ice Gold Marble Wood Sand with water