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 applet area; 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 — bump shifts were stored in pre-computed tables and the RGB scaling used shifts and adds instead of full multiplications.
On the x86 side, 386/486 demos exploited 32-bit registers to pack two color channels and process them in a single operation, while Pentium coders paired instructions to keep both U and V pipelines busy.
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.
+
=>
| Name | Type | Description | Default | Required |
|---|---|---|---|---|
pic | URL | Color texture image (GIF or JPEG) | – | yes |
bump | URL | Grayscale bump/height map image | – | yes |
link | URL | Link target when applet is clicked | – | no |
targetWindow | String | Target window for link (_blank, _parent, etc.) | – | no |
movement | flag | If present, light auto-orbits (no value needed) | – | no |
lightSize | int | Diameter of the light source (4–256) | 128 | no |
ambient | int | Ambient light level (0–128, 0 = no ambient) | 8 | no |
On startup the applet pre-computes a radial light falloff table:
for each (x, y) in lightSize × lightSize:
r = distance² from center
if r < radius²:
field = (1024 - 1024 * r / radius²)² >> 12
else:
field = 0
This gives a smooth inverse-square-like falloff clamped to 0–255.
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
The >> 8 shift replaces a division by 256, keeping the per-pixel math entirely in integer arithmetic.
<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>