Skip to main content

12.1菲涅耳和边缘照明

初始设置

我们将从一个基本的光线行进模板开始。

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);
}

运行此代码时,您应该会看到一个只有漫反射 (Lambertian) 反射的蓝色球体。

-

菲涅耳方程(Fresnel equations)描述了光入射到两种不同光学介质之间的界面上的反射和透射。简单来说,这意味着当您从掠射角度 grazing angles 观察物体时,它们的照明可能会有所不同。

术语光学介质是指光穿过的材料类型。不同的材质往往具有不同的折射率,这使得光线看起来是弯曲的。

-

折射 by 维基百科上的数据

空气是一种介质。它的折射率通常约为 1.000293。金刚石等材料具有高折射率。钻石的折射率为 2.417。高折射率意味着光线看起来会弯曲得更厉害。

菲涅耳方程可能变得非常复杂。对于计算机图形学,您通常会看到人们使用 Schlick 近似来近似反射的菲涅耳贡献。

-

维基百科上的 Schlick 近似值

上述方程计算了菲涅耳对反射的贡献 R,其中 R0 是平行于法线入射的光的反射系数(通常当 θ 等于零时)。

cos θ 的值等于表面法线和入射光来自方向之间的点积。但是,在我们的代码中,我们将使用光线方向 rd

为了我们的示例,我们将假设空气和球体的折射率都等于 1。这将有助于简化我们的计算。这意味着 R0 等于零。

n1 = 1
n2 = 1

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

R0 等于零时,我们可以进一步简化菲涅耳反射方程。

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

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

在 GLSL 代码中,可以写成:

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

但是,我们会固定这些值以确保保持 01 之间的范围。我们还使用 -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);
}

如果你运行这段代码,你应该会看到蓝色球体的一个细细的白色边缘。这模拟了光线照射到球体的掠射角度的效果。

-

你可以调整指数和轮辋颜色,以获得类似 力场(force field) 的效果。

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

col = diffuse * diffuseColor + fresnel * rimColor;

-

结论

在本文中,我们学习了如何通过应用菲涅耳反射在对象周围添加边缘照明。如果您正在处理模仿玻璃塑料的对象,那么添加菲涅耳有助于使它们更逼真。