#!/usr/bin/gawk -f # reinhard@finalmedia.de # Thu May 14 11:07:10 AM CEST 2026 # Public Domain BEGIN { # Verwende kleinere Werte für Echtzeit-Demos im Terminal! W = 320; H = 240; # Kamera-Setup camX = 0; camY = 0; camZ = -4; # Kugel-Setup (Zentrum X, Y, Z und Radius) spX = 0; spY = -0.2; spZ = 1.2; spR = 0.7; spR2 = spR * spR; # Lichtquelle (Punktlicht) lX = 1.5; lY = 2.0; lZ = -1.0; # Wände des Raums (Eckige Box-Grenzen) leftWallX = -1.8; rightWallX = 1.8; floorY = -1.0; ceilingY = 1.0; backWallZ = 3.0; frontWallZ = -5.0; # Strahlverfolgung für jedes Pixel for (y = 0; y < H; y++) { for (x = 0; x < W; x++) { # Normalisierte Bildschirmkoordinaten (-1 bis 1) mit Aspect Ratio # Y wird invertiert, da im Videospeicher 0 oben ist fx = (x / W * 2.0 - 1.0) * (W / H); fy = (1.0 - y / H * 2.0); # Strahl-Richtung (Normalisiert) dx = fx; dy = fy; dz = 1.5; len = sqrt(dx*dx + dy*dy + dz*dz); dx /= len; dy /= len; dz /= len; # Primärstrahl schießen render_ray(camX, camY, camZ, dx, dy, dz, x, y); } } } # Hilfsfunktion zum Berechnen der Schnittpunkte und Farben function render_ray(ox, oy, oz, dx, dy, dz, screenX, screenY, a, b, c, disc, t_sp, hit, t, hx, hy, hz, nx, ny, nz, r, g, b_val, dot, t_wall, wall, tw, rx, ry, rz, r_len, r_hit, t_ref, r_hx, r_hy, r_hz, r_nx, r_ny, r_nz, r_r, r_g, r_b, r_dot) { t = 1e9; # Unendlich weit weg hit = "none"; # --- 1. SCHNITTPUNKT MIT DER KUGEL BERECHNEN --- # Quadratische Gleichung für Strahl-Kugel-Schnittpunkt # (O-C) Vektor ocX = ox - spX; ocY = oy - spY; ocZ = oz - spZ; b = 2.0 * (dx * ocX + dy * ocY + dz * ocZ); c = (ocX*ocX + ocY*ocY + ocZ*ocZ) - spR2; disc = b*b - 4.0 * c; if (disc >= 0) { t_sp = (-b - sqrt(disc)) / 2.0; if (t_sp > 0.001 && t_sp < t) { t = t_sp; hit = "sphere"; } } # --- 2. SCHNITTPUNKT MIT DEN WÄNDEN (ECKIGER RAUM) BERECHNEN --- # Linke Wand if (dx < 0) { tw = (leftWallX - ox) / dx; if (tw > 0.001 && tw < t) { t = tw; hit = "left"; } } # Rechte Wand if (dx > 0) { tw = (rightWallX - ox) / dx; if (tw > 0.001 && tw < t) { t = tw; hit = "right"; } } # Boden if (dy < 0) { tw = (floorY - oy) / dy; if (tw > 0.001 && tw < t) { t = tw; hit = "floor"; } } # Decke if (dy > 0) { tw = (ceilingY - oy) / dy; if (tw > 0.001 && tw < t) { t = tw; hit = "ceiling"; } } # Rückwand if (dz > 0) { tw = (backWallZ - oz) / dz; if (tw > 0.001 && tw < t) { t = tw; hit = "back"; } } # --- 3. SHADING (FARBGEBUNG) --- if (hit == "none") { # Hintergrund / Schwarz printf "p %d %d 0 0 0\n", screenX, screenY; return; } # Exakter Treffpunkt im 3D-Raum hx = ox + dx * t; hy = oy + dy * t; hz = oz + dz * t; # Basis-Materialfarben zuweisen if (hit == "sphere") { # Kugel ist hochgradig spiegelnd (Reflexion) # Berechne Normalenvektor der Kugel am Treffpunkt nx = (hx - spX) / spR; ny = (hy - spY) / spR; nz = (hz - spZ) / spR; # Reflexionsstrahl berechnen: R = D - 2 * (D . N) * N dot = dx*nx + dy*ny + dz*nz; rx = dx - 2.0 * dot * nx; ry = dy - 2.0 * dot * ny; rz = dz - 2.0 * dot * nz; r_len = sqrt(rx*rx + ry*ry + rz*rz); rx /= r_len; ry /= r_len; rz /= r_len; # Sekundärstrahl in den Raum schießen (1 Reflexionsstufe) # Verschiebe Startpunkt minimal in Normalenrichtung, um Selbst-Schnittpunkte zu vermeiden r_hit = trace_scene(hx + nx*0.001, hy + ny*0.001, hz + nz*0.001, rx, ry, rz); # Extrahiere Farbe der Spiegelung split(r_hit, rgb, " "); r = rgb[1]; g = rgb[2]; b_val = rgb[3]; # Ein wenig Glanzlicht hinzufügen r = int(r * 0.9 + 25); g = int(g * 0.9 + 25); b_val = int(b_val * 0.9 + 25); } else { # Wände bekommen feste Farben für den Cornell-Box-Look if (hit == "left") { r = 200; g = 30; b_val = 30; nx = 1; ny = 0; nz = 0; } # Rot if (hit == "right") { r = 30; g = 180; b_val = 30; nx = -1; ny = 0; nz = 0; } # Grün if (hit == "floor") { r = 180; g = 180; b_val = 180; nx = 0; ny = 1; nz = 0; } # Grau if (hit == "ceiling") { r = 140; g = 140; b_val = 140; nx = 0; ny = -1; nz = 0; } # Weißlich if (hit == "back") { r = 160; g = 160; b_val = 160; nx = 0; ny = 0; nz = -1; } # Grau # Diffuses Lambert-Shading basierend auf der Lichtquelle lx = lX - hx; ly = lY - hy; lz = lZ - hz; l_len = sqrt(lx*lx + ly*ly + lz*lz); lx /= l_len; ly /= l_len; lz /= l_len; dot = nx*lx + ny*ly + nz*lz; if (dot < 0) dot = 0; # Schattenwurf prüfen (Strahl vom Treffpunkt zum Licht) if (shadow_ray(hx + nx*0.001, hy + ny*0.001, hz + nz*0.001, lx, ly, lz, l_len)) { dot *= 0.2; # Im Schatten ist es deutlich dunkler } # Finale Pixelfarbe berechnen r = int(r * (dot * 0.8 + 0.2)); g = int(g * (dot * 0.8 + 0.2)); b_val = int(b_val * (dot * 0.8 + 0.2)); } # Begrenzung auf gültigen RGB-Raum (0-255) if (r > 255) r = 255; if (g > 255) g = 255; if (b_val > 255) b_val = 255; if (r < 0) r = 0; if (g < 0) g = 0; if (b_val < 0) b_val = 0; # Befehl an das C-Programm ausgeben printf "p %d %d %d %d %d\n", screenX, screenY, r, g, b_val; } # Hilfsfunktion für den Reflexionsstrahl (gibt "R G B" als String zurück) function trace_scene(ox, oy, oz, dx, dy, dz, t, hit, tw, ocX, ocY, ocZ, b, c, disc, t_sp, hx, hy, hz, nx, ny, nz, r, g, b_val, lx, ly, lz, l_len, dot) { t = 1e9; hit = "none"; # Wände prüfen if (dx < 0) { tw = (-1.8 - ox) / dx; if (tw > 0.001 && tw < t) { t = tw; hit = "left"; } } if (dx > 0) { tw = (1.8 - ox) / dx; if (tw > 0.001 && tw < t) { t = tw; hit = "right"; } } if (dy < 0) { tw = (-1.0 - oy) / dy; if (tw > 0.001 && tw < t) { t = tw; hit = "floor"; } } if (dy > 0) { tw = (1.0 - oy) / dy; if (tw > 0.001 && tw < t) { t = tw; hit = "ceiling"; } } if (dz > 0) { tw = (3.0 - oz) / dz; if (tw > 0.001 && tw < t) { t = tw; hit = "back"; } } if (hit == "none") return "0 0 0"; hx = ox + dx * t; hy = oy + dy * t; hz = oz + dz * t; if (hit == "left") { r = 200; g = 30; b_val = 30; nx = 1; ny = 0; nz = 0; } if (hit == "right") { r = 30; g = 180; b_val = 30; nx = -1; ny = 0; nz = 0; } if (hit == "floor") { r = 180; g = 180; b_val = 180; nx = 0; ny = 1; nz = 0; } if (hit == "ceiling") { r = 140; g = 140; b_val = 140; nx = 0; ny = -1; nz = 0; } if (hit == "back") { r = 160; g = 160; b_val = 160; nx = 0; ny = 0; nz = -1; } lx = 1.5 - hx; ly = 2.0 - hy; lz = -1.0 - hz; l_len = sqrt(lx*lx + ly*ly + lz*lz); lx /= l_len; ly /= l_len; lz /= l_len; dot = nx*lx + ny*ly + nz*lz; if (dot < 0) dot = 0; # Schatten für Reflexionspunkt prüfen if (shadow_ray(hx + nx*0.001, hy + ny*0.001, hz + nz*0.001, lx, ly, lz, l_len)) dot *= 0.2; return int(r * (dot * 0.8 + 0.2)) " " int(g * (dot * 0.8 + 0.2)) " " int(b_val * (dot * 0.8 + 0.2)); } # Gibt 1 zurück, falls ein Objekt zwischen Treffpunkt und Lichtquelle liegt (Schatten) function shadow_ray(ox, oy, oz, dx, dy, dz, max_t, ocX, ocY, ocZ, b, c, disc, t_sp) { ocX = ox - spX; ocY = oy - spY; ocZ = oz - spZ; b = 2.0 * (dx * ocX + dy * ocY + dz * ocZ); c = (ocX*ocX + ocY*ocY + ocZ*ocZ) - spR * spR; disc = b*b - 4.0 * c; if (disc >= 0) { t_sp = (-b - sqrt(disc)) / 2.0; if (t_sp > 0.001 && t_sp < max_t) { return 1; # Kugel blockiert das Licht } } return 0; }