Skip to content
Light · FresnelBeginner

Hologram

A see-through glowing shell, a Fresnel rim on both sides plus scanlines crawling up the world's height.

NormalViewPositionTimeSurface
Open in Playground
  1. 01GoalLight
  2. 02TechniqueFresnel
  3. 03ExecutionHologram
Test mesh

Parameters

#4dd9ff
2.5
0.02
12.0
0.60
0.35

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. absAbsabsabsabs
    Normal
    View
    Position
    Time

    01 Facing, both sides

  2. powPowpowpowpow

    02 Fresnel rim

  3. fractFractfractfractfract

    03 Repeat up the height

  4. smoothstepSmoothStepsmoothstepsmoothstepsmoothstep

    04 Thin soft lines

  5. *Multiply***

    05 Tint and add up

How it works

A hologram is light without a surface: you see through it, and what you see is its outline. Three choices in the card make that read. The color is written as emission, so nothing lights it. The pass draws with additive blending, adding its light to what is behind instead of covering it, so the dark middle of the shell adds only a faint glow. And culling is off, so the back faces draw too and the back of the shell shows through the front, which gives it depth.

Step by step

  1. Facing, both sides. The dot product of the normal and the view direction, as in Rim light, but through abs() instead of max(…, 0.0). A back face’s normal points away from the camera, so its dot product is negative; abs() treats it like a front face, and the far side of the shell gets the same rim. With max() the whole back would count as edge and glow solid.
  2. Fresnel rim. pow(1.0 - facing, p_power), the Fresnel shape: dark in the middle, bright at the outline. In the preview the back faces land on the same rim as the front ones (on a sphere a view ray meets the surface at the same angle going in and coming out), so the outline is twice as bright; what you see through the front is the back layer’s scanlines, curving the other way.
  3. Repeat up the height. fract() of the world height times a density repeats a 0-to-1 ramp every 1 / p_density units, and subtracting time slides the ramps upward. World position, not UV, keeps the lines level and evenly spaced on any mesh, and the same height on the front and back faces lines the two layers up.
  4. Thin soft lines. The distance from the middle of each ramp, abs(band - 0.5), is 0 at the line’s center; 1.0 - smoothstep(0.0, 0.15, …) turns it into a line that fades out over 0.15 of the spacing on each side. The soft edge matters: a hard step() flickers as the lines crawl across pixels.
  5. Tint and add up. The rim, a constant fill so the body never vanishes completely, and the lines at their own brightness, summed and multiplied by the color. Because the blend adds, overlapping layers simply get brighter, and there’s no sorting to get wrong.

Orbit the preview: the rims stay on the outlines while the lines stay level in the world.

Make it in GLSL

Draw it after the opaque scene with gl.enable(gl.BLEND), gl.blendFunc(gl.ONE, gl.ONE), gl.disable(gl.CULL_FACE) and gl.depthMask(false), keeping the depth test on so solid objects in front still hide it. The generated main() writes the light premultiplied, as ONE, ONE expects.

The copy button gives a complete WebGL2 fragment shader. Your vertex shader writes the world-space position, normal and view direction (toward the camera) to v_position, v_normal and v_view, and declares v_uv; the generated main() normalizes the two directions per pixel. Feed u_time the seconds since start, every frame, or the lines stand still.

Draw it with the blend and depth state in the note above. Because addition is order-independent, several holograms can overlap without sorting, which is the main reason glow effects use additive blending over alpha.

Make it in Godot

The export sets render_mode blend_add, cull_disabled and writes EMISSION over a black ALBEDO with specular and ambient light off. blend_add puts the material in Godot's transparent pass, so it draws after the opaque scene, doesn't write depth and is never hidden by its own faces.

Make a MeshInstance3D (a SphereMesh or a character mesh), give it a new ShaderMaterial, and paste the shader into a new Shader. Put it over a dark background, or a WorldEnvironment with a dark sky, to see the additive glow; turn on Glow in the WorldEnvironment for a halo around the rims.

In VisualShader, the rim is the Fresnel node fed by NORMAL and VIEW, but with culling off its back faces need the abs() version: a DotProduct of the two inputs, a FloatFunc Abs, a FloatFunc OneMinus and a FloatOp Pow. The lines are a world-space Y (an Input VERTEX through a TransformVecMult by INV_VIEW_MATRIX, then a VectorDecompose), a MultiplyAdd with the density and minus TIME times the speed (a FloatOp Multiply by a negated speed), a FloatFunc Fract, a FloatOp Subtract of 0.5, a FloatFunc Abs, a SmoothStep from 0.0 to 0.15 and a FloatFunc OneMinus for the lines, then a FloatOp Multiply by a FloatParameter for the line brightness. Add the rim, a FloatParameter fill and the lines, multiply by a ColorParameter into Emission, and set Blend Mode to Add and Cull Mode to Disabled in the shader’s render modes.

No Unreal Engine 5 tutorial for this card yet.

No Unity tutorial for this card yet.

No Blender tutorial for this card yet.

Code

hologram.fragGLSL
#version 300 es// Fresnel → Hologram, 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 vec3 p_color; // Color: vec3(0.3, 0.85, 1.0), sRGB; upload it decoded to linearuniform float p_power; // Rim power: 2.5 (0.5 to 8.0)uniform float p_fill; // Body glow: 0.02 (0.0 to 0.5)uniform float p_density; // Lines per unit: 12.0 (1.0 to 40.0)uniform float p_speed; // Line speed: 0.6 (-3.0 to 3.0)uniform float p_lineStrength; // Line brightness: 0.35 (0.0 to 1.5)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));}vec4 effect(Inputs i) {	// With culling off the back faces draw too, and their normals point away from the camera: abs() gives them the	// same rim as the front, so the far side of the shell glows through.	float facing = abs(dot(i.normal, i.view));	float rim = pow(1.0 - facing, p_power);	// World height instead of uv: the lines stay level and evenly spaced whatever the mesh's unwrap looks like.	float band = fract(i.position.y * p_density - i.time * p_speed);	float lines = 1.0 - smoothstep(0.0, 0.15, abs(band - 0.5));	vec3 emitted = p_color * (rim + p_fill + p_lineStrength * lines);	return vec4(emitted, 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	// Additive light over a transparent target: premultiplied, as bright as its alpha, so dark adds nothing	vec3 frag_light = u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)) * frag_out.a;	fragColor = vec4(frag_light, max(max(frag_light.r, frag_light.g), frag_light.b));}
hologram.gdshaderGodot shader
// Fresnel → Hologram, 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, blend_add, cull_disabled;uniform vec3 color : source_color = vec3(0.3, 0.85, 1.0);uniform float power : hint_range(0.5, 8.0, 0.1) = 2.5;uniform float fill : hint_range(0.0, 0.5, 0.01) = 0.02;uniform float density : hint_range(1.0, 40.0, 0.5) = 12.0;uniform float speed : hint_range(-3.0, 3.0, 0.05) = 0.6;uniform float lineStrength : hint_range(0.0, 1.5, 0.05) = 0.35;struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};vec4 effect(Inputs i) {	// With culling off the back faces draw too, and their normals point away from the camera: abs() gives them the	// same rim as the front, so the far side of the shell glows through.	float facing = abs(dot(i.normal, i.view));	float rim = pow(1.0 - facing, power);	// World height instead of uv: the lines stay level and evenly spaced whatever the mesh's unwrap looks like.	float band = fract(i.position.y * density - i.time * speed);	float lines = 1.0 - smoothstep(0.0, 0.15, abs(band - 0.5));	vec3 emitted = color * (rim + fill + lineStrength * lines);	return vec4(emitted, 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;	ALPHA = frag_out.a;}

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