Fork me on GitHub

Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been authorized by the author. If reprinted or reposted, please be sure to mark the original link and description in the key position of the article after obtaining the author’s consent as well as the translator's. If the article is helpful to you, click this Donation link to buy the author a cup of coffee.
说明:该系列博文翻译自Nathan Vaughn着色器语言教程。文章已经获得作者翻译授权,如有转载请务必在取得作者译者同意之后在文章的重点位置标明原文链接以及说明。如果你觉得文章对你有帮助,点击此打赏链接请作者喝一杯咖啡。

朋友们,你们好!欢迎来到第12节Shadertoy教程。在本篇教程中,我们将学习如何利用涅菲尔反射给一个球添加边缘光照的效果。

初始化

先用我们的光线步进算法为模板创建一个着色器。

  const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;

float sdSphere(vec3 p, float r )
{
  vec3 offset = vec3(0, 0, -2);
  return length(p - offset) - r;
}

float sdScene(vec3 p) {
  return sdSphere(p, 1.);
}

float rayMarch(vec3 ro, vec3 rd) {
  float depth = MIN_DIST;

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    float d = sdScene(p);
    depth += d;
    if (d < PRECISION || depth > MAX_DIST) break;
  }

  return depth;
}

vec3 calcNormal(vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005;
    return normalize(
      e.xyy * sdScene(p + e.xyy) +
      e.yyx * sdScene(p + e.yyx) +
      e.yxy * sdScene(p + e.yxy) +
      e.xxx * sdScene(p + e.xxx));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec3 backgroundColor = vec3(0.1);
  vec3 col = vec3(0);

  vec3 ro = vec3(0, 0, 3);
  vec3 rd = normalize(vec3(uv, -1));

  float d = rayMarch(ro, rd);
  
  if (d > MAX_DIST) {
    col = backgroundColor;
  } else {
    vec3 p = ro + rd * d;
    vec3 normal = calcNormal(p);
    vec3 lightPosition = vec3(4, 4, 7);
    vec3 lightDirection = normalize(lightPosition - p);

    float diffuse = clamp(dot(normal, lightDirection), 0., 1.);
    vec3 diffuseColor = vec3(0, 0.6, 1);

    col = diffuse * diffuseColor;
  }

  fragColor = vec4(col, 1.0);
}

运行以上的代码,会看到一个蓝色的球,我们目前只用到了漫反射。

涅菲尔反射

涅菲尔反射是指当一束光线入射,从一个物体入射到另外一个物体时产生的折射或者反射现象。简单来说,也就是如果我们从不同的角度观察物体时,它会表现得稍有不同。

空气是一种传播的介质,它的折射率大概是1.000293。而类似钻石这样的物质的折射率就非常高。钻石的折射率高达2.417。更高的折射率意味着光线更容易产生折射现象的。涅菲尔公式看起来可能有一点复杂。对于计算机图形学来说,大家通常使用近似值来模拟涅菲尔折射效果。

上面的公式计算了涅菲尔折射率,R0中的R表示的一束平行光线射入法线时产生的折射效果(尤其是当0位零的时候),cos0 表示表面法线与入射光线之间的点积。在代码中用光线方向rd来表示。为了举例,我们假设空气的折射率和球的折射率都是1。

n1 = 1
n2 = 1

R0 = ((n1 - n2)/(n1 + n2)) ^ 2
R0 = ((1 - 1)/(1 + 1)) ^ 2
R0 = 0

将R0设置为0,我们可以让涅菲尔反射等式变得更加简明。

R = R0 + (1 - R0)(1 - cosθ)^5

Since R0 = 0,
R = (1 - cosθ)^5

在GLSL代码中,可以用下面方式表示

  float fresnel = pow(1. - dot(normal, -rd), 5.);

我们用clamp函数确保取值范围在0到1之间,并且使用-rd。如果使用rd,那么我们就看不到球体边缘的颜色了。

  float fresnel = pow(clamp(1. - dot(normal, -rd), 0., 1.), 5.);

可以给涅菲尔乘以一个颜色值,这样就可以给边缘的描绘颜色了。下面就是完整的代码:

   const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;

float sdSphere(vec3 p, float r )
{
  vec3 offset = vec3(0, 0, -2);
  return length(p - offset) - r;
}

float sdScene(vec3 p) {
  return sdSphere(p, 1.);
}

float rayMarch(vec3 ro, vec3 rd) {
  float depth = MIN_DIST;

  for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + depth * rd;
    float d = sdScene(p);
    depth += d;
    if (d < PRECISION || depth > MAX_DIST) break;
  }

  return depth;
}

vec3 calcNormal(vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005;
    return normalize(
      e.xyy * sdScene(p + e.xyy) +
      e.yyx * sdScene(p + e.yyx) +
      e.yxy * sdScene(p + e.yxy) +
      e.xxx * sdScene(p + e.xxx));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
  vec3 backgroundColor = vec3(0.1);
  vec3 col = vec3(0);

  vec3 ro = vec3(0, 0, 3);
  vec3 rd = normalize(vec3(uv, -1));

  float d = rayMarch(ro, rd);
  
  if (d > MAX_DIST) {
    col = backgroundColor;
  } else {
    vec3 p = ro + rd * d;
    vec3 normal = calcNormal(p);
    vec3 lightPosition = vec3(4, 4, 7);
    vec3 lightDirection = normalize(lightPosition - p);

    float diffuse = clamp(dot(normal, lightDirection), 0., 1.);
    vec3 diffuseColor = vec3(0, 0.6, 1);

    float fresnel = pow(clamp(1. - dot(normal, -rd), 0., 1.), 5.);
    vec3 rimColor = vec3(1, 1, 1);

    col = diffuse * diffuseColor + fresnel * rimColor; // add the fresnel contribution
  }

  fragColor = vec4(col, 1.0);
}
 

运行以上的代码,我们就可以看到蓝色的球的边缘有一层白色的光。这就是模拟了光线以某个角度照射到球上产生的特殊效果。

随意修改指数和边缘的颜色,调整立场效果。

  float fresnel = pow(clamp(1. - dot(normal, -rd), 0., 1.), 0.5);
vec3 rimColor = vec3(1, 0, 1);

col = diffuse * diffuseColor + fresnel * rimColor;

总结

本篇文章中我们学些了应用涅菲尔反射模型给物体的边缘添加光照效果。如果你需要制造出一些玻璃或者塑料的光照效果,添加此类光照会让物体看起来更加地真实。

参考资源

posted on 2021-12-27 10:21  chen·yan  阅读(301)  评论(0编辑  收藏  举报