Blog · Engineering
Drawing K2 live in WebGPU
How ktwo.dev draws a night climb of K2 with WGSL shaders: a scroll-driven camera, a contour map, a footer engraving, and fallbacks for browsers without WebGPU.
The background of ktwo.dev isn’t a video or an image. It is K2, the world’s second-highest mountain, drawn live on the GPU with WebGPU, and the page reads as a night climb: scroll down and the camera climbs the valley, passes through a bank of cloud and reaches the summit at dawn. This post explains how it is built, and how the page stays fast and readable in browsers that can’t draw it.
The stack
The scenes are built on Shaders, an open-source (MIT) library of composable WebGPU effects. Its stock components supply the film grain, the vignette and the falling snow. Everything specific to K2 is four custom components written in WGSL, WebGPU’s shading language:
- KTwoMountain, the backdrop: night sky and stars, the K2 pyramid with its Abruzzi shoulder and moonlit arêtes, a summit plume, valley cloud, ridges in front, the cloud bank the camera climbs through, and dawn on the summit.
- KTwoSurvey, the contour map on the model page, drawn from the summit down as it scrolls in. Pointing at it lights the one contour at the pointer’s elevation.
- KTwoPlate and KTwoEtch, the line engraving of K2’s profile in the footer, cut from the left as the footer comes into view.
The library is self-hosted: it is bundled once from npm into a single committed file, so the site has no build step at deploy time. Its telemetry is switched off, and the pages make no third-party requests.
Scroll as a climb
Each component exposes numeric props: camera position, zoom, how far the moon has risen, how much mist, how much dawn. The page computes targets for them from the scroll position:
- The opening frame centers K2 under the logo. Over the first 0.6 of a screen of scroll, the camera eases into the model frame while the headline is drawn on.
- Behind the statement, a cloud bank thickens as the section enters and clears as you leave it.
- At the access panel, dawn: a warm horizon, alpenglow on the summit, and the camera pans right to keep the peak clear of the text.
- Throughout, an altimeter at the edge of the screen reads the page as a climb from 5,000 m, roughly base camp, to the summit at 8,611 m.
The pointer adds a little parallax, and on load the moon rises on the face.
Easing that doesn’t depend on frame rate
Scroll and pointer events arrive in bursts, so the props don’t jump to their targets: a small follower eases each one toward its target, then idles. The easing is time-based, so a slow GPU at 30 frames a second settles as quickly as a fast one at 120:
// share of the remaining distance covered per 60 Hz frame, scaled to the real frame time
const frames = Math.min((now - last) / (1000 / 60), 30);
cur[key] += (tgt[key] - cur[key]) * (1 - Math.pow(1 - rate, frames));
A fixed step per frame would make the camera twice as slow at half the frame rate. Raising the per-frame factor to the power of the elapsed frames makes the motion the same in wall-clock time.
Not drawing what nobody can see
A live shader costs battery, so the page does as little as it can:
- Covered means paused. Most pages lay an opaque “survey sheet” over the landscape. While the sheet fills the screen, the backdrop stops drawing.
- Settled means quiet. Once the eased props reach their targets, the follower stops sending updates.
- The engraving redraws only when its cut moves on.
- Resolution is capped. The render scale follows the device pixel ratio, but no higher than 1.5 on touch devices up to 1366 px and 2 elsewhere.
Progressive enhancement: no WebGPU, no problem
WebGPU isn’t everywhere yet, so the shaders are an enhancement, never a requirement:
- The engine loads only where WebGPU exists. Browsers without
navigator.gpunever download it. Where it exists, the engine’s own support test looks for a usable GPU before any K2 shader is loaded, and if there isn’t one the page keeps its static fallbacks. - Static fallbacks are already in the page. An inline SVG of the mountain sits behind every page, the model page has a still frame of the survey map, and the footer simply leaves the engraving out.
- GPU failures hand back to the fallbacks. If the device is lost for good, the page switches to the static versions.
- Reduced motion is respected. With
prefers-reduced-motion, the scenes render still frames, with no snow, no parallax and no scroll animation. - Text never depends on it. Every word on the page is plain HTML, readable with JavaScript off.
Why bother?
Because a site for a lab called KTwo should feel like K2, and because doing it carefully costs less than people expect: one small library, four components, a few hundred lines of scroll logic, and fallbacks that make all of it optional. The page still loads its text first and stays readable without any of it.
Frequently asked questions
What is WebGPU?
A modern web API for the GPU and the successor to WebGL. It gives web pages access to graphics and general-purpose compute on the graphics card, with shaders written in WGSL.
What happens in browsers without WebGPU?
Browsers without WebGPU never download the shader engine, and one with WebGPU but no usable GPU fails the engine’s support test and draws nothing. Either way the page shows a static SVG of the mountain and still images instead, and all of its content works as normal.
Does the animation respect reduced-motion settings?
Yes. With prefers-reduced-motion set, the scenes show still frames, with no snow, parallax or scroll animation.
Which library draws the scene?
Shaders, an open-source (MIT) WebGPU library, with four custom WGSL components written for ktwo.dev.
Keep reading
-
Engineering
Per user, per model, per UTC day: metering a free AI chat
How LE PEAK enforces daily AI quotas with one atomic SQL statement, reserves room for streamed replies, and rate-limits guests without storing IP addresses. -
LE PEAK
LE PEAK: a free AI chat with 23 models in one thread
Chat with DeepSeek, Gemma, gpt-oss, Nemotron and more for free, twelve models without an account. How LE PEAK works, its daily quotas, attachments and what it keeps.