Navigate
Search topics across all sections
Hologram Effect
Like a sci-fi forcefield brought to life. The hologram effect combines three ingredients: fresnel glow that makes edges bright and centers transparent, animated scan lines sweeping vertically, and random glitch flickers. Together they make any 3D shape look like a holographic projection.
You set up the fresnel effect but it only glows on one side of the mesh. When you orbit around, the glow stays on the same face instead of appearing on the silhouette edges as expected.
Fresnel glow is fixed to one side of the mesh regardless of camera angle. Edges do not glow.
Real-world
Think about a sci-fi forcefield in a movie. It is not a solid object -- it is translucent in the middle but glows bright at the edges where your line of sight is nearly parallel to the surface. That edge glow is the fresnel effect, and it is the backbone of every hologram shader.
Now add scan lines -- horizontal stripes that scroll upward, like old CRT monitors or a laser scanning the object. These are just a sine wave applied to the Y position: wherever the sine is above 0.5, the surface is bright; below 0.5, it is dark.
Finally, throw in occasional glitch flickers -- random horizontal slices that flash bright for a single frame, as if the holographic projector is slightly unstable. That is the complete recipe: fresnel + scanlines + glitch.
How the Hologram Works
Three effects layered together in the fragment shader.
Vertex Shader
Pass position, normal, worldPos
Fresnel
Edge glow from view angle
Scan Lines
Scrolling horizontal stripes
Glitch
Random brightness flashes
Building the Hologram Step by Step
Each effect is simple on its own. The magic is in how they combine.
Step 1 -- Set up the vertex shader with world-space data
varying vec3 vPosition;
varying vec3 vNormal;
varying vec3 vWorldPosition;
void main() {
vPosition = position;
vNormal = normalize(normalMatrix * normal);
vec4 worldPos = modelMatrix * vec4(position, 1.0);
vWorldPosition = worldPos.xyz;
gl_Position = projectionMatrix
* modelViewMatrix * vec4(position, 1.0);
}The vertex shader must pass three things: the local position (for scanlines), the world-space normal (for fresnel), and the world-space position (for the view direction calculation). The normalMatrix correctly transforms normals even with non-uniform scaling.
Step 2 -- Calculate the fresnel effect
// View direction: camera to surface
vec3 viewDir = normalize(
cameraPosition - vWorldPosition
);
// Fresnel: bright when view grazes the surface
float fresnel = pow(
1.0 - abs(dot(viewDir, vNormal)),
uFresnelPower
);
// fresnelPower=1: wide glow
// fresnelPower=5: tight edge-only glowThe dot product between the view direction and surface normal is 1.0 when looking straight at the surface and 0.0 at grazing angles. Subtracting from 1.0 inverts it so edges are bright. The power exponent controls how quickly the glow falls off -- higher values make a tighter, more dramatic edge glow.
Step 3 -- Add scrolling scan lines
// Scan lines based on Y position + time
float scanline = step(0.5,
sin(vPosition.y * uScanlineCount
+ uTime * uScanlineSpeed)
* 0.5 + 0.5
);
// Result: alternating bright/dark horizontal bands
// that scroll upward over timeThe sine wave creates a smooth oscillation along the Y axis. We remap it from [-1, 1] to [0, 1] then threshold it with step() at 0.5 to get sharp on/off bands. Adding uTime multiplied by speed makes the pattern scroll upward each frame.
Step 4 -- Layer in glitch and flicker
// Glitch: random bright slices
float block = floor(vPosition.y * 20.0 + uTime * 3.0);
float glitch = step(
1.0 - uGlitchIntensity * 0.15,
fract(sin(block * 12.9898) * 43758.5453)
);
// Flicker: subtle overall brightness pulsing
float flicker = 0.85 + 0.15
* sin(uTime * 15.0)
* sin(uTime * 7.3);
// Combine all effects
float alpha = (fresnel * 0.6 + 0.15)
+ scanline * 0.2;
alpha *= flicker;
alpha += glitch * 0.5;The glitch divides the mesh into horizontal blocks and randomly brightens some of them each frame. The pseudo-random function fract(sin(x) * big_number) is a classic GPU trick for generating randomness. The flicker multiplies two offset sine waves for an organic brightness oscillation.
What you just learned
The fresnel effect uses dot(viewDir, normal) to make edges glow bright while centers stay transparent.
Scan lines are just sin(y * count + time * speed) thresholded with step() for sharp bands.
Pseudo-random glitch uses fract(sin(x) * 43758.5453) -- a classic GPU random number trick.
Additive blending + depthWrite=false makes overlapping transparent parts glow instead of occlude.
All three effects (fresnel, scanlines, glitch) are computed entirely in the fragment shader -- no textures needed.
Question
The fresnel effect relies on dot(viewDirection, normal). What happens to the fresnel glow if you use a completely flat plane instead of a curved mesh like the torus knot? Would you still see edge glow, and why or why not?
Think about it...
You apply the hologram shader to a cube, but the fresnel glow shows hard edges at the cube's corners instead of smooth gradients. Why?
Hint: Think about how normals differ between a smooth sphere and a hard-edged cube...
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set scanlineCount to 5 — chunky lines
Try This!
Beginner
Max glitchIntensity — glitchy!
Try This!
Beginner
Set opacity to 0.1 — barely visible
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// In fragment shader:
vec3 viewDir = normalize(
cameraPosition - vPosition // model space!
);
float fresnel = pow(
1.0 - dot(viewDir, normal), 2.0
);
// Fresnel rotates with the object!<shaderMaterial
transparent
// Using default NormalBlending
vertexShader={vs}
fragmentShader={fs}
/>
// Looks opaque and solid, not glowing// Fragment shader:
float scanline = step(0.5,
sin(vPosition.y * 20.0) * 0.5 + 0.5
);
// Static stripes -- not animated!Best Practices
Use abs() in fresnel dot
Without abs(), back-facing normals produce negative dot products that break the fresnel calculation. abs() ensures it works from both sides when using DoubleSide rendering.
Keep glitch subtle
Too much glitch intensity makes the hologram unreadable. Start with intensity around 1-2 and only increase for dramatic effects. The best holograms are mostly stable with occasional glitches.
Pair with dark backgrounds
Additive blending adds light, so holograms glow best against dark backgrounds. On a white background, additive blending has no visible effect since you cannot add to white.
Choose curved geometry
Fresnel looks best on rounded shapes with smooth normals. Torus knots, spheres, and organic shapes work great. For hard-edge geometry, compute smooth normals first.