magic-pixels uses the conventions of OpenGL and three.js: a right-handed system with +X to the right, +Y up and +Z towards the viewer. A camera at the origin with no rotation looks down -Z. Angles are in radians, positive is counter-clockwise when looking down the axis towards the origin.
"Right-handed" is a fact about which way the third axis points once two are chosen: point your right index finger along +X and middle finger along +Y, and your thumb gives +Z. It matters because cross products, normals and rotation directions all flip in a left-handed system, and mixing the two is a classic source of inside-out models.
A vertex position from a geometry goes through four coordinate systems on its way to the screen, one matrix multiplication per step:
position attribute. A sphere from
createSphereGeometry() is centred on the origin here.modelMatrix (the object's worldMatrix). Where
the vertex is in the scene.viewMatrix. The
camera is at the origin looking down -Z. Lighting is easiest here, so the
built-in vertex shader passes the view-space position and normal to the
fragment shader.projectionMatrix. A 4D position (x, y, z, w).
The GPU clips triangles against -w ≤ x, y, z ≤ w, then divides by w
(the perspective divide) to get normalised device coordinates from -1
to 1 on each axis, which map to the viewport.The renderer provides modelViewMatrix (viewMatrix × modelMatrix) as a
built-in uniform because most shaders never need world space on its own.
The camera is an Object3D. Its worldMatrix places the camera in the
world like it would place a mesh. But to draw from the camera's point of view
we need the opposite: a transform that moves the world so the camera ends up
at the origin. That is the inverse of the camera's world matrix, and
Camera recomputes it in onWorldMatrixChanged() whenever the tree
walk updates the camera:
viewMatrix = worldMatrix⁻¹
Two consequences follow. Moving the camera to (0, 0, 5) moves the world to
z = -5 in view space, in front of the camera. And parenting a camera to
an object works without any special code: its world matrix already includes
the parent's transform.
PerspectiveCamera takes a vertical field of view in degrees, an
aspect ratio, and near and far distances. Mat4.setPerspective turns the
field of view into the half-height of the near plane,
top = near × tan(fov / 2), and the aspect ratio into its half-width, then
calls setFrustum, which builds the standard OpenGL frustum matrix:
| 2n/(r-l) 0 (r+l)/(r-l) 0 |
| 0 2n/(t-b) (t+b)/(t-b) 0 |
| 0 0 -(f+n)/(f-n) -2fn/(f-n) |
| 0 0 -1 0 |
The -1 in the bottom row is the trick: it copies -z (the distance in
front of the camera) into w, so the perspective divide shrinks far things.
The third row maps the depth range [-near, -far] to [-1, 1] non-linearly;
depth precision is concentrated near the near plane, which is why a tiny
near value causes z-fighting far away.
The horizontal field of view is derived from the vertical one and the
aspect ratio, so resizing the canvas needs camera.aspect = width / height
followed by camera.updateProjectionMatrix().
OrthographicCamera maps a box [left, right] × [bottom, top] × [near, far] straight onto the clip cube with a scale and a translation; w
stays 1, so there is no perspective divide and parallel lines stay parallel.
It is the camera for 2D and for UI, and for isometric looks.
Normals go from object space to view space with normalMatrix, the inverse
transpose of the model-view matrix (see
Matrices). The renderer
computes it per mesh only when the shader declares the uniform.
near and far.