Hologram
A see-through glowing shell, a Fresnel rim on both sides plus scanlines crawling up the world's height.
- 01GoalLight
- 02TechniqueFresnel
- 03ExecutionHologram
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.
absAbsabsabsabs NormalViewPositionTime01 Facing, both sides
powPowpowpowpow In02 Fresnel rim
fractFractfractfractfract In03 Repeat up the height
smoothstepSmoothStepsmoothstepsmoothstepsmoothstep In04 Thin soft lines
*Multiply*** In05 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 blendingCombining the color a fragment shader outputs with the color already in the framebuffer, instead of replacing it. Transparency, additive glow and multiply all come from it. Blending Combining the color a fragment shader outputs with the color already in the framebuffer, instead of replacing it. Transparency, additive glow and multiply all come from it.result = src × srcFactor + dst × dstFactor, 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
- Facing, both sides. The 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).Fundamental
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).
dot(a, b) = |a| |b| cos θSourcesof the normalA unit vector perpendicular to a surface, pointing the way it faces. Lighting and Fresnel effects compare it with other directions.- dot (opens in a new tab)Reference
- Dot product (opens in a new tab)Reference
FundamentalNormal
A unit vector perpendicular to a surface, pointing the way it faces. Lighting and Fresnel effects compare it with other directions.
Sourcesand the view direction, as in Rim light, but throughabs()instead ofmax(…, 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. Withmax()the whole back would count as edge and glow solid. - Fresnel rim.
pow(1.0 - facing, p_power), the FresnelSurfaces reflect more light when seen at a grazing angle than when seen head-on. Shaders often fake it with the dot product of the normal and the view direction.TechniqueFresnel effect
Surfaces reflect more light when seen at a grazing angle than when seen head-on. Shaders often fake it with the dot product of the normal and the view direction.
F = F0 + (1 − F0)(1 − cos θ)^5Sourcesshape: 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. - Repeat up the height.
fract()of the world height times a density repeats a 0-to-1 ramp every1 / p_densityunits, 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. - 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 hardstep()flickers as the lines crawl across pixels. - 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
#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));}// 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.