Tutorial 3: Homogene matricer og translationer¶
Homogene matricer er 4x4 matricer, der bruges til at repræsentere affine transformationer i 3D-rum. Dette inkluderer translation, rotation, skalering, shear og perspektivisk projektion.
En generel 4x4 transformationsmatrix ser således ud:
Hvor:
- \(r_{ij}\) repræsenterer elementerne i en 3x3 rotationsmatrix (eller en anden lineær transformation).
- \(t_x, t_y, t_z\) repræsenterer translationen i x-, y- og z-retningen.
I bogen notation repræsenteres translationen i den nederste række af matricen, hvor den vektor, der skal transformeres, er en rækkevektor der ganges fra venstre. I videoerne repræsenteres translationen i den sidste søjle af matricen, hvor vektoren, der skal transformeres, er en søjlevektor der ganges fra højre. Dette er også den mest almindelige konvention.
💡 Notation: Rækkevektorer vs. Søjlevektorer¶
Når vi arbejder med homogene koordinater, kan vi vælge at repræsentere punkter som:
-
Rækkevektorer: \(\mathbf{p} = \begin{bmatrix} x & y & z & 1 \end{bmatrix}\)
- Her multiplicerer vi matricen fra højre: \(\mathbf{p}' = \mathbf{p} \mathbf{M}\)
-
Søjlevektorer: \(\mathbf{p} = \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}\)
- Her multiplicerer vi matricen fra venstre: \(\mathbf{p}' = \mathbf{M} \mathbf{p}\)
Vi bruger som bekendt et venstrehåndet system, hvor \( y \)-aksen peger op, men formlerne ændrer sig afhængigt af, om vi bruger række- eller søjlevektorer. I sidste emne præsenterede vi transformationer i 2D og 3D, hvor vi brugte søjlevektorer - vi fulgte bogens konvention. Men når det kommer til translationer og homogene matricer, skifter bogen om til rækkevektorer. Det er lidt træls og kan skabe forvirring. Men heldigvis er det ret simpelt at konvertere mellem de to konventioner:
✔ Forskellen mellem række- og søjlevektorer er, at alle transformationer transponeres!
Bemærk, der kan også være lidt tvivl om hvad der menes med at gange fra højre eller venstre. Lad os sige vi har en vektor \(\mathbf{v}\) og en matrix \(\mathbf{M}\). Hvis vi skriver \(\mathbf{v} \mathbf{M}\), betyder det at vi ganger vektoren fra højre med matricen. Hvis vi skriver \(\mathbf{M} \mathbf{v}\), betyder det at vi ganger vektoren fra venstre med matricen. Men når der er tale om transformationer, skriver man ofte at transformationen anvendes på vektoren fra højre, fx \(\mathbf{p}' = \mathbf{p} \mathbf{M}\), selvom det i praksis betyder at vi ganger vektoren fra venstre med matricen.
2D Transformationer i 3D¶
Selvom disse transformationer er 2D, kan de altså repræsenteres i 3D ved at inkludere dem i en 3D homogen matrix for søjlevektorer:
-
2D Rotation om Origo:
\[ \mathbf{R}(θ)_{\text{søjle}} = \begin{bmatrix} \cos θ & \sin θ & 0 \\ -\sin θ & \cos θ & 0 \\ 0 & 0 & 1 \end{bmatrix} \] -
2D Skalering langs akserne:
\[ \mathbf{S}(k_x, k_y) = \begin{bmatrix} k_x & 0 & 0 \\ 0 & k_y & 0 \\ 0 & 0 & 1 \end{bmatrix} \]
For rækkevektorer, skal du transponere matricen:
-
2D Rotation om Origo:
\[ \mathbf{R}(θ)_{\text{række}} = \begin{bmatrix} \cos θ & -\sin θ & 0 \\ \sin θ & \cos θ & 0 \\ 0 & 0 & 1 \end{bmatrix} \] -
2D Skalering langs akserne: Ingen ændring
1️⃣ Translation¶
En translation forskyder et punkt i rummet med \( (t_x, t_y, t_z) \). For en 3D translation af søjlevektorer bruger vi en 4x4 matrix:
Multiplikation af et punkt \((x, y, z, 1)\) med \(\mathbf{T}\) giver:
Og til rækkevektorer bruger vi den transponerede version:
Multiplikation af et punkt \((x, y, z, 1)\) med \(\mathbf{T}\) giver:
💻 Translation i Python¶
import numpy as np
# 4x4 translationsmatrix for søjlevektoren (4, 3, 2)
tx, ty, tz = 4, 3, 2
translation_matrix_col = np.array([[1, 0, 0, tx],
[0, 1, 0, ty],
[0, 0, 1, tz],
[0, 0, 0, 1]])
# Punkt der skal translateres (som en eksplicit søjlevektor)
p_col = np.array([[1], [2], [3], [1]]) # 4x1 column vector
T_col = translation_matrix_col @ p_col # Output vil være 4x1
print("Translation med søjlevektorer:\n", T_col)
# ✔ Hvis vi bruger rækkevektorer, transponerer vi blot matricen:
# 4x4 translationsmatrix for rækkevektorer
translation_matrix_row = translation_matrix_col.T
# Translation med (4, 2, 3)
T_row1 = p_col.T @ translation_matrix_row
print("Translation med rækkevektorer:\n", T_row1)
# Vi kunne også konvertere søjlevektoren til rækkevektor notation direkte
T_row2 = T_col.T
print("Konverteret til rækkevektor notation:\n", T_row2)
2️⃣ Rotation omkring koordinatakserne¶
Sidst brugte vi \(3 \times 3\) rotationsmatricer til at rotere om koordinatakserne I 3D. Vi kan også repræsentere dette vha. homogene 4D matricer, her for søjlevektorer:
- Rotation om \( x \)-aksen:
- Rotation om \( y \)-aksen:
- Rotation om \( z \)-aksen:
Vil du have for rækkevektorer, transponerer du blot matricen:
- Rotation om \( x \)-aksen: \(\mathbf{R}_x(\theta)_{\text{række}} = \mathbf{R}_x(\theta)_{\text{søjle}}^T\)
- Rotation om \( y \)-aksen: \(\mathbf{R}_y(\theta)_{\text{række}} = \mathbf{R}_y(\theta)_{\text{søjle}}^T\)
- Rotation om \( z \)-aksen: \(\mathbf{R}_z(\theta)_{\text{række}} = \mathbf{R}_z(\theta)_{\text{søjle}}^T\)
💻 Rotation¶
def rotation_matrix_x(theta):
"""Returnerer en 4x4 rotationsmatrix om x-aksen."""
c, s = np.cos(theta), np.sin(theta)
return np.array([[1, 0, 0, 0],
[0, c, -s, 0],
[0, s, c, 0],
[0, 0, 0, 1]])
# Test rotation om x-aksen
theta = np.radians(30)
Rx = rotation_matrix_x(theta)
print("Rotation om x-aksen:\n", Rx)
# Konverter til rækkevektor notation
Rx_row = Rx.T
3️⃣ Affine Transformationer¶
Affine transformationer er transformationer, der kan kombineres af både lineære transformationer (såsom rotation, skalering og spejling) og translationer.
Rotation Om Origo Efterfulgt af Translation¶
Når vi først roterer omkring origo og derefter translaterer, anvender vi transformationerne i denne rækkefølge:
- Søjlevektorer:
- Her roteres punktet før det translateres.
-
Hvis vi ønskede at bruge "de samme" matricer, men med rækkevektorer, skulle vi transponere dem.
-
Rækkevektorer:
- Her er Matricerne \(T\) og \(R\) de samme matricer som i søjletilfældet. Hvis de er givet som matricer for rækkevektorer, skal de ikke transponeres. Dette er vigtigt!
Denne type transformation bruges når objekter roteres i world space og derefter placeres i rummet.
💻 Eksempel¶
import numpy as np
def translation_matrix(tx, ty, tz):
"""Returnerer en 4x4 translationsmatrix."""
return np.array([[1, 0, 0, tx],
[0, 1, 0, ty],
[0, 0, 1, tz],
[0, 0, 0, 1]])
def rotation_matrix_x(theta):
"""Returnerer en 4x4 rotationsmatrix om x-aksen."""
c, s = np.cos(theta), np.sin(theta)
return np.array([[1, 0, 0, 0],
[0, c, -s, 0],
[0, s, c, 0],
[0, 0, 0, 1]])
# Definer et punkt i homogene koordinater
P = np.array([1, 2, 3, 1]) # Søjlevektor
# Transformationer
T = translation_matrix(5, -2, 3)
R = rotation_matrix_x(np.radians(45))
# Anvend transformationerne
P_transformed = T @ R @ P
print("Transformeret søjlevektor:\n", P_transformed)
# Konverter til rækkevektor notation
P_transformed_row = P.T @ R.T @ T.T # P.T er ikke nødvendigt
Du kan se og interagere med ovenstående transformation her:
I dette plot illustrerer vi, hvordan en vektor \( P \) transformeres ved hjælp af en rotation efterfulgt af en translation. Vi viser tre vektorer:
-
Den røde vektor (oprindelig vektor)
- Starter i origo \( (0,0,0) \) og peger mod \( (1,2,3) \).
- Dette er den oprindelige vektor, før nogen transformation er anvendt.
-
Den grønne vektor (roteret vektor)
- Starter i \( (5,-2,3) \) og peger mod \( (6, 1.53553391, 3.70710678) \).
- Dette viser, hvordan vektoren ser ud efter at være blevet roteret, men før translationen har flyttet dens startpunkt.
- Retningen er ændret i forhold til den røde vektor på grund af rotationen omkring x-aksen.
-
Den blå vektor (endeligt transformeret vektor)
- Starter i origo \( (0,0,0) \) og peger mod \( (6, 1.53553391, 3.70710678) \).
- Dette er den færdige transformation, hvor både rotation og translation er anvendt.
- Bemærk, at vektoren nu har en ny længde og retning i forhold til den røde vektor, hvilket viser effekten af begge transformationer kombineret.
🔹 Forskellen mellem den røde og den grønne vektor viser, hvordan rotationen påvirker vektorens retning.
🔹 Forskellen mellem den grønne og den blå vektor viser effekten af translationen.
Bemærk, at det også er muligt først at translere og derefter rotere om x-aksen. Her ville man så bytte om på rækkefølgen af matricerne.
Rotation Omkring Et Vilkårligt Punkt¶
Hvis vi vil rotere omkring et punkt \( \mathbf{p} \), anvender vi en anden strategi:
- Denne metode sikrer, at rotationen ikke nødvendigvis sker omkring origo, men omkring et vilkårligt punkt \( \mathbf{p} \).
- Dette gælder for søjlevektorer.
-
\( T(-\mathbf{p}) \) flytter koordinatsystemet, så rotationspunktet \( \mathbf{p} \) bliver origo.
- \( T(-\mathbf{p}) \) er en translation, der flytter hele rummet modsat af \( \mathbf{p} \), dvs. til \( (0,0,0) \).
-
\( R \) roterer vektoren omkring origo.
- \( R \) anvender rotation omkring origo, hvilket er korrekt efter translationen.
-
\( T(\mathbf{p}) \) flytter koordinatsystemet tilbage.
- \( T(\mathbf{p}) \) gendanner den oprindelige position efter rotationen.
I homogene koordinater ser dette sådan ud for en søjlevektor \( \mathbf{v} \):
Hvis vi vil rotere omkring et punkt \( \mathbf{p} \) med rækkevektorer, anvender vi en anden strategi:
- Denne metode sikrer, at rotationen ikke nødvendigvis sker omkring origo, men omkring et vilkårligt punkt \( \mathbf{p} \).
- Dette gælder for rækkevektorer.
-
\( T(-\mathbf{p}) \) flytter koordinatsystemet, så rotationspunktet \( \mathbf{p} \) bliver origo.
- \( T(-\mathbf{p}) \) er en translation, der flytter hele rummet modsat af \( \mathbf{p} \), dvs. til \( (0,0,0) \).
-
\( R \) roterer vektoren omkring origo.
- \( R \) anvender rotation omkring origo, hvilket er korrekt efter translationen.
-
\( T(\mathbf{p}) \) flytter koordinatsystemet tilbage.
- \( T(\mathbf{p}) \) gendanner den oprindelige position efter rotationen.
I homogene koordinater ser dette sådan ud for en rækkevektor \( \mathbf{v} \):
Bemærk her at \(T\) og \(R\) ikke er de helt samme matricer som vi anvendte til søjlevektorerne. De er transponerede, da vi nu arbejder med rækkevektorer. Dette fremgår også af koden nedenfor.
💻 Affine: Translation så rotation - søjle¶
### For søjlevektorer
def affine_transform_around_point_col(p, R):
"""Udfører en transformation omkring et punkt p = (px, py, pz)."""
T1 = translation_matrix(-p[0], -p[1], -p[2])
T2 = translation_matrix(p[0], p[1], p[2])
return T2 @ R @ T1
p = np.array([3, 2, 1]) # Rotationscenter
R = rotation_matrix_x(np.radians(45))
M = affine_transform_around_point_col(p, R)
print("Affine transformation omkring punktet (3,2,1):\n", M)
💻 Affine: Translation, så rotation - række¶
def affine_transform_around_point_row(p, R):
"""Udfører en transformation omkring et punkt p = (px, py, pz) for rækkevektorer."""
T1 = translation_matrix(-p[0], -p[1], -p[2]).T # Bemærk
T2 = translation_matrix(p[0], p[1], p[2]).T # Bemærk
return T1 @ R @ T2
p = np.array([3, 2, 1]) # Rotationscenter
R = rotation_matrix_x(np.radians(45)).T # Bemærk
M = affine_transform_around_point_row(p, R)
print("Affine transformation omkring punktet (3,2,1) (rækkevektor-metoden):\n", M)
4️⃣ 3D Transformationer som 4D Homogene Matricer¶
Øvrige transformationer i 3D kan også repræsenteres som 4x4 homogene matricer:
-
Skalering langs de kartesiske akser:
\[ \mathbf{S}(k_x, k_y, k_z) = \begin{bmatrix} k_x & 0 & 0 & 0 \\ 0 & k_y & 0 & 0 \\ 0 & 0 & k_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \] -
Rotation om en vilkårlig akse \(\hat{\mathbf{n}} = (n_x, n_y, n_z)\):
\[ \mathbf{R}(\hat{\mathbf{n}}, θ) = \begin{bmatrix} n_x^2(1 - \cos θ) + \cos θ & n_xn_y(1 - \cos θ) + n_z\sin θ & n_xn_z(1 - \cos θ) - n_y\sin θ & 0 \\ n_xn_y(1 - \cos θ) - n_z\sin θ & n_y^2(1 - \cos θ) + \cos θ & n_yn_z(1 - \cos θ) + n_x\sin θ & 0 \\ n_xn_z(1 - \cos θ) + n_y\sin θ & n_yn_z(1 - \cos θ) - n_x\sin θ & n_z^2(1 - \cos θ) + \cos θ & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \] -
Skalering i en vilkårlig retning \(\hat{\mathbf{n}}\) med faktor \(k\):
\[ \mathbf{S}(\hat{\mathbf{n}}, k) = \begin{bmatrix} 1 + (k-1)n_x^2 & (k-1)n_xn_y & (k-1)n_xn_z & 0 \\ (k-1)n_xn_y & 1 + (k-1)n_y^2 & (k-1)n_yn_z & 0 \\ (k-1)n_xn_z & (k-1)n_yn_z & 1 + (k-1)n_z^2 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \] -
Ortografisk projektion på et plan med normalvektor \(\hat{\mathbf{n}}\):
\[ \mathbf{P}(\hat{\mathbf{n}}) = \begin{bmatrix} 1 - n_x^2 & -n_xn_y & -n_xn_z & 0 \\ -n_xn_y & 1 - n_y^2 & -n_yn_z & 0 \\ -n_xn_z & -n_yn_z & 1 - n_z^2 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \] -
Refleksion om et plan med normalvektor \(\hat{\mathbf{n}}\):
\[ \mathbf{R}(\hat{\mathbf{n}}) = \begin{bmatrix} 1 - 2n_x^2 & -2n_xn_y & -2n_xn_z & 0 \\ -2n_xn_y & 1 - 2n_y^2 & -2n_yn_z & 0 \\ -2n_xn_z & -2n_yn_z & 1 - 2n_z^2 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \]
Vigtige afsluttende pointer¶
- Rækkefølge af Transformationer: Når du bruger homogene matricer, er rækkefølgen, transformationerne anvendes i, afgørende. Hvis du kombinerer flere transformationer, skal du multiplicere matricerne i den korrekte rækkefølge. Hvis du bruger rækkevektorer, udføres transformationerne fra venstre mod højre. Hvis du bruger søjlevektorer, udføres de fra højre mod venstre.
- To sæt transformationsmatricer: Transformationsmatricerne er også forskellige om du bruger søjle- eller rækkevektorer.
- Affine Transformationer: Homogene matricer giver dig mulighed for at kombinere lineære transformationer (rotation, skalering, shear) med translation, hvilket resulterer i affine transformationer.
- Perspektivisk Projektion: Den fjerde kolonne i en homogen matrix kan bruges til at implementere perspektivisk projektion, hvilket er afgørende for at skabe realistiske 3D-scener.
- Points at infinity: Disse punkter er defineret som værende på formen \((x, y, z, 0)\)
- Translation 'Slås fra': w-komponenten i en 4D vektor kan bruges til selektivt at "slukke" for translationsdelen af en 4×4 matrix