TOM · 1ROZDZIAŁY · 2–5TRYB · krok po krokuWERYFIKACJA · numeryczna
Pomocnik obliczeniowy / Tom 1

Wektory, kamera, płaszczyzny i materiały — obliczenia krok po kroku

Osobny zeszyt do książki o ray tracingu. Każda sekcja daje wzór, jeden w pełni rozpisany przykład liczbowy — sprawdzony numerycznie, nie „na oko” — i zadania z odpowiedzią do samodzielnej weryfikacji. Miej pod ręką kartkę, kalkulator i te same wzory z Rozdziałów 2–5.

Jak z tego korzystać
Spis treści
  1. Powtórka: operacje wektorowe
  2. Rozdział 2 — Promień i przecięcie ze sferą
  3. Rozdział 3 — Baza kamery i promień z piksela
  4. Rozdział 4 — Płaszczyzna i algorytm Möllera–Trumbore
  5. Rozdział 5 — Lambert i Blinn-Phong
POWTÓRKA

Operacje wektorowe — odświeżenie

Odejmowanie punktów, długość, normalizacja

V = A − B
|V| = √(Vx² + Vy² + Vz²)
V̂ = V / |V|
Przykład
Dane: A = (3, 1, 4), B = (1, 1, 1)
Krok 1 — odejmowanie: V = A − B = (3−1, 1−1, 4−1) = (2, 0, 3)
Krok 2 — długość: |V| = √(2² + 0² + 3²) = √(4+0+9) = √13 ≈ 3,606
Krok 3 — normalizacja: V̂ = (2/3,606; 0/3,606; 3/3,606) ≈ (0,555; 0; 0,832)
Wynik: V̂ ≈ (0,555; 0; 0,832)
Sprawdzenie: |V̂| powinno wyjść 1 — 0,555²+0²+0,832² = 0,308+0,692 = 1,000 ✓

Iloczyn skalarny i kąt między wektorami

A·B = Ax·Bx + Ay·By + Az·Bz = |A||B|·cosθ
θ = arccos( A·B / (|A||B|) )
Przykład
Dane: A = (2, −1, 3), B = (0, 4, −2)
Krok 1 — iloczyn skalarny: A·B = 2·0 + (−1)·4 + 3·(−2) = 0 − 4 − 6 = −10
Krok 2 — długości: |A| = √(4+1+9) = √14 ≈ 3,742, |B| = √(0+16+4) = √20 ≈ 4,472
Krok 3 — cosinus: cosθ = −10 / (3,742·4,472) = −10/16,74 ≈ −0,598
Krok 4 — kąt: θ = arccos(−0,598) ≈ 126,7°
Wynik: A·B = −10, θ ≈ 126,7° (kąt rozwarty — wektory w większości „przeciwne”)
Sprawdzenie znaku: iloczyn skalarny ujemny ⇒ kąt > 90°, zgodnie z wynikiem.

Iloczyn wektorowy

A × B = (Ay·Bz − Az·By, Az·Bx − Ax·Bz, Ax·By − Ay·Bx)

Zapamiętaj schemat: każda współrzędna wyniku „pomija” tę samą współrzędną wejściową i mnoży na krzyż pozostałe dwie, w cyklicznej kolejności x→y→z→x.

ROZDZIAŁ 2

Promień i przecięcie ze sferą

Wzór

P(t) = O + t·D
a = D·D, b = 2·D·L, c = L·L − r², gdzie L = O − C
Δ = b² − 4ac

Przykład: promień trafia w sferę

Dane wejściowe
Kamera: O = (0, 0, 0). Kierunek surowy: D_raw = (1, 0, 2). Sfera: środek C = (2, 0, 4), radius r = 1,5.
Krok po kroku
1) Normalizacja D: |D_raw| = √(1+0+4) = √5 ≈ 2,236 → D = (0,447; 0; 0,894)
2) L = O − C = (0−2, 0−0, 0−4) = (−2, 0, −4)
3) a = D·D = 0,447² + 0² + 0,894² = 0,2 + 0,8 = 1,0 (jak zawsze dla znormalizowanego D)
4) b = 2·(D·L) = 2·[(0,447·−2)+(0·0)+(0,894·−4)] = 2·(−0,894−3,578) = 2·(−4,472) = −8,944
5) c = L·L − r² = [(−2)²+0²+(−4)²] − 1,5² = 20 − 2,25 = 17,75
6) Δ = b² − 4ac = (−8,944)² − 4·1·17,75 = 80,0 − 71,0 = 9,0
7) √Δ = 3,0 (Δ dodatnie ⇒ dwa przecięcia)
8) t₀ = (−b − √Δ)/(2a) = (8,944 − 3)/2 = 2,972
9) t₁ = (−b + √Δ)/(2a) = (8,944 + 3)/2 = 5,972
10) Punkt trafienia: P(t₀) = O + 2,972·D = (2,972·0,447; 0; 2,972·0,894) = (1,329; 0; 2,658)
Wynik: t₀ ≈ 2,972 (bliższe trafienie), punkt (1,329; 0; 2,658)
Sprawdzenie: odległość P(t₀) od C powinna równać się r=1,5. √[(1,329−2)²+0²+(2,658−4)²] = √[0,450+1,801] = √2,251 ≈ 1,500 ✓

Zadania

Zadanie 1 — trafienie na wprost
O = (0,0,0), D = (0,0,1) (już znormalizowany), sfera C = (0,0,5), r = 2. Policz a, b, c, Δ, t₀ i t₁.
L = (0,0,−5). a = 1. b = 2·(D·L) = 2·(−5) = −10. c = L·L−r² = 25−4 = 21.
Δ = 100 − 84 = 16, √Δ = 4.
t₀ = (10−4)/2 = 3,0, t₁ = (10+4)/2 = 7,0.
Zadanie 2 — promień mija sferę
O = (0,0,0), D = (1,0,0), sfera C = (5,3,0), r = 1. Sprawdź, czy promień trafia w sferę.
L = (−5,−3,0). a=1. b = 2·(D·L) = 2·(−5) = −10. c = L·L−r² = 34−1 = 33.
Δ = 100 − 132 = −32 — ujemne ⇒ promień mija sferę, brak rzeczywistych t.
Zadanie 3 — przypadek tangentny
O = (0,0,0), D = (1,0,0), sfera C = (5,1,0), r = 1. Sprawdź Δ i zinterpretuj wynik.
L = (−5,−1,0). a=1. b = 2·(−5) = −10. c = 26−1 = 25.
Δ = 100 − 100 = 0 — promień jest tangentny, dotyka sfery w jednym punkcie, t = −b/2a = 5,0.
ROZDZIAŁ 3

Baza kamery i promień z piksela

Wzór

forward = normalize(target − eye)
right = normalize(forward × up_world)
up = right × forward

Przykład: baza kamery

Dane wejściowe
eye = (0,0,0), target = (2,1,−3), up_world = (0,1,0)
Krok po kroku
1) forward_raw = target − eye = (2, 1, −3)
2) |forward_raw| = √(4+1+9) = √14 ≈ 3,742 → forward = (0,535; 0,267; −0,802)
3) right_raw = forward × up_world:
x: fy·uz − fz·uy = 0,267·0 − (−0,802)·1 = 0,802
y: fz·ux − fx·uz = (−0,802)·0 − 0,535·0 = 0
z: fx·uy − fy·ux = 0,535·1 − 0,267·0 = 0,535
→ right_raw = (0,802; 0; 0,535)
4) |right_raw| = √(0,643+0+0,286) = √0,929 ≈ 0,964 → right = (0,832; 0; 0,555)
5) up = right × forward:
x: ry·fz − rz·fy = 0·(−0,802) − 0,555·0,267 = −0,148
y: rz·fx − rx·fz = 0,555·0,535 − 0,832·(−0,802) = 0,297+0,667 = 0,964
z: rx·fy − ry·fx = 0,832·0,267 − 0·0,535 = 0,222
→ up = (−0,148; 0,964; 0,222)
Wynik: forward=(0,535; 0,267; −0,802), right=(0,832; 0; 0,555), up=(−0,148; 0,964; 0,222)
Sprawdzenie: forward·right i forward·up powinny wyjść ≈0 (prostopadłość). |up| powinno wyjść ≈1.

Przykład: viewport i promień dla konkretnego piksela

viewport_height = 2·tan(FOV/2)
viewport_width = aspect · viewport_height
s = (2(i+0,5)/W − 1)·(vw/2), t = (1 − 2(j+0,5)/H)·(vh/2)
D_final = normalize(eye + forward + s·right + t·up − eye)
Dane wejściowe
FOV = 60°, W×H = 800×450, piksel (i,j) = (600, 100). Baza kamery z poprzedniego przykładu.
Krok po kroku
1) aspect = 800/450 ≈ 1,778
2) viewport_height = 2·tan(30°) = 2·0,577 ≈ 1,155
3) viewport_width = 1,778·1,155 ≈ 2,053
4) s = (2·(600,5)/800 − 1)·(2,053/2) = (1,501−1)·1,026 ≈ 0,514
5) t = (1 − 2·(100,5)/450)·(1,155/2) = (1−0,447)·0,577 ≈ 0,319
6) P = eye + forward + s·right + t·up
= (0,535; 0,267; −0,802) + 0,514·(0,832; 0; 0,555) + 0,319·(−0,148; 0,964; 0,222)
= (0,535+0,428−0,047; 0,267+0+0,308; −0,802+0,285+0,071)
≈ (0,915; 0,575; −0,445)
7) D_final = normalize(P − eye) = normalize(0,915; 0,575; −0,445), |.| ≈ 1,169 → (0,783; 0,492; −0,381)
Wynik: promień dla piksela (600,100): O=(0,0,0), D≈(0,783; 0,492; −0,381)
Sprawdzenie: |D_final| powinno wyjść 1 — 0,783²+0,492²+0,381² = 0,613+0,242+0,145 = 1,000 ✓

Zadanie

Zadanie 4 — inna baza kamery
eye = (0,0,0), target = (1,−1,−2), up_world = (0,1,0). Policz forward, right, up.
forward_raw=(1,−1,−2), |.|=√6≈2,449 → forward ≈ (0,408; −0,408; −0,816)
right_raw = forward×up_world ≈ (0,894; 0; 0,447), już bliskie unormowaniu → right ≈ (0,894; 0; 0,447)
up = right×forward ≈ (0,183; 0,913; −0,365)
ROZDZIAŁ 4

Płaszczyzna i algorytm Möllera–Trumbore

Wzór — płaszczyzna

t = N·(Q−O) / (N·D)
Przykład
Dane: N_raw=(1,2,2) → N=(1/3; 2/3; 2/3) [już policzone: |N_raw|=3]. Q=(1,0,0). O=(0,0,5), D=(0,0,−1).
1) Q−O = (1−0, 0−0, 0−5) = (1, 0, −5)
2) N·(Q−O) = (1/3·1)+(2/3·0)+(2/3·−5) = 0,333 − 3,333 = −3,0
3) N·D = (1/3·0)+(2/3·0)+(2/3·−1) = −0,667
4) t = −3,0 / −0,667 = 4,5
5) P = O + t·D = (0,0,5) + 4,5·(0,0,−1) = (0, 0, 0,5)
Wynik: t = 4,5, punkt trafienia (0, 0, 0,5)
Sprawdzenie: N·(P−Q) powinno wyjść 0. P−Q=(−1,0,0,5). N·(P−Q) = −0,333+0,333 = 0 ✓

Wzór — Möller–Trumbore

edge1=B−A, edge2=C−A, h=D×edge2, det=edge1·h
s=O−A, u=(s·h)/det, q=s×edge1, v=(D·q)/det, t=(edge2·q)/det
Przykład
Trójkąt: A=(0,0,0), B=(4,0,0), C=(0,3,0). Promień: O=(1,1,5), D=(0,0,−1).
1) edge1 = B−A = (4,0,0). edge2 = C−A = (0,3,0).
2) h = D × edge2:
x: Dy·e2z − Dz·e2y = 0·0 − (−1)·3 = 3
y: Dz·e2x − Dx·e2z = (−1)·0 − 0·0 = 0
z: Dx·e2y − Dy·e2x = 0·3 − 0·0 = 0
→ h = (3, 0, 0)
3) det = edge1·h = 4·3 + 0·0 + 0·0 = 12 (≠0, promień nie jest równoległy do płaszczyzny trójkąta)
4) s = O−A = (1, 1, 5)
5) u = (s·h)/det = (1·3+1·0+5·0)/12 = 3/12 = 0,25 — spełnia 0≤u≤1 ✓
6) q = s × edge1:
x: sy·e1z − sz·e1y = 1·0 − 5·0 = 0
y: sz·e1x − sx·e1z = 5·4 − 1·0 = 20
z: sx·e1y − sy·e1x = 1·0 − 1·4 = −4
→ q = (0, 20, −4)
7) v = (D·q)/det = (0·0+0·20+(−1)·(−4))/12 = 4/12 = 0,333 — u+v=0,583≤1 ✓
8) t = (edge2·q)/det = (0·0+3·20+0·(−4))/12 = 60/12 = 5,0
9) Punkt trafienia: P = O+t·D = (1,1,5)+5·(0,0,−1) = (1, 1, 0)
Wynik: TRAFIENIE. t=5,0, u=0,25, v=0,333, punkt (1,1,0)
Sprawdzenie barycentryczne: (1−u−v)·A + u·B + v·C = 0,417·(0,0,0) + 0,25·(4,0,0) + 0,333·(0,3,0) = (1,0,0)+(0,1,0) = (1,1,0) ✓ zgadza się z P.

Zadanie

Zadanie 5 — przecięcie z płaszczyzną
N=(0,0,1), Q=(0,0,3), O=(1,1,0), D=(0,0,1). Policz t i punkt trafienia.
Q−O = (−1,−1,3). N·(Q−O) = 3. N·D = 1. t = 3/1 = 3,0.
P = O+3·D = (1,1,0)+(0,0,3) = (1, 1, 3).
ROZDZIAŁ 5

Lambert i Blinn-Phong

Wzór

diffuse = kd·I·max(0,N·L)
H = normalize(L+V), specular = ks·I·max(0,N·H)^shininess
color = ka·Ia + diffuse + specular

Przykład: kompletne shading jednego punktu

Dane wejściowe
Punkt trafienia P=(0,0,0), normalna N=(0,1,0). Światło w Lpos=(2,4,3). Kamera (eye) w (3,5,4). kd=(0,8; 0,2; 0,2), ks=1,0 (biały), shininess=16, ka=0,1, I=Ia=1.
Krok po kroku
1) L_raw = Lpos−P = (2,4,3). |L_raw| = √(4+16+9) = √29 ≈ 5,385 → L = (0,371; 0,743; 0,557)
2) N·L = 0·0,371 + 1·0,743 + 0·0,557 = 0,743
3) diffuse = kd·I·(N·L) = (0,8; 0,2; 0,2)·1·0,743 = (0,594; 0,149; 0,149)
4) V_raw = eye−P = (3,5,4). |V_raw| = √(9+25+16) = √50 ≈ 7,071 → V = (0,424; 0,707; 0,566)
5) H_raw = L+V = (0,371+0,424; 0,743+0,707; 0,557+0,566) = (0,796; 1,450; 1,123)
6) |H_raw| = √(0,633+2,102+1,261) = √3,996 ≈ 1,999 → H = (0,398; 0,725; 0,562)
7) N·H = 0·0,398 + 1·0,725 + 0·0,562 = 0,725
8) (N·H)^16: ln(0,725)≈−0,322, ×16≈−5,157, e^(−5,157) ≈ 0,00587
9) specular = ks·I·0,00587 = 0,00587 (biały, dodaje się do każdego kanału RGB równo)
10) ambient = ka·Ia = 0,1·1 = 0,1 (każdy kanał)
11) color = ambient + diffuse + specular:
R: 0,1+0,594+0,006 = 0,700
G: 0,1+0,149+0,006 = 0,254
B: 0,1+0,149+0,006 = 0,254
Wynik: color ≈ (0,700; 0,254; 0,254) — nasycona czerwień z małym białym błyskiem
Sprawdzenie rzędu wielkości: N·L=0,743 jest duże (światło blisko „nad” punktem) — diffuse dominuje, zgodnie z wynikiem. Specular jest mały, bo N·H=0,725 podniesione do 16. potęgi szybko maleje.

Zadanie

Zadanie 6 — samo N·L
N=(0,1,0), światło w Lpos=(0,1,0), punkt P=(0,0,0) (światło idealnie nad punktem). Policz L i N·L.
L_raw = (0,1,0), już znormalizowany (|.|=1) → L = (0,1,0).
N·L = 0·0+1·1+0·0 = 1,0 — maksymalna możliwa wartość, światło idealnie na wprost normalnej.