🧠 Description
This project demonstrates a next-generation hero section design powered by WebGL shaders and advanced UI effects.
The background is a fully procedural ray-marched prism object rendered in real time using GLSL fragment shaders, featuring refraction, fresnel lighting, glow, and nebula-style space visuals.
On top of the shader canvas sits a glassmorphism-based UI layer, including animated gradient borders, shimmer effects, glowing typography, and interactive buttons.
Mouse movement subtly affects both the 3D shader camera and the UI lighting, creating a deeply immersive, cinematic experience — ideal for landing pages, creative portfolios, or premium product showcases.
💻 HTML Structure
1<!DOCTYPE html>
2<html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <title>WebGL Shader Hero Design</title>
7
8
9 </head>
10
11 <body>
12 <style>
13 * {
14 margin: 0;
15 padding: 0;
16 box-sizing: border-box;
17 }
18 body {
19 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
20 overflow: hidden;
21 background: #000;
22 }
23 canvas {
24 display: block;
25 width: 100%;
26 height: 100vh;
27 }
28 .content {
29 position: fixed;
30 top: 50%;
31 left: 50%;
32 transform: translate(-50%, -50%);
33 z-index: 10;
34 text-align: center;
35 color: white;
36 pointer-events: none;
37 }
38 h1 {
39 font-size: clamp(4.5rem, 13vw, 11rem);
40 font-weight: 900;
41 margin-bottom: 0.5rem;
42 letter-spacing: -0.06em;
43 background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 50%, #ffffff 100%);
44 -webkit-background-clip: text;
45 -webkit-text-fill-color: transparent;
46 background-clip: text;
47 filter: drop-shadow(0 0 40px rgba(255, 255, 255, 0.4)) drop-shadow(0 0 80px rgba(138, 43, 226, 0.3));
48 animation: glowPulse 3s ease-in-out infinite alternate;
49 }
50 @keyframes glowPulse {
51 from {
52 filter: drop-shadow(0 0 40px rgba(255, 255, 255, 0.4)) drop-shadow(0 0 80px rgba(138, 43, 226, 0.3));
53 }
54 to {
55 filter: drop-shadow(0 0 60px rgba(255, 255, 255, 0.6)) drop-shadow(0 0 120px rgba(0, 191, 255, 0.4));
56 }
57 }
58 .tagline {
59 font-size: clamp(0.9rem, 2vw, 1.2rem);
60 font-weight: 300;
61 color: rgba(255, 255, 255, 0.9);
62 letter-spacing: 0.3em;
63 text-transform: uppercase;
64 text-shadow: 0 0 30px rgba(255, 255, 255, 0.5), 0 0 60px rgba(138, 43, 226, 0.3);
65 }
66 .buttons {
67 display: flex;
68 justify-content: center;
69 gap: 24px;
70 margin-top: 40px;
71 pointer-events: auto;
72 }
73 .glass-button {
74 position: relative;
75 padding: 16px 40px;
76 font-size: 1rem;
77 font-weight: 600;
78 letter-spacing: 0.08em;
79 color: #fff;
80 background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%);
81 border: 1.5px solid transparent;
82 border-radius: 40px;
83 backdrop-filter: blur(30px);
84 box-shadow:
85 0 8px 32px rgba(0, 0, 0, 0.2),
86 inset 0 1px 0 rgba(255, 255, 255, 0.2),
87 inset 0 -1px 0 rgba(255, 255, 255, 0.05);
88 overflow: hidden;
89 cursor: pointer;
90 transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
91 pointer-events: auto;
92 }
93
94 .glass-button::before {
95 content: '';
96 position: absolute;
97 inset: 0;
98 border-radius: 40px;
99 padding: 1.5px;
100 background: linear-gradient(135deg,
101 rgba(255, 255, 255, 0.4) 0%,
102 rgba(138, 43, 226, 0.4) 25%,
103 rgba(0, 191, 255, 0.4) 50%,
104 rgba(255, 105, 180, 0.4) 75%,
105 rgba(255, 255, 255, 0.4) 100%);
106 background-size: 200% 200%;
107 -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
108 -webkit-mask-composite: xor;
109 mask-composite: exclude;
110 animation: borderFlow 3s linear infinite;
111 opacity: 0.6;
112 transition: opacity 0.5s ease;
113 }
114 @keyframes borderFlow {
115 0% { background-position: 0% 50%; }
116 100% { background-position: 200% 50%; }
117 }
118 .glass-button::after {
119 content: '';
120 position: absolute;
121 inset: 0;
122 border-radius: 40px;
123 background: radial-gradient(circle at var(--x, 50%) var(--y, 50%),
124 rgba(255, 255, 255, 0.2) 0%,
125 transparent 50%);
126 opacity: 0;
127 transition: opacity 0.5s ease;
128 }
129 .glass-button:hover {
130 background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.05) 100%);
131 box-shadow:
132 0 12px 48px rgba(138, 43, 226, 0.3),
133 0 0 80px rgba(0, 191, 255, 0.2),
134 inset 0 1px 0 rgba(255, 255, 255, 0.3),
135 inset 0 -1px 0 rgba(255, 255, 255, 0.1);
136 transform: translateY(-3px) scale(1.02);
137 }
138 .glass-button:hover::before {
139 opacity: 1;
140 animation-duration: 2s;
141 }
142 .glass-button:hover::after {
143 opacity: 1;
144 }
145 .glass-button:active {
146 transform: translateY(-1px) scale(0.98);
147 box-shadow:
148 0 6px 24px rgba(138, 43, 226, 0.2),
149 inset 0 1px 0 rgba(255, 255, 255, 0.3);
150 }
151 .glass-button .shimmer {
152 position: absolute;
153 top: -50%;
154 left: -50%;
155 width: 200%;
156 height: 200%;
157 background: linear-gradient(
158 90deg,
159 transparent 0%,
160 rgba(255, 255, 255, 0.1) 45%,
161 rgba(255, 255, 255, 0.3) 50%,
162 rgba(255, 255, 255, 0.1) 55%,
163 transparent 100%
164 );
165 transform: rotate(30deg);
166 animation: shimmer 3s infinite;
167 pointer-events: none;
168 }
169 @keyframes shimmer {
170 0% { transform: translateX(-100%) rotate(30deg); }
171 100% { transform: translateX(100%) rotate(30deg); }
172 }
173 .glass-button:hover .shimmer {
174 animation-duration: 1.5s;
175 }
176 .glass-button span {
177 position: relative;
178 z-index: 1;
179 }
180</style>
181<canvas id="canvas"></canvas>
182<div class="content">
183 <h1>PRISM</h1>
184 <p class="tagline">Spectrum of Light</p>
185 <div class="buttons">
186 <button class="glass-button">
187 <span class="shimmer"></span>
188 <span>Discover</span>
189 </button>
190 <button class="glass-button">
191 <span class="shimmer"></span>
192 <span>Join Now</span>
193 </button>
194 </div>
195</div>
196<script id="vertexShader" type="x-shader/x-vertex">
197 attribute vec2 position;
198
199 void main() {
200 gl_Position = vec4(position, 0.0, 1.0);
201 }
202</script>
203<script id="fragmentShader" type="x-shader/x-fragment">
204 precision highp float;
205
206 uniform float uTime;
207 uniform vec2 uResolution;
208 uniform vec2 uMouse;
209
210 #define PI 3.14159265359
211 #define TAU 6.28318530718
212 #define MAX_STEPS 80
213 #define MAX_DIST 50.0
214 #define SURF_DIST 0.001
215
216 float hash(float n) {
217 return fract(sin(n) * 43758.5453123);
218 }
219
220 mat2 rot(float a) {
221 float s = sin(a);
222 float c = cos(a);
223 return mat2(c, -s, s, c);
224 }
225
226 float sdSphere(vec3 p, float r) {
227 return length(p) - r;
228 }
229 float sdBox(vec3 p, vec3 b) {
230 vec3 q = abs(p) - b;
231 return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
232 }
233
234 float sdOctahedron(vec3 p, float s) {
235 p = abs(p);
236 float m = p.x + p.y + p.z - s;
237 vec3 q;
238 if(3.0 * p.x < m) q = p.xyz;
239 else if(3.0 * p.y < m) q = p.yzx;
240 else if(3.0 * p.z < m) q = p.zxy;
241 else return m * 0.57735027;
242
243 float k = clamp(0.5 * (q.z - q.y + s), 0.0, s);
244 return length(vec3(q.x, q.y - s + k, q.z - k));
245 }
246 float sdTriPrism(vec3 p, vec2 h) {
247 vec3 q = abs(p);
248 return max(q.z - h.y, max(q.x * 0.866025 + p.y * 0.5, -p.y) - h.x * 0.5);
249 }
250
251 float smin(float a, float b, float k) {
252 float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
253 return mix(b, a, h) - k * h * (1.0 - h);
254 }
255 float smax(float a, float b, float k) {
256 return -smin(-a, -b, k);
257 }
258
259 float map(vec3 p) {
260 vec3 op = p;
261
262 vec2 m = (uMouse - 0.5) * 2.5;
263 p.xy += m * 0.4;
264
265 p.xz *= rot(uTime * 0.12);
266 p.xy *= rot(uTime * 0.08);
267
268 float d = 100.0;
269
270 vec3 p1 = p;
271 p1.yz *= rot(uTime * 0.15);
272
273 float core_distort = sin(p1.x * 3.0 + uTime) * sin(p1.y * 3.0 + uTime) * sin(p1.z * 3.0 + uTime) * 0.1;
274 float core = sdOctahedron(p1, 1.6) + core_distort;
275
276 vec3 p2 = p1;
277 p2.xy *= rot(PI * 0.25 + uTime * 0.2);
278 float prism = sdTriPrism(p2, vec2(1.4, 2.0));
279 core = smax(core, -prism, 0.2);
280
281 d = core;
282
283 float k_blend = 0.2 + 0.15 * (0.5 + 0.5 * sin(uTime * 1.5));
284
285 for(int i = 0; i < 4; i++) {
286 float fi = float(i);
287 float angle = fi * TAU / 4.0 + uTime * 0.3;
288
289 float radius = 3.0 + 0.3 * sin(uTime * 0.4 + fi);
290
291 vec3 pos = vec3(
292 cos(angle) * radius,
293 sin(angle * 0.7) * 1.0,
294 sin(angle) * radius
295 );
296
297 vec3 po = p - pos;
298 po.xy *= rot(uTime * 0.5 + fi);
299
300 float sat_distort = sin(po.x * 5.0 + fi) * sin(po.y * 5.0 + fi) * sin(po.z * 5.0 + fi) * 0.05;
301 float satellite = sdOctahedron(po, 0.4) + sat_distort;
302
303 d = smin(d, satellite, k_blend);
304 }
305
306 return d;
307 }
308
309 vec3 getNormal(vec3 p) {
310 vec2 e = vec2(0.001, 0.0);
311 return normalize(vec3(
312 map(p + e.xyy) - map(p - e.xyy),
313 map(p + e.yxy) - map(p - e.yxy),
314 map(p + e.yyx) - map(p - e.yyx)
315 ));
316 }
317
318 float raymarch(vec3 ro, vec3 rd) {
319 float t = 0.0;
320 for(int i = 0; i < MAX_STEPS; i++) {
321 vec3 p = ro + rd * t;
322 float d = map(p);
323 if(abs(d) < SURF_DIST || t > MAX_DIST) break;
324 t += d * 0.7;
325 }
326 return t;
327 }
328
329 vec3 getBackground(vec3 rd) {
330 float stars = 0.0;
331 vec3 p = rd * 100.0;
332 float h = hash(dot(p, vec3(12.9898, 78.233, 54.53)));
333 if(h > 0.98) stars = pow(h - 0.98, 10.0) * 20.0;
334 vec3 nebula = vec3(0.0);
335 nebula += vec3(0.3, 0.15, 0.5) * pow(max(0.0, sin(rd.x * 2.0 + uTime * 0.1)), 3.0) * 0.2;
336 nebula += vec3(0.15, 0.3, 0.6) * pow(max(0.0, sin(rd.y * 2.5 + uTime * 0.05)), 3.0) * 0.2;
337
338 return stars + nebula;
339 }
340
341 void main() {
342 vec2 uv = (gl_FragCoord.xy - 0.5 * uResolution) / min(uResolution.x, uResolution.y);
343
344 vec2 m = (uMouse - 0.5) * 0.5;
345 vec3 ro = vec3(m.x * 2.0, m.y * 2.0, 5.5);
346 vec3 rd = normalize(vec3(uv, -1.0));
347
348 rd.xy *= rot(m.x * 0.2);
349 rd.yz *= rot(m.y * 0.2);
350
351 float t = raymarch(ro, rd);
352
353 vec3 color = vec3(0.0);
354
355 if(t < MAX_DIST) {
356 vec3 p = ro + rd * t;
357 vec3 normal = getNormal(p);
358
359 vec3 viewDir = normalize(ro - p);
360
361 float fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), 3.0);
362
363 float ior = 1.5;
364 vec3 refractDir = refract(rd, normal, 1.0 / ior);
365
366 if(length(refractDir) > 0.0) {
367 float t2 = raymarch(p - normal * 0.01, refractDir);
368
369 if(t2 < MAX_DIST) {
370 vec3 p2 = p - normal * 0.01 + refractDir * t2;
371 vec3 normal2 = getNormal(p2);
372
373 vec3 r = refract(refractDir, -normal2, ior - 0.2);
374 vec3 g = refract(refractDir, -normal2, ior);
375 vec3 b = refract(refractDir, -normal2, ior + 0.2);
376
377 vec3 bgR = getBackground(r) * vec3(1.4, 0.7, 0.7);
378 vec3 bgG = getBackground(g) * vec3(0.7, 1.4, 0.8);
379 vec3 bgB = getBackground(b) * vec3(0.7, 0.8, 1.4);
380
381 color = vec3(bgR.x, bgG.y, bgB.z);
382 color = pow(color, vec3(0.7)) * 5.0;
383
384 } else {
385 color = getBackground(refractDir) * 2.0;
386 }
387 }
388
389 vec3 lightDir = normalize(vec3(1.0, 1.0, -1.0));
390 vec3 halfDir = normalize(lightDir + viewDir);
391 float spec = pow(max(dot(normal, halfDir), 0.0), 150.0);
392 color += spec * vec3(1.0, 1.0, 1.0) * 3.5;
393
394 vec3 fresnelColor = vec3(
395 0.5 + 0.5 * sin(fresnel * TAU + uTime),
396 0.5 + 0.5 * sin(fresnel * TAU + uTime + TAU / 3.0),
397 0.5 + 0.5 * sin(fresnel * TAU + uTime + TAU * 2.0 / 3.0)
398 );
399 color += fresnel * fresnelColor * 1.2;
400
401 float edge = pow(1.0 - abs(dot(viewDir, normal)), 4.0);
402 color += edge * vec3(0.6, 0.7, 1.0) * 0.7;
403
404 float sss = pow(max(dot(-normal, lightDir), 0.0), 2.0);
405 color += sss * vec3(1.0, 0.6, 0.8) * 0.5;
406
407 } else {
408 color = getBackground(rd);
409 }
410
411 float vignette = 1.0 - length(uv) * 0.4;
412 vignette = smoothstep(0.3, 1.0, vignette);
413 color *= vignette;
414
415 color *= vec3(0.96, 0.99, 1.06);
416
417 color = pow(color, vec3(0.88));
418 color *= 1.12;
419
420 gl_FragColor = vec4(color, 1.0);
421 }
422</script>
423<script>
424 const canvas = document.getElementById('canvas');
425 const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
426 if (!gl) {
427 alert('WebGL not supported');
428 }
429
430 function resizeCanvas() {
431 canvas.width = window.innerWidth;
432 canvas.height = window.innerHeight;
433 gl.viewport(0, 0, canvas.width, canvas.height);
434 }
435 resizeCanvas();
436 window.addEventListener('resize', resizeCanvas);
437
438 function createShader(gl, type, source) {
439 const shader = gl.createShader(type);
440 gl.shaderSource(shader, source);
441 gl.compileShader(shader);
442
443 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
444 console.error('Shader compile error:', gl.getShaderInfoLog(shader));
445 gl.deleteShader(shader);
446 return null;
447 }
448 return shader;
449 }
450
451 function createProgram(gl, vertexShader, fragmentShader) {
452 const program = gl.createProgram();
453 gl.attachShader(program, vertexShader);
454 gl.attachShader(program, fragmentShader);
455 gl.linkProgram(program);
456
457 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
458 console.error('Program link error:', gl.getProgramInfoLog(program));
459 gl.deleteProgram(program);
460 return null;
461 }
462 return program;
463 }
464
465 const vertexShaderSource = document.getElementById('vertexShader').textContent;
466 const fragmentShaderSource = document.getElementById('fragmentShader').textContent;
467
468 const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
469 const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
470 const program = createProgram(gl, vertexShader, fragmentShader);
471
472 const uTime = gl.getUniformLocation(program, 'uTime');
473 const uResolution = gl.getUniformLocation(program, 'uResolution');
474 const uMouse = gl.getUniformLocation(program, 'uMouse');
475
476 const positions = new Float32Array([
477 -1, -1,
478 1, -1,
479 -1, 1,
480 1, 1,
481 ]);
482 const positionBuffer = gl.createBuffer();
483 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
484 gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
485
486 const mouse = { x: 0.5, y: 0.5, targetX: 0.5, targetY: 0.5 };
487 canvas.addEventListener('mousemove', (e) => {
488 mouse.targetX = e.clientX / canvas.width;
489 mouse.targetY = 1.0 - e.clientY / canvas.height;
490 });
491 document.querySelectorAll('.glass-button').forEach(button => {
492 button.addEventListener('mousemove', (e) => {
493 const rect = button.getBoundingClientRect();
494 const x = ((e.clientX - rect.left) / rect.width) * 100;
495 const y = ((e.clientY - rect.top) / rect.height) * 100;
496 button.style.setProperty('--x', x + '%');
497 button.style.setProperty('--y', y + '%');
498 });
499 });
500
501 let startTime = Date.now();
502 function render() {
503 const currentTime = (Date.now() - startTime) * 0.001;
504
505 mouse.x += (mouse.targetX - mouse.x) * 0.05;
506 mouse.y += (mouse.targetY - mouse.y) * 0.05;
507
508 gl.clearColor(0.0, 0.0, 0.0, 1.0);
509 gl.clear(gl.COLOR_BUFFER_BIT);
510 gl.useProgram(program);
511
512 gl.uniform1f(uTime, currentTime);
513 gl.uniform2f(uResolution, canvas.width, canvas.height);
514 gl.uniform2f(uMouse, mouse.x, mouse.y);
515
516 const positionLocation = gl.getAttribLocation(program, 'position');
517 gl.enableVertexAttribArray(positionLocation);
518 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
519 gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
520
521 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
522
523 requestAnimationFrame(render);
524 }
525 render();
526</script>
527
528 </body>
529
530</html>
\
HTML Explanation:
The HTML layout is intentionally minimal:
A full-screen
A fixed .content layer overlays the canvas
UI elements remain independent of the GPU-rendered scene
Buttons are fully interactive despite the canvas underneath
🎨 CSS (Glassmorphism + Typography)
The CSS delivers:
Glassmorphism via backdrop blur
Animated gradient borders
Glow + shimmer effects
Responsive typography with cinematic feel
GPU-friendly animations
