Files
skills/shader-dev/techniques/matrix-transform.md
shihao 6487becf60 Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:52:49 +08:00

456 lines
12 KiB
Markdown

# Matrix Transforms & Camera
## Use Cases
- Camera systems in 3D scenes (orbit camera, fly camera, path camera)
- SDF object domain transforms via translation, rotation, and scale matrices
- Generating 3D rays from screen pixels (perspective / orthographic projection)
- Hierarchical rotation transforms for joint animation
- Rotation in noise domain warping, IFS fractal iterations
## Core Principles
The essence of matrix transforms is coordinate system transformation. In a ray marching pipeline:
1. **Camera matrix**: Screen pixels → world-space ray direction (view-to-world)
2. **Object transform matrix**: World-space sample point → object local space (world-to-local, domain transform)
### Key Formulas
**2D Rotation** R(θ) = `[[cosθ, -sinθ], [sinθ, cosθ]]`
**3D Rotation Around Y-Axis** Ry(θ) = `[[cosθ, 0, sinθ], [0, 1, 0], [-sinθ, 0, cosθ]]`
**Rodrigues (Arbitrary Axis k, Angle θ)**: `R = cosθ·I + (1-cosθ)·k⊗k + sinθ·K`
**LookAt Camera**:
```
forward = normalize(target - eye)
right = normalize(cross(forward, worldUp))
up = cross(right, forward)
viewMatrix = mat3(right, up, forward)
```
**Perspective Ray**: `rd = normalize(camMatrix * vec3(uv, focalLength))`
## Implementation Steps
### Step 1: Screen Coordinate Normalization
```glsl
// Range [-aspect, aspect] x [-1, 1]
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
```
### Step 2: Rotation Matrices
```glsl
// 2D rotation (mat2)
mat2 rot2D(float a) {
float c = cos(a), s = sin(a);
return mat2(c, s, -s, c);
}
// 3D single-axis rotation (mat3)
mat3 rotX(float a) {
float s = sin(a), c = cos(a);
return mat3(1, 0, 0, 0, c, s, 0, -s, c);
}
mat3 rotY(float a) {
float s = sin(a), c = cos(a);
return mat3(c, 0, s, 0, 1, 0, -s, 0, c);
}
mat3 rotZ(float a) {
float s = sin(a), c = cos(a);
return mat3(c, s, 0, -s, c, 0, 0, 0, 1);
}
// Euler angles → mat3 (yaw/pitch/roll)
mat3 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x), cos(ang.x));
vec2 a2 = vec2(sin(ang.y), cos(ang.y));
vec2 a3 = vec2(sin(ang.z), cos(ang.z));
mat3 m;
m[0] = vec3( a1.y*a3.y + a1.x*a2.x*a3.x,
a1.y*a2.x*a3.x + a3.y*a1.x,
-a2.y*a3.x);
m[1] = vec3(-a2.y*a1.x, a1.y*a2.y, a2.x);
m[2] = vec3( a3.y*a1.x*a2.x + a1.y*a3.x,
a1.x*a3.x - a1.y*a3.y*a2.x,
a2.y*a3.y);
return m;
}
// Rodrigues arbitrary-axis rotation (mat3)
mat3 rotationMatrix(vec3 axis, float angle) {
axis = normalize(axis);
float s = sin(angle), c = cos(angle), oc = 1.0 - c;
return mat3(
oc*axis.x*axis.x + c, oc*axis.x*axis.y - axis.z*s, oc*axis.z*axis.x + axis.y*s,
oc*axis.x*axis.y + axis.z*s, oc*axis.y*axis.y + c, oc*axis.y*axis.z - axis.x*s,
oc*axis.z*axis.x - axis.y*s, oc*axis.y*axis.z + axis.x*s, oc*axis.z*axis.z + c
);
}
```
### Step 3: LookAt Camera
```glsl
// Classic setCamera, cr = camera roll
mat3 setCamera(in vec3 ro, in vec3 ta, float cr) {
vec3 cw = normalize(ta - ro);
vec3 cp = vec3(sin(cr), cos(cr), 0.0);
vec3 cu = normalize(cross(cw, cp));
vec3 cv = normalize(cross(cu, cw));
return mat3(cu, cv, cw);
}
// mat4 LookAt (with translation, for homogeneous coordinate scenes)
mat4 LookAt(vec3 pos, vec3 target, vec3 up) {
vec3 dir = normalize(target - pos);
vec3 x = normalize(cross(dir, up));
vec3 y = cross(x, dir);
return mat4(vec4(x, 0), vec4(y, 0), vec4(dir, 0), vec4(pos, 1));
}
```
### Step 4: Perspective Ray Generation
```glsl
// mat3 camera — focalLength controls FOV: 1.0≈90°, 2.0≈53°, 4.0≈28°
#define FOCAL_LENGTH 2.0
mat3 cam = setCamera(ro, ta, 0.0);
vec3 rd = cam * normalize(vec3(uv, FOCAL_LENGTH));
// Manual basis vector composition
#define FOV 1.0
vec3 rd = normalize(camDir + (uv.x * camRight + uv.y * camUp) * FOV);
// mat4 homogeneous coordinates
mat4 viewToWorld = LookAt(camPos, camTarget, camUp);
vec3 rd = (viewToWorld * normalize(vec4(uv, 1.0, 0.0))).xyz;
```
### Step 5: Mouse-Interactive Camera
```glsl
// Spherical coordinate orbit camera
#define CAM_DIST 5.0
#define CAM_HEIGHT 1.0
vec2 mouse = iMouse.xy / iResolution.xy;
float angleH = mouse.x * 6.2832;
float angleV = mouse.y * 3.1416 - 1.5708;
if (iMouse.z <= 0.0) {
angleH = iTime * 0.5;
angleV = 0.3;
}
vec3 ro = vec3(
CAM_DIST * cos(angleH) * cos(angleV),
CAM_DIST * sin(angleV) + CAM_HEIGHT,
CAM_DIST * sin(angleH) * cos(angleV)
);
vec3 ta = vec3(0.0);
```
### Step 6: SDF Domain Transforms
```glsl
// Translation
float d = sdSphere(p - vec3(2.0, 0.0, 0.0), 1.0);
// Rotation (orthogonal matrix inverse = transpose)
float d = sdBox(rotY(0.5) * p, vec3(1.0));
// Scale (divide by scale factor, multiply back into distance)
#define SCALE 2.0
float d = sdSphere(p / SCALE, 1.0) * SCALE;
// mat4 SRT composition
mat4 Loc4(vec3 d) {
d *= -1.0;
return mat4(1,0,0,d.x, 0,1,0,d.y, 0,0,1,d.z, 0,0,0,1);
}
mat4 transposeM4(in mat4 m) {
return mat4(
vec4(m[0].x, m[1].x, m[2].x, m[3].x),
vec4(m[0].y, m[1].y, m[2].y, m[3].y),
vec4(m[0].z, m[1].z, m[2].z, m[3].z),
vec4(m[0].w, m[1].w, m[2].w, m[3].w));
}
vec3 opTx(vec3 p, mat4 m) {
return (transposeM4(m) * vec4(p, 1.0)).xyz;
}
// First translate to (3,0,0), then rotate 45° around Y-axis
mat4 xform = Rot4Y(0.785) * Loc4(vec3(3.0, 0.0, 0.0));
float d = sdBox(opTx(p, xform), vec3(1.0));
```
### Step 7: Quaternion Rotation
```glsl
vec4 axisAngleToQuat(vec3 axis, float angleDeg) {
float half_angle = angleDeg * 3.14159265 / 360.0;
vec2 sc = sin(vec2(half_angle, half_angle + 1.5707963));
return vec4(normalize(axis) * sc.x, sc.y);
}
vec3 quatRotate(vec3 pos, vec3 axis, float angleDeg) {
vec4 q = axisAngleToQuat(axis, angleDeg);
return pos + 2.0 * cross(q.xyz, cross(q.xyz, pos) + q.w * pos);
}
// Hierarchical rotation in joint animation
vec3 limbPos = quatRotate(p - shoulderOffset, vec3(1,0,0), swingAngle);
float d = sdEllipsoid(limbPos, limbSize);
```
## Complete Code Template
Can be run directly in ShaderToy, demonstrating LookAt camera + multi-object domain transforms + mouse interaction.
```glsl
// === Matrix Transforms & Camera - Complete Template ===
#define PI 3.14159265
#define MAX_STEPS 128
#define MAX_DIST 50.0
#define SURF_DIST 0.001
#define FOCAL_LENGTH 2.0
#define CAM_DIST 6.0
#define AUTO_SPEED 0.4
// ---------- Rotation Matrix Utilities ----------
mat2 rot2D(float a) {
float c = cos(a), s = sin(a);
return mat2(c, s, -s, c);
}
mat3 rotX(float a) {
float s = sin(a), c = cos(a);
return mat3(1,0,0, 0,c,s, 0,-s,c);
}
mat3 rotY(float a) {
float s = sin(a), c = cos(a);
return mat3(c,0,s, 0,1,0, -s,0,c);
}
mat3 rotZ(float a) {
float s = sin(a), c = cos(a);
return mat3(c,s,0, -s,c,0, 0,0,1);
}
mat3 rotAxis(vec3 axis, float angle) {
axis = normalize(axis);
float s = sin(angle), c = cos(angle), oc = 1.0 - c;
return mat3(
oc*axis.x*axis.x+c, oc*axis.x*axis.y-axis.z*s, oc*axis.z*axis.x+axis.y*s,
oc*axis.x*axis.y+axis.z*s, oc*axis.y*axis.y+c, oc*axis.y*axis.z-axis.x*s,
oc*axis.z*axis.x-axis.y*s, oc*axis.y*axis.z+axis.x*s, oc*axis.z*axis.z+c
);
}
// ---------- LookAt Camera ----------
mat3 setCamera(vec3 ro, vec3 ta, float cr) {
vec3 cw = normalize(ta - ro);
vec3 cp = vec3(sin(cr), cos(cr), 0.0);
vec3 cu = normalize(cross(cw, cp));
vec3 cv = normalize(cross(cu, cw));
return mat3(cu, cv, cw);
}
// ---------- SDF Primitives ----------
float sdSphere(vec3 p, float r) { return length(p) - r; }
float sdBox(vec3 p, vec3 b) {
vec3 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
float sdTorus(vec3 p, vec2 t) {
vec2 q = vec2(length(p.xz) - t.x, p.y);
return length(q) - t.y;
}
// ---------- Scene (Domain Transform Demo) ----------
float map(vec3 p) {
float d = p.y + 1.0; // Ground plane
// Static sphere
d = min(d, sdSphere(p, 0.5));
// Rotating box (spinning around Y-axis)
vec3 p2 = p - vec3(2.5, 0.0, 0.0);
p2 = rotY(iTime * 0.8) * p2;
d = min(d, sdBox(p2, vec3(0.6)));
// Arbitrary-axis rotating torus
vec3 p3 = p - vec3(-2.5, 0.5, 0.0);
p3 = rotAxis(vec3(1,1,0), iTime * 0.6) * p3;
d = min(d, sdTorus(p3, vec2(0.6, 0.2)));
// Scaled + rotated sphere
vec3 p4 = p - vec3(0.0, 0.5, 2.5);
p4 = rotZ(iTime * 1.2) * rotX(iTime * 0.7) * p4;
float scale = 1.5;
d = min(d, sdSphere(p4 / scale, 0.4) * scale);
return d;
}
// ---------- Normal ----------
vec3 calcNormal(vec3 p) {
vec2 e = vec2(0.001, 0.0);
return normalize(vec3(
map(p + e.xyy) - map(p - e.xyy),
map(p + e.yxy) - map(p - e.yxy),
map(p + e.yyx) - map(p - e.yyx)
));
}
// ---------- Ray March ----------
float rayMarch(vec3 ro, vec3 rd) {
float t = 0.0;
for (int i = 0; i < MAX_STEPS; i++) {
vec3 p = ro + rd * t;
float d = map(p);
if (d < SURF_DIST) break;
t += d;
if (t > MAX_DIST) break;
}
return t;
}
// ---------- Main Function ----------
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
// Mouse-interactive orbit camera
float angleH, angleV;
if (iMouse.z > 0.0) {
vec2 m = iMouse.xy / iResolution.xy;
angleH = m.x * 2.0 * PI;
angleV = (m.y - 0.5) * PI;
} else {
angleH = iTime * AUTO_SPEED;
angleV = 0.35;
}
vec3 ro = vec3(
CAM_DIST * cos(angleH) * cos(angleV),
CAM_DIST * sin(angleV) + 1.0,
CAM_DIST * sin(angleH) * cos(angleV)
);
vec3 ta = vec3(0.0);
mat3 cam = setCamera(ro, ta, 0.0);
vec3 rd = cam * normalize(vec3(uv, FOCAL_LENGTH));
float t = rayMarch(ro, rd);
vec3 col = vec3(0.0);
if (t < MAX_DIST) {
vec3 p = ro + rd * t;
vec3 n = calcNormal(p);
vec3 lightDir = normalize(vec3(1.0, 2.0, -1.0));
float diff = max(dot(n, lightDir), 0.0);
col = vec3(0.8, 0.85, 0.9) * (diff + 0.15);
if (p.y < -0.99) {
float checker = mod(floor(p.x) + floor(p.z), 2.0);
col *= 0.5 + 0.3 * checker;
}
} else {
col = vec3(0.4, 0.6, 0.9) - rd.y * 0.3;
}
col = pow(col, vec3(0.4545));
fragColor = vec4(col, 1.0);
}
```
## Common Variants
### Orthographic Projection Camera
```glsl
#define ORTHO_SIZE 5.0
mat3 cam = setCamera(ro, ta, 0.0);
vec3 rd = cam * vec3(0.0, 0.0, 1.0); // Fixed direction
ro += cam * vec3(uv * ORTHO_SIZE, 0.0); // Offset origin
```
### Euler Angle Full Rotation Camera
```glsl
vec3 ang = vec3(pitch, yaw, roll);
mat3 rot = fromEuler(ang);
vec3 ori = vec3(0.0, 0.0, 3.0) * rot;
vec3 rd = normalize(vec3(uv, -2.0)) * rot;
```
### Quaternion Joint Rotation
```glsl
vec3 legP = quatRotate(p - hipOffset, vec3(1,0,0), legAngle);
float dLeg = sdEllipsoid(legP, vec3(0.2, 0.6, 0.25));
```
### mat4 SRT Pipeline
```glsl
mat4 Rot4Y(float a) {
float c = cos(a), s = sin(a);
return mat4(c,0,s,0, 0,1,0,0, -s,0,c,0, 0,0,0,1);
}
mat4 xform = Rot4Y(angle) * Loc4(vec3(3.0, 0.0, 0.0));
float d = sdBox(opTx(p, xform), boxSize);
```
### Path Camera (Animated Flight)
```glsl
vec2 pathCenter(float z) {
return vec2(sin(z * 0.17) * 3.0, sin(z * 0.1 + 4.0) * 2.0);
}
float z_offset = iTime * 10.0;
vec3 camPos = vec3(pathCenter(z_offset), 0.0);
vec3 camTarget = vec3(pathCenter(z_offset + 5.0), 5.0);
mat4 viewToWorld = LookAt(camPos, camTarget, camUp);
vec3 rd = (viewToWorld * normalize(vec4(uv, 1.0, 0.0))).xyz;
```
## Performance & Composition
**Performance**:
- Compute `sin/cos` of the same angle only once: `vec2 sc = sin(vec2(a, a + 1.5707963));`
- Use `mat3` instead of `mat4` for pure rotation (saves 7 multiply-adds)
- Inverse of orthogonal rotation matrix = transpose; use `transpose(m)` or `v * m`
- Pre-compute matrices that don't depend on `p` outside `map()`
- Pre-multiply multiple rotations into a single matrix
**Composition**:
- **SDF / Ray Marching**: Camera generates rays + domain transforms place objects (fundamental pipeline)
- **Noise / fBm**: Rotate sampling coordinates to break axis-aligned regularity `fbm(rot * p)`
- **Fractals / IFS**: Embed rotation in iterations to create complex geometry
- **Lighting**: Normal transform for pure rotation matrices is the same as vertex transform
- **Post-Processing**: FOV for depth of field; `mat2` for chromatic aberration/motion blur direction
## Further Reading
For complete step-by-step tutorials, mathematical derivations, and advanced usage, see [reference](../reference/matrix-transform.md)