Shadertoy 教程 Part 12 - 涅菲爾和邊緣光

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;

總結

本篇文章中我們學些了應用涅菲爾反射模型給物體的邊緣添加光照效果。如果你需要製造出一些玻璃或者塑料的光照效果,添加此類光照會讓物體看起來更加地真實。

參考資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章