Bumped Surface Lighting 2.1 — © 1997-1999 Christian Cohnen
The LightMap applet takes a color texture and a grayscale bump map to calculate a real-time lit surface using 2D bump mapping. A radial light source follows the mouse cursor across the canvas; when the mouse leaves, the light orbits automatically along a smooth elliptical path. The bump map is converted into per-pixel x/y gradient shifts via finite differences, and for each pixel the light intensity is looked up from a pre-computed radial falloff table. The final color is the texture color multiplied by that intensity — all done per frame using integer shifts for speed, producing true-color output.
Bump mapping with dynamic light sources was a signature effect of the Atari ST, Amiga and early PC demoscene in the mid-to-late 1990s.
On the 68000 CPU the per-pixel gradient lookup and light multiply were carefully arranged to avoid expensive MUL instructions.
This Java applet follows the same philosophy: the >> 8 shift replaces a division by 256 in the per-pixel lighting, and the radial falloff is read from a pre-computed lookup table rather than calculated on the fly.
Originally a Java applet (1997). The JavaScript/Canvas port uses the same bump mapping algorithm
but renders into an HTML5 <canvas> via ImageData.
Texture and bump map are loaded as standard <img> elements.
+
=>
| Name | Type | Description | Default |
|---|---|---|---|
pic | URL | Color texture image (GIF or JPEG) | – |
bump | URL | Grayscale bump/height map image | – |
link | URL | Link target when applet is clicked | – |
movement | flag | If present, light auto-orbits | – |
lightSize | int | Diameter of the light source (4–256) | 128 |
ambient | int | Ambient light level (0–128) | 0 |
| Name | Type | Description | Default |
|---|---|---|---|
lightSize | int | Diameter of light source (4–512) | 128 |
ambient | int | Ambient light level (0–128) | 0 |
On startup the effect pre-computes a radial light falloff table:
for each (x, y) in lightSize x lightSize:
r = distance² from center
if r < radius²:
field = (1024 - 1024 * r / radius²)² >> 12
else:
field = 0
The bump map is converted to gradient vectors using finite differences:
xShift[pos] = (height[pos+1] - height[pos-1]) / 2
yShift[pos] = (height[pos+w] - height[pos-w]) / 2
Each frame, for every pixel the light position is offset by the bump gradient, then the pre-computed falloff value is used to shade the texture color:
intensity = lightBall[lightPos - bumpShift]
r = (intensity * textureR[pos]) >> 8
g = (intensity * textureG[pos]) >> 8
b = (intensity * textureB[pos]) >> 8
<applet archive="LightMap.jar"
code="LightMap.class" width="320" height="150">
<param name="pic" value="logo.jpg">
<param name="bump" value="bump.jpg">
<param name="movement">
<param name="link" value="https://www.chriscohnen.de">
</applet>
<canvas id="game" width="640" height="300"></canvas>
<img id="texImg" src="logo.jpg" style="display:none">
<img id="bumpImg" src="bump.jpg" style="display:none">
<script src="lightmap.min.js"></script>