Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
455
shader-dev/techniques/matrix-transform.md
Normal file
455
shader-dev/techniques/matrix-transform.md
Normal file
@@ -0,0 +1,455 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user