Rim light
A glow along the silhouette that follows the camera.
- 01GoalLight
- 02TechniqueFresnel
- 03ExecutionRim light
Parameters
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.
dotDotProductdotdotdot NormalView01 Facing ratio
1.0 - xOneMinus1.0 - x1.0 - x1.0 - x In02 Invert
powPowpowpowpow In03 Narrow the band
*Multiply*** In04 Tint
How it works
Placeholder text for the walking skeleton. Israel replaces it.
A dot productMultiplies two vectors component by component and adds the results. For unit vectors it is the cosine of the angle between them, from 1 (same direction) to -1 (opposite). Dot product Multiplies two vectors component by component and adds the results. For unit vectors it is the cosine of the angle between them, from 1 (same direction) to -1 (opposite). Normal A unit vector perpendicular to a surface, pointing the way it faces. Lighting and Fresnel effects compare it with other directions.dot(a, b) = |a| |b| cos θ
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
#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);}// 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
- PBR: Theory (opens in a new tab)Article
- Schlick's approximation (opens in a new tab)Reference
- dot (opens in a new tab)Reference
- pow (opens in a new tab)Reference