Skip to content
Light · FresnelBeginner

Rim light

A glow along the silhouette that follows the camera.

NormalViewSurface
  1. 01GoalLight
  2. 02TechniqueFresnel
  3. 03ExecutionRim light
Test mesh

Parameters

3.0
#59bfff
1.50

Copy for your platform

  • GLSLCopy
  • GodotCopy

Node chain

Built step by step, in GLSL style, with its node names.

Built step by step, in Godot style, with its node names.

No Unreal Engine 5 version of this card yet, so the nodes show the GLSL names.

No Unity version of this card yet, so the nodes show the GLSL names.

No Blender version of this card yet, so the nodes show the GLSL names.

  1. dotDotProductdotdotdot
    Normal
    View

    01 Facing ratio

  2. 1.0 - xOneMinus1.0 - x1.0 - x1.0 - x

    02 Invert

  3. powPowpowpowpow

    03 Narrow the band

  4. *Multiply***

    04 Tint

How it works

Placeholder text for the walking skeleton. Israel replaces it.

A dot product of the surface normal and the direction toward the camera is 1 where the surface faces you and 0 at the silhouette. Inverting it gives a value that is bright only at the edges. Raising that value to a power narrows the band, and multiplying by a color and an intensity tints it.

Make it in GLSL

Placeholder text for the walking skeleton. Israel replaces it.

Paste the function into a fragment shader that passes in the world-space normal and the direction toward the camera.

Make it in Godot

VisualShader also has a built-in Fresnel node that does the first three steps.

Placeholder text for the walking skeleton. Israel replaces it.

Paste the shader into a new ShaderMaterial on a MeshInstance3D. It writes the rim to EMISSION over a black base color and turns off specular and ambient light, so the mesh shows only the rim color, and with Glow on in the WorldEnvironment it blooms.

No Unreal Engine 5 tutorial for this card yet.

No Unity tutorial for this card yet.

No Blender tutorial for this card yet.

Code

rim-light.fragGLSL
#version 300 es// Fresnel → Rim light, web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_power; // Power: 3.0 (0.5 to 8.0)uniform vec3 p_color; // Color: vec3(0.35, 0.75, 1.0), sRGB; upload it decoded to linearuniform float p_intensity; // Intensity: 1.5 (0.0 to 4.0)in vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};vec3 u_toSrgb(vec3 c) {	return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}// i.view points from the surface toward the camera, so dot(normal, view) is 1 where the surface faces the camera// and 0 at the silhouette. max() drops back faces, which would otherwise go above 1 after the inversion.vec4 effect(Inputs i) {	float facing = max(dot(i.normal, i.view), 0.0);	float edge = 1.0 - facing;	float rim = pow(edge, p_power);	vec3 glow = rim * p_color * p_intensity;	return vec4(glow, 1.0);}void main() {	Inputs i;	i.uv = v_uv;	i.position = v_position;	// Interpolation shortens the vectors between vertices	i.normal = normalize(v_normal);	i.view = normalize(v_view);	i.time = u_time;	i.fragCoord = gl_FragCoord.xy;	i.resolution = u_resolution;	vec4 frag_out = effect(i);	// The output is sRGB: clamped and encoded once, here, with no tone mapping; opaque, so alpha is 1	fragColor = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}
rim-light.gdshaderGodot shader
// Fresnel → Rim light, Godot 4. From Creative Center, MIT license.shader_type spatial;// Emission: a black base color, no specular or ambient light, and the effect as EMISSION, which glow catchesrender_mode specular_disabled, ambient_light_disabled;uniform float power : hint_range(0.5, 8.0, 0.1) = 3.0;uniform vec3 color : source_color = vec3(0.35, 0.75, 1.0);uniform float intensity : hint_range(0.0, 4.0, 0.05) = 1.5;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// i.view points from the surface toward the camera, so dot(normal, view) is 1 where the surface faces the camera// and 0 at the silhouette. max() drops back faces, which would otherwise go above 1 after the inversion.vec4 effect(Inputs i) {	float facing = max(dot(i.normal, i.view), 0.0);	float edge = 1.0 - facing;	float rim = pow(edge, power);	vec3 glow = rim * color * intensity;	return vec4(glow, 1.0);}void fragment() {	Inputs i;	// Godot's UV starts at the top-left; the effect expects GLSL's bottom-left	i.uv = vec2(UV.x, 1.0 - UV.y);	// VERTEX, NORMAL and VIEW are in view space here; the effect works in world space	i.position = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;	i.normal = normalize((INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);	i.view = normalize((INV_VIEW_MATRIX * vec4(VIEW, 0.0)).xyz);	i.time = TIME;	// FRAGCOORD's origin is the top-left too	i.resolution = VIEWPORT_SIZE;	i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y);	vec4 frag_out = effect(i);	ALBEDO = vec3(0.0);	EMISSION = frag_out.rgb;}

No Unreal Engine 5 code for this card yet.

No Unity code for this card yet.

No Blender code for this card yet.

Further reading