Fresnel and Rim Lighting
by GameDev.net · GLSL ES 3.00 (WebGL2) · 30 Aug 2026
Run the shader to adjust these controls.
What it demonstrates
This scene isolates two view-dependent edge responses on matching raymarched spheres. The left sphere shows Schlick Fresnel, a physical approximation that rises from a base reflectance toward full reflection at grazing angles. The right sphere adds a directed artistic rim light, making the distinction between material response and a controllable readability effect visible.
Shader inputs
void mainImage(out vec4 fragColor, in vec2 fragCoord)
Called once per pixel. Write the colour to fragColor.
-
iResolutionvec3 - Viewport size in pixels (z is the pixel aspect ratio).
-
iTimefloat - Seconds since the shader started.
-
iTimeDeltafloat - Seconds since the previous frame.
-
iFrameRatefloat - Frames per second, smoothed.
-
iFrameint - Frames rendered since the start.
-
iMousevec4 - Mouse position: xy while held, zw of the last click.
-
iDatevec4 - Year, month, day, and seconds within the day.
-
iChannel0sampler2D - Texture bound to channel 0.
-
iChannel1sampler2D - Texture bound to channel 1.
-
iChannel2sampler2D - Texture bound to channel 2.
-
iChannel3sampler2D - Texture bound to channel 3.
-
iChannelResolutionvec3[4] - Pixel size of each bound channel texture.
-
iChannelTimefloat[4] - Playback time of each channel, in seconds.
-
iSampleRatefloat - Audio sample rate, always 44100.
Main Image8
uniform float uF0; // @param 0.0..0.8 = 0.08 "Base reflectance"
uniform float uRimStrength; // @param 0.0..2.0 = 0.9 "Rim strength"
uniform float uRimPower; // @param 0.5..8.0 = 2.4 "Rim power"
uniform float uRimDirection; // @param -1.0..1.0 = 0.35 "Rim direction"
float mapScene(vec3 p) {
float leftSphere = length(p - vec3(-0.72, 0.0, 0.0)) - 0.58;
float rightSphere = length(p - vec3(0.72, 0.0, 0.0)) - 0.58;
return min(leftSphere, rightSphere);
}
float marchScene(vec3 ro, vec3 rd) {
float distanceAlongRay = 0.0;
for (int i = 0; i < 72; i++) {
float distanceToScene = mapScene(ro + rd * distanceAlongRay);
if (distanceToScene < 0.001) return distanceAlongRay;
distanceAlongRay += distanceToScene;
if (distanceAlongRay > 8.0) break;
}
return -1.0;
}
vec3 sceneNormal(vec3 p) {
vec2 e = vec2(0.0015, 0.0);
return normalize(vec3(
mapScene(p + e.xyy) - mapScene(p - e.xyy),
mapScene(p + e.yxy) - mapScene(p - e.yxy),
mapScene(p + e.yyx) - mapScene(p - e.yyx)));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
vec3 ro = vec3(0.0, 0.0, -3.1);
vec3 rd = normalize(vec3(uv, 1.75));
float hit = marchScene(ro, rd);
vec3 color = mix(vec3(0.025, 0.035, 0.07), vec3(0.10, 0.14, 0.22), uv.y + 0.5);
if (hit > 0.0) {
vec3 p = ro + rd * hit;
vec3 normal = sceneNormal(p);
vec3 viewDirection = normalize(ro - p);
float facing = clamp(dot(normal, viewDirection), 0.0, 1.0);
float fresnel = uF0 + (1.0 - uF0) * pow(1.0 - facing, 5.0);
vec3 lightDirection = normalize(vec3(-0.45, 0.75, -0.6));
float diffuse = max(dot(normal, lightDirection), 0.0);
vec3 base = vec3(0.08, 0.34, 0.62);
color = base * (0.16 + diffuse * 0.55);
color += vec3(0.65, 0.88, 1.0) * fresnel * 0.75;
vec3 rimDirection = normalize(vec3(uRimDirection, 0.65, -0.75));
float rim = pow(1.0 - facing, uRimPower) * max(dot(normal, rimDirection), 0.0);
if (p.x > 0.0) color += vec3(1.0, 0.35, 0.78) * rim * uRimStrength;
}
color *= 1.0 - 0.08 * smoothstep(0.0, 0.012, abs(uv.x));
color = color / (color + vec3(1.0));
color = pow(color, vec3(1.0 / 2.2));
fragColor = vec4(color, 1.0);
}
Learn from this shader
How it works
A short sphere-tracing loop finds the surfaces and finite differences recover their normals. Normal dotted with the direction toward the camera produces facing: one at the center and zero at the silhouette. Schlick raises one minus facing to the fifth power and blends from Base reflectance to one. The spheres share identical blue albedo and diffuse lighting. The rim uses a separate exponent to control edge width, then weights that edge by a fixed rim-light direction. A Reinhard tone map followed by gamma display encoding preserves bright edge detail instead of clipping it.
Try changing
Set Rim strength to zero and compare the identical Fresnel silhouettes. Increase Base reflectance to model a more reflective material; notice that the center changes much more than the edge. Lower Rim power for a broad halo or raise it for a thin outline. Rim direction rotates the response around the right sphere without changing Fresnel.
Using it in a game
Use Schlick when balancing reflected environment light against a surface layer. Use rim light separately for selected characters, pickups, or silhouettes that must survive a busy background. Keeping the terms separate avoids pretending an art-directed light is physical and lets designers tune readability without disturbing the material's base reflectance.
Explore the techniques
Continue with curated explanations and progressively related examples.
Discussion