Skip to main content
GameDev.net gamedev.net

Fresnel and Rim Lighting

by GameDev.net · GLSL ES 3.00 (WebGL2) · 30 Aug 2026

Use in your engine

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.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image8
1 uniform float uF0; // @param 0.0..0.8 = 0.08 "Base reflectance"
2 uniform float uRimStrength; // @param 0.0..2.0 = 0.9 "Rim strength"
3 uniform float uRimPower; // @param 0.5..8.0 = 2.4 "Rim power"
4 uniform float uRimDirection; // @param -1.0..1.0 = 0.35 "Rim direction"
5
6 float mapScene(vec3 p) {
7 float leftSphere = length(p - vec3(-0.72, 0.0, 0.0)) - 0.58;
8 float rightSphere = length(p - vec3(0.72, 0.0, 0.0)) - 0.58;
9 return min(leftSphere, rightSphere);
10 }
11
12 float marchScene(vec3 ro, vec3 rd) {
13 float distanceAlongRay = 0.0;
14 for (int i = 0; i < 72; i++) {
15 float distanceToScene = mapScene(ro + rd * distanceAlongRay);
16 if (distanceToScene < 0.001) return distanceAlongRay;
17 distanceAlongRay += distanceToScene;
18 if (distanceAlongRay > 8.0) break;
19 }
20 return -1.0;
21 }
22
23 vec3 sceneNormal(vec3 p) {
24 vec2 e = vec2(0.0015, 0.0);
25 return normalize(vec3(
26 mapScene(p + e.xyy) - mapScene(p - e.xyy),
27 mapScene(p + e.yxy) - mapScene(p - e.yxy),
28 mapScene(p + e.yyx) - mapScene(p - e.yyx)));
29 }
30
31 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
32 vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
33 vec3 ro = vec3(0.0, 0.0, -3.1);
34 vec3 rd = normalize(vec3(uv, 1.75));
35 float hit = marchScene(ro, rd);
36 vec3 color = mix(vec3(0.025, 0.035, 0.07), vec3(0.10, 0.14, 0.22), uv.y + 0.5);
37 if (hit > 0.0) {
38 vec3 p = ro + rd * hit;
39 vec3 normal = sceneNormal(p);
40 vec3 viewDirection = normalize(ro - p);
41 float facing = clamp(dot(normal, viewDirection), 0.0, 1.0);
42 float fresnel = uF0 + (1.0 - uF0) * pow(1.0 - facing, 5.0);
43 vec3 lightDirection = normalize(vec3(-0.45, 0.75, -0.6));
44 float diffuse = max(dot(normal, lightDirection), 0.0);
45 vec3 base = vec3(0.08, 0.34, 0.62);
46 color = base * (0.16 + diffuse * 0.55);
47 color += vec3(0.65, 0.88, 1.0) * fresnel * 0.75;
48 vec3 rimDirection = normalize(vec3(uRimDirection, 0.65, -0.75));
49 float rim = pow(1.0 - facing, uRimPower) * max(dot(normal, rimDirection), 0.0);
50 if (p.x > 0.0) color += vec3(1.0, 0.35, 0.78) * rim * uRimStrength;
51 }
52 color *= 1.0 - 0.08 * smoothstep(0.0, 0.012, abs(uv.x));
53 color = color / (color + vec3(1.0));
54 color = pow(color, vec3(1.0 / 2.2));
55 fragColor = vec4(color, 1.0);
56 }
57

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.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...