Hologram shader

홀로그램 셰이더

A shader combining fresnel rim light, scanlines, and flicker to fake the blue sci-fi hologram look.

Also known as: Scanline fresnelSci-fi hologramRim-light flicker
···
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x040810);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 0.4, 4.6);
camera.lookAt(0, 0, 0);

const vertexShader = [
  'varying vec3 vNormal;',
  'varying vec3 vViewDir;',
  'varying vec2 vUvV;',
  'void main() {',
  '  vNormal = normalize(normalMatrix * normal);',
  '  vec4 mv = modelViewMatrix * vec4(position, 1.0);',
  '  vViewDir = normalize(-mv.xyz);',
  '  vUvV = uv;',
  '  gl_Position = projectionMatrix * mv;',
  '}',
].join('\n');

const fragmentShader = [
  'uniform float uTime;',
  'varying vec3 vNormal;',
  'varying vec3 vViewDir;',
  'varying vec2 vUvV;',
  'float hash(float x) { return fract(sin(x) * 43758.5453); }',
  'void main() {',
  '  vec3 n = normalize(vNormal);',
  '  vec3 v = normalize(vViewDir);',
  '  float fresnel = pow(1.0 - max(dot(n, v), 0.0), 2.2);',
  '  float scan = sin((vUvV.y * 60.0) - uTime * 6.0) * 0.5 + 0.5;',
  '  scan = pow(scan, 6.0);',
  '  float flicker = 0.85 + 0.15 * hash(floor(uTime * 14.0));',
  '  vec3 base = vec3(0.15, 0.75, 0.95);',
  '  vec3 color = base * (0.35 + fresnel * 1.3 + scan * 0.5) * flicker;',
  '  float alpha = clamp(fresnel * 0.9 + scan * 0.3 + 0.12, 0.0, 1.0);',
  '  gl_FragColor = vec4(color, alpha);',
  '}',
].join('\n');

const mat = new THREE.ShaderMaterial({
  uniforms: { uTime: { value: 0 } },
  vertexShader, fragmentShader,
  transparent: true, side: THREE.DoubleSide, blending: THREE.AdditiveBlending, depthWrite: false,
});
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(1.15, 3), mat);
scene.add(mesh);

const ring = new THREE.Mesh(new THREE.RingGeometry(1.0, 1.3, 48), new THREE.MeshBasicMaterial({ color: 0x2ad8ff, transparent: true, opacity: 0.5, side: THREE.DoubleSide }));
ring.rotation.x = -Math.PI / 2;
ring.position.y = -1.3;
scene.add(ring);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  mat.uniforms.uTime.value = time;
  mesh.rotation.y = time * 0.4;
  mesh.rotation.x = Math.sin(time * 0.2) * 0.15;
  ring.rotation.z = time * 0.3;
  renderer.render(scene, camera);
});

The hologram look comes from three ingredients layered together. First, fresnel = pow(1 - dot(normal, viewDir), n) — a rim light that brightens at grazing edges, giving that "hollow inside, glowing edge" hologram impression. Second, scanlines: running sin() over the UV’s y-coordinate with a time offset produces horizontal bands that mimic a CRT or projector’s stripes.

Third is flicker. Feeding a time value cut into integer steps — hash(floor(uTime * 14.0)) — into a hash function produces brightness that jumps randomly every 1/14th of a second instead of drifting smoothly. That "staircase" noise reads as far more artificial and electronic than natural shimmer.

The material sets blending: THREE.AdditiveBlending and depthWrite: false so overlapping light gets brighter and whatever’s behind shows through. Setting side: THREE.DoubleSide to also draw inward-facing surfaces is the other key detail — without it the hologram would visibly break wherever the mesh curves away from the camera.

When to use

Sci-fi UI, AR projections, or a character/product hologram preview — anywhere something needs to read as "an image made of light."