Turning a head into
a page of pen strokes.
The pen engine draws whatever strokes it is handed. How the pen works covers that half — path, mark, deposit, render. This page is about where the strokes come from: a 3D head, a light, and a rule for turning tone into marks.
Nothing here is a filter over a rendered image. There is no image. The head is posed, rasterised into a buffer of surface facts, shaded down to a single channel of darkness, and then walked in rows by a hatcher that decides where a pen touches down, where it lifts, and how hard it presses at every step in between. The pen only ever sees the strokes.
Every head on this page is a live run of that pipeline, at the same 1080px the app renders at, so no figure can drift away from the code it describes. Where something has a slider, drag it.
00A HEAD, AND A WAY OF SHADING IT
Two things have to be settled before any of this runs: where the geometry comes from, and what kind of marks are going to describe it.
The geometry is GNM1, Google's parametric head model. A parametric model is the right kind of source here because it does not hand you a head, it hands you a mean head plus an identity basis2 — a stack of shapes that deform it — so any particular face is a list of numbers saying how much of each to add. GNM's list is 253 long; this drawing resolves the first ten and leaves the rest at the mean. That is worth more than it sounds. The width of a skull is not something a hatch can adjust afterwards, and having it be a parameter means it can be chosen instead of lived with; section 08 comes back to exactly that. The numbers are resolved when the mesh is baked, so what the browser downloads is one specific head rather than the machinery to reach any of them.
The marks are hatching3: ruled rows, twenty pixels apart at 1080, all running in one direction. The shading is carried by the weight of each mark rather than by the spacing between rows — a distinction the whole of sections 05 and 06 turn on, and the single decision that most changes how the drawing reads. The finished page then goes through the same five-level tone step4 as everything on the pen page — 17, 77, 136, 196, 255. The head changes nothing about that.
01THE PIPELINE
Four stages sit in front of the pen's four. Each hands the next something narrower than it received: a skeleton becomes a posed mesh, a mesh becomes a G-buffer5, a G-buffer becomes one channel of darkness, and darkness becomes strokes. By the time the pen is involved there is no head left in the data at all.
Narrowing that hard, that early, is the point. It means the light can be changed without touching a triangle, the hatch can be changed without touching the light, and the pen can be swapped for a different pen without any of the three knowing. It also means each stage can be looked at on its own — the TONE toggle in the app shows the darkness field directly, because tuning a hatch against a field you cannot see is guesswork.
The whole head half runs in plain TypeScript on the CPU. The GPU only appears once there are strokes to rasterise.
02STAGE ONE · POSE
The asset is 14,008 vertices, 27,844 triangles and four joints — neck, head, and one for each eye — stored in metres relative to the head joint, and already carrying its identity. Posing it is linear blend skinning6, matching GNM's own implementation so the mesh deforms the way the model's authors intended.
Each joint gets a rotation as yaw, pitch and roll, composed Ry · Rx · Rz. Forward kinematics7 walks the chain from the neck outward, multiplying each local rotation onto its parent's world transform and accumulating the parent-relative offsets into a world translation. Turning the neck carries the head, and the head carries the eyes, because they are downstream of it.
Then each joint's transform is rebased — offset = worldT − world · jointBind — so that it rotates the mesh about that joint's own bind position rather than about the origin. Without it a neck yaw would swing the entire head around a point somewhere inside the skull.
Skinning itself is the average you would expect: for every vertex, every joint that influences it transforms it, and the results are summed by weight. Weights are stored as one byte per joint per vertex, renormalised at bake time so the sum is exactly 255. Normals go through the rotation only — no translation — and are renormalised afterwards, because a weighted average of unit vectors is not a unit vector.
02.1ONE SLIDER, TWO MECHANISMS
ROTATE HEAD is a single control and it drives two entirely different things. The neck and head joints take a fixed share of the rotation until they reach their limits, so a small turn is mostly articulation — the skeleton does the work it is there for, and the drawing shows a head turning on a neck. The camera orbits by whatever is left over: about a third of the turn from the first degree, and all of it once the joints have run out.
The limits are not arbitrary. Plain skinning pinches at the base of a turned neck, and the usual repair is a pose corrective8. GNM has a slot for one — pose_correctives_regressor, 36 × 53,463 floats — but every coefficient in it is zero in the v3.0 head this is baked from, so there is nothing to apply. The joints stop instead at the angles where skinning alone still holds up, and the camera takes the rest.
| NECK JOINT | ±20° | -7.5° | |
| HEAD JOINT | ±30° | -8.8° | |
| CAMERA ORBIT | the remainder | -8.8° |
One outright bug fell out of building it this way. Joint yaw and camera yaw had been written with opposite handedness, so posing the neck partly cancelled the orbit. Neither was wrong on its own, and it only surfaced when a single slider drove both.
03STAGE TWO · RASTER
The rasteriser is on the CPU, deliberately. The page already owns a WebGL context for the ink, and standing up a second one just to read a depth buffer back on every slider move costs more than the few milliseconds JS needs for a mesh this size — with no readback stall, and with the result sitting in plain arrays where the hatcher can read it directly.
Framing comes first. The camera is placed so the subject's bounding sphere9 fills the requested fraction of the frame: dist = radius / (zoom · tan(fov/2)). That one line is what lets PERSPECTIVE and SIZE be independent controls — change the field of view10 and the camera moves in or out to keep the head the same size on the page, so the only thing that changes is how much the lens exaggerates depth.
Every vertex is then transformed once into screen space and cached — rotate by the camera matrix, take the camera-space distance d, and project with w = pixelsPerUnit / d. Screen y runs downward, so it is subtracted rather than added. Vertices closer than the near plane get a zero marker and any triangle touching one is dropped whole; the camera stays outside the bounding sphere at every setting the panel allows, so nothing visible is lost that way.
Then the triangle loop, forty-odd lines that do four things. Backface culling11 first, from the sign of the projected signed area — on a closed surface that discards half the mesh and nothing that would have been seen. Then a bounding box clipped to the buffer. Then, per pixel, three edge functions whose signs say whether the pixel is inside; the same three numbers, divided by the area, are the barycentric weights12. Then a depth test13 against whatever was written there before.
Depth, normals14 and ink allowance are all interpolated with those screen-space weights, which is affine rather than perspective-correct — strictly it is the wrong interpolation. At 27,844 triangles on a head that fills half the frame, a triangle spans a couple of pixels and the error is far below anything a 20px hatch row could express. It is a deliberate trade, not an oversight.
03.1FRAMING ON WHAT IS DRAWN
The mesh is stored relative to the head joint, which sits inside the skull with a neck stump hanging below it. Point the camera at the joint and the drawing rides 120px up the page: the joint is not the centre of what gets drawn, because the neck below it takes up frame without taking much ink. Technically centred, visually wrong.
So the framing is measured instead. At load time the bounds of every vertex that takes more than a token amount of ink are computed, and the camera looks at and orbits about the centre of that. The drawing lands centred on the page whatever the pose, and no manual nudge is needed anywhere in the settings.
03.2THE TONE FIELD IS HALF SIZE
The G-buffer, and so the tone field15 that comes out of it, is rendered at half the canvas resolution — 540px against 1080 — clamped to between 384 and 768. Not as a performance compromise. A sharper field is measurably worse.
The reason is that a hatcher can only express detail by starting and stopping. Tone structure finer than the row pitch has nowhere else to go, so the only thing extra resolution can buy is breakage: marks chopped by features too small to be marks of their own. At a 20px pitch the effect is mild — the drawing gains a run or two, in places a draughtsman would not have lifted the pen — and it costs three times as much time to compute. Both directions of that trade point the same way. Section 05 comes back to what stray breaks do to a drawing.
04STAGE THREE · LIGHT
This stage is thirty lines long and decides almost everything about whether the drawing reads as a face. It takes the G-buffer and produces one number per pixel: darkness, where 0 is bare paper and 1 is solid ink. Nothing downstream sees anything else.
The light itself is a direction, built from an azimuth and an elevation — there is no position, no falloff, no shadow map. What matters here is not simulating a room, it is producing a tone gradient a pen can follow. Four lines do that — wrapped Lambert, a rim darkening, a contrast curve and the neck fade — and they are worth stepping through in the order the code applies them. The figure below switches on three of them; contrast does nothing at its default, and has a section to itself.
float lambert = dot(normal, lightDir);float lit = clamp((lambert + wrap) / (1.0 + wrap), 0.0, 1.0);float d = 1.0 - lit;d += rim * pow(1.0 - facing, 3.0);d = PIVOT + (d - PIVOT) * contrast;▸ d *= fade;
The last multiply is not lighting at all. Each vertex carries an ink allowance that falls from 1 just under the jaw to 0 by the bottom of the neck stump, and the rasteriser interpolates it into the G-buffer alongside the normals. Multiplying the darkness by it means the tone reaches the hatcher already faded, so runs shorten and then stop — the drawing ends where the head does rather than at the edge of the paper.
The contrast line stays greyed throughout: at the default of 1.0 it is the identity. What it does when it is not is 04.3.
04.1WHY LAMBERT FAILS, AND WHAT WRAP DOES
Lambertian shading16 is the right model for a matte surface: how squarely it faces the light, clamped at zero where it turns away. The trouble is what it leaves for a pen. Half the sphere of surface directions maps onto a gradient and the other half maps onto a single value — solid black — so the head arrives at the hatcher as a bright region, a black region, and a terminator17 a pixel wide between them.
Rows crossing that either draw at full weight or not at all. There is no swelling and no thinning, because there is no gradient to swell across. The result is a silhouette with a hard edge through it, and no amount of tuning the hatch fixes a field with no information in it.
Wrap lighting18 is the fix, and it is a cheat. Light is allowed past the terminator by a fixed amount and the whole range is rescaled so full brightness still means full brightness. Physically it stands in for light bouncing around a room; practically it converts the cliff into a ramp that runs from the lit profile edge all the way to the back of the skull. That ramp is the face. Everything legible in the drawing — the cheekbone, the brow, the plane change at the jaw — is the hatch following it.
The shaded band is the part of the sphere of directions that produces a tone the hatcher can vary a mark over — 68% of it here. At zero it is exactly half, and every surface past the terminator is the same solid black.
04.2HOLDING THE SILHOUETTE
The second term is a small darkening wherever the surface turns away from the camera, taken from the facing ratio19 — the z component of the normal in view space, zero exactly at the silhouette. It is cubed, so it is nothing across the front of the face and only bites in the last few degrees before an edge.
Without it the lit side of the head fades to paper before it reaches the outline and the profile dissolves; the drawing loses its own boundary. It is set at 0.12 and it is the one term here that is doing a purely graphic job rather than an optical one.
04.3A TONE CURVE THAT HINGES LOW
Third is contrast, and the only interesting thing about it is where it pivots. d = PIVOT + (d − PIVOT) · contrast with PIVOT at 0.35, below mid grey.
A curve hinged at 0.5 lightens as much as it darkens, which is the wrong trade for this drawing. What it wants is the lit side held near paper while everything past the terminator is pushed toward solid — so the hinge sits low, and raising contrast spends most of its range on the shadow side.
Below the pivot the curve is pulled toward paper, above it toward solid. Hinging at 0.35 rather than 0.5 means a lift in contrast spends most of its range darkening the shadow side and leaves the lit cheek alone, which is the trade the drawing wants.
04.4WHERE THE DRAWING STOPS
The last multiply is not lighting at all. GNM ends at a neck stump: the subject is a head, and the stump is an artefact of where the model stops, not a shoulder. Cropping it at the frame edge only moves the problem — a drawing that runs off the bottom of the paper.
So every vertex carries an ink allowance instead, falling from 1 just below the jaw to 0 by the end of the stump, measured against the stump's own depth so it tracks the mesh rather than a magic number. The rasteriser interpolates it into the G-buffer alongside the normals, and the darkness is multiplied by it. Tone reaches the hatcher already faded, runs shorten and then stop, and the drawing ends where the head does.
05STAGE FOUR · HATCH
The hatcher is the piece that turns a continuous field into marks, and it is where a renderer stops being a renderer and starts being a draughtsman. It has one rule: one run20, one stroke.
Rows are laid out in their own coordinate frame — u along the row, v across it — so the row angle is a parameter rather than a special case, even though every figure here draws them horizontally. The canvas corners are projected into that frame to find how far the rows have to run. Each row then gets a jitter of up to 0.8px off its nominal line, a slope of up to 0.5px across its length, a random nib speed and a seed of its own.
Walking a row is a loop at a step of 12% of the row spacing — 2.4px at the default — fine enough that a run end lands within a nib width of the real boundary. At each step the darkness under the nib is sampled from the tone field, bilinearly21, because the field is 540px and the walk is in 1080px canvas coordinates.
The test is one line: if the tone is below the paper threshold — WHITE SPACE scaled into the top 70% of the tone range, so 0.091 at the default — the pen is off the page. Ink starts where the tone crosses that threshold and continues until it drops back below, and the entire run goes down as a single pen stroke — not a chain of dashes. Runs shorter than 3px are dropped. So every break in the finished drawing is somewhere the head got light enough to lift the nib: the eye socket, the front of the cheek, the gap under the jaw. None of them are arbitrary.
It is worth saying how few marks that is. At the defaults the whole head is about thirty strokes — a couple of dozen rows, most of them a single run from the profile edge to the back of the skull. There is no density of marks doing the work here. There is barely any drawing at all, in the sense of quantity. All of it is weight.
What this replaced matters, because the obvious approach is the wrong one. An earlier version chopped each row into jittered cells and inked a fraction of each, which is the usual recipe for a hand-drawn look and produces a convincing texture at a glance. But it puts a break wherever a cell happens to end, not where the head does, and those breaks read as noise laid on top of the form instead of as the form itself. Weight, not breakage, is what carries depth here — which is the next section.
the row pitch — 20px at 1080, about the density a fineliner hatches at.
the widest the nib gets, where the tone is solid. Push it past the spacing and rows fuse.
the darkness a row must reach before the pen touches down.
06WEIGHT ALONG THE MARK
This is the part that makes the drawing look drawn, and it is one line in the hatcher. The tone under the nib is not sampled once per stroke — it is sampled at every point along it, and handed to the pen as pressure22.
p = minPressure + (1 − minPressure) · tone^gamma, with a floor of 0.05 and a gamma of 1. That pressure travels with the stroke into the pen's own path stage, where it drives two separate things: the nib23's half width, via PRESSURE → WIDTH, and its flow24, via PRESSURE → FLOW. Darker tone means both a wider mark and a denser one.
Measuring a hatched drawing makes the case better than any argument. Slice across the bands and they run from about 3px to about 14px inside a single stroke, over and over. The nostril, the lip line and the eye socket are all far too small to be marks of their own at a 20px row pitch — they exist in the drawing only as the nib changing weight as it passes over them. That is most of what “detail” means in a hatched head, and it is why the tone is sampled per point rather than per stroke.
Getting the swing right meant sizing the nib for its peak rather than its average. Tone pressure and the pen's own width jitter compound, so a nominal 10px nib was drawing 18px bands in shadow and fusing one row into the next, wiping out the row pitch entirely. INK WEIGHT is now defined as the peak, and the pen's nominal width is backed out of it — nibWidth = weight / (1 + pressureWidth). Set the peak, read the peak off the render.
07HANDING OVER TO THE PEN
What crosses the boundary is a list of strokes: points with position, pressure and a timestamp, plus a seed and a smoothing flag. Nothing else. From here the drawing is the pen page's problem — every stroke becomes a mark25, every segment a quad, deposited26 as density27 into a float buffer and resolved into pixels once at the end.
Two details of that handoff matter. The smoothing flag is zero, because these paths are generated rather than sampled from a pointer and have no device jitter to remove. And the timestamps are synthetic: sampleAlong advances time by distance over a per-row nib speed, so the pen's speed-dependent terms — thinning, ink starvation — still have something meaningful to read.
The pen has no panel of its own in the head app. It is the calibrated fineliner with ten parameters overridden, and the reasons are all the same reason: a test sheet wants one weight end to end, and a head wants the weight to be the drawing.
| PARAMETER | FINELINER | HEAD | WHY |
|---|---|---|---|
nibWidth | 12.50 | weight / 1.85 | the slider sets the peak, not the nominal |
pressureWidth | 0.12 | 0.85 | tone has to be able to move the nib most of its range |
pressureFlow | 0.30 | 0.45 | darker tone should be denser as well as wider |
widthJitter | 0.22 | 0.10 | tone now supplies the variation within a mark |
endBlob | 0.12 | 0.02 | pooled ends fatten rows until they touch |
dwell | 0.45 | 0.10 | nothing here ever stops moving |
taperIn / taperOut | 2 / 3 | 6 / 9 | a run should die away at a tone boundary, not stop dead |
widthVar | 0.40 | INK VARIANCE | per-stroke variation is a control here, and off by default |
edgeAmt | 1.55 | EDGE EROSION | same parameter, exposed under a plainer name |
levels | 5 | TONE LEVELS | the five greys, unchanged |
Everything else — the capsule coverage28 model, canvas-anchored grain, the MAX blend within a stroke, Beer–Lambert29 absorption, the five-level tone step — is untouched. The head does not need a special pen. It needs the same pen, driven.
08MEASURING IT
Tuning a hatched head by eye stalls even faster than tuning a pen does, because there are two things to get wrong at once — the tone and the marks — and they compensate for each other. So both are measured.
The marks are the easy half: slice across the bands and count pixels. tools/tone-match.mjs handles the tone, and the useful idea in it is that it compares two drawings by tonal structure rather than by pixels. Ink coverage is resampled onto a coarse grid laid over each image's own ink bounds, so framing and scale drop out of the comparison entirely — two renders of the same head at different sizes score as the same drawing, which is what you want when the thing you changed was the light.
The grid being coarse is the part that is easy to get wrong. Make it fine enough to resolve individual rows and it stops scoring tone and starts scoring the phase of the hatch: nudge every row half a pitch and a perfect drawing suddenly scores terribly. Coarser than the row pitch, and the measure sees what the eye sees. The tool also prints both grids as ASCII beside the number, which is more useful than the number, because a single figure tells you a change was worse and the grids tell you where.
A drawing against itself is 0.00, so the number is only ever meaningful as a comparison — this render against the one before the change. That is the whole workflow: change one parameter, re-score, keep or revert.
| MEASURE | WHAT IT TELLS YOU | THIS PIPELINE |
|---|---|---|
| subject bounds | framing, independent of pose | 302 × 447px |
| row pitch | the hatch's own scale | 20px |
| blank scanlines | how much paper the drawing leaves | 23% |
| stroke weight | the swelling inside a mark | 3–15px, peak/floor 5.00 |
| weight change per column | how fast the nib is allowed to respond | 8.7% |
| run length | how often the pen lifts | median 182px, p90 247px |
| grey levels | the render pass's quantiser | 5 — 17 76 136 196 255 |
The greys are the five quarter-steps from section 00, give or take a rounding tie: mix(255, 17, ¾) is 76.5 exactly, so that level can land on either side depending on how the byte is rounded.
Stroke weight is the row to watch, because it is the one the eye is actually reading. The range and the peak-to-floor ratio are the swelling inside a mark, and a pipeline that got every other row on this table right while drawing at a constant width would measure well and look flat.
One number here cannot be fixed by any of the drawing controls, and it is worth knowing which. GNM's mean head measures 314px wide for this height; a narrower skull is a different shape, not a different hatch, and no amount of row pitch or ink weight will get you there. It is the clearest case on this page of a problem that has to be solved upstream — so it is, by resolving ten identity coefficients at bake time rather than shipping the mean head. Section 09 covers how.
09BAKING THE MESH
GNM ships as a 51MB .npz — 150MB of arrays, deflated — holding the identity and expression bases as well as the mesh, none of which has any business in a web bundle. tools/bake-head.mjs takes only what a rotating hatched head needs and writes 300KB.
| STEP | WHAT IT DOES |
|---|---|
| identity | evaluates the identity basis at ten fixed coefficients — head_000 to head_009 — and adds the result to the mean mesh and to the joints, so a longer jaw carries the joint it hangs from |
| select | keeps the skin and eye vertex groups; teeth and tongue sit behind the lips and never survive the depth test |
| reindex | remaps to a compact vertex list — 14,008 vertices, which fits a uint16 index with room to spare |
| quantise | positions as int16 about the head joint, scaled by the mesh's own extent: 6.0µm per step |
| skin | weights as one byte per joint, renormalised after rounding so the sum is exactly 255 |
| winding | checks face normals point away from the centroid and flips the index order if not, rather than assuming |
| write | 128-byte header — magic, counts, scale, joint positions and parents — then three packed arrays |
Resolving the identity here rather than shipping it is what keeps the asset the size it was. The alternative — ten basis shapes in the download so the browser could mix them — is about 840KB of vertex deltas to support a control nobody is turning. The cost is that changing the face means re-running the bake, which is the right trade for a drawing with one sitter.
Three things are computed at load rather than stored, because they are cheap and would otherwise be another thing that can go stale: area-weighted vertex normals, the neck ink fade, and the bounding sphere of the geometry that takes ink. None of them care which face arrived — they measure whatever mesh they are given, which is why a new identity re-frames itself with no other change. tools/npz.mjs reads the numpy archive directly, so the bake stays a plain node command with no Python involved.
GNM is copyright Google LLC and licensed under Apache 2.0. The licence, and a note recording exactly which version and variant this head was baked out of, sit beside the asset in public/head; the model itself is at github.com/google/GNM.
10WHAT IS NOT THERE
Identity at runtime, and expression at all. The face is fixed at bake time, so changing it is a command, not a slider; 09 has the arithmetic on why. Expression is out for a plainer reason — its basis is 82MB uncompressed, and a hatched portrait of a still sitter never asks for one.
Pose correctives. There is nothing to leave out — GNM's regressor is all zeros in the release this is baked from — and the joint limits are the workaround.
Cross-hatching. The row angle is already a parameter, and a second pass at a different angle over the same field would be a few lines. It is not in because it is not needed: one direction of rows plus weight already covers the tonal range a five-level quantiser can hold, and a second pass would be a different drawing rather than a better one.
Anything about the light being a light. No shadows, no falloff, no second source. A hatch resolves about five tones; spending complexity on a shading model that resolves more of them buys nothing that survives the quantiser.
11GLOSSARY
Every marked word on this page, numbered in the order it first appears. The last few belong to the pen rather than to the head, and are repeated here so the page reads on its own.
- 01GNM
- Generative aNthropometric Model — Google's parametric head model, and pronounced like genome. A mean head, a basis of identity and expression shapes that deform it, and a four-joint skeleton with skinning weights. Uncompressed the two bases are 54MB and 82MB, so what is shipped here is a single head evaluated out of them, not the machinery to evaluate more.
- 02Identity basis
- The set of shapes that turn a mean head into a particular one. Each is a displacement per vertex, and a face is a weighted sum of them — so an identity is a short list of numbers rather than a mesh. The weights are in standard deviations, ordered by how much variation each shape accounts for, which is why the first ten carry a recognisable face. There are 253 in all — 170 for the head, the rest for the teeth and the eyeballs.SEE ALSO GNM1
- 03Hatching
- Shading with ruled lines rather than with a wash: tone comes from how much of the paper the marks cover and how heavy they are. The whole problem of this page is turning a continuous tone into marks that read as that tone.SEE ALSO Tone field15 · Run20
- 04Posterisation
- Snapping a continuous range onto a small number of fixed steps. Doing it to opacity — at the very end, after everything else — is what puts a pen drawing's handful of greys on the page, and the speckled edge along with them.SEE ALSO Coverage28
- 05G-buffer
- A geometry buffer: the per-pixel facts about a surface — its distance, which way it faces, what it is made of — stored before any lighting is applied. Splitting the pipeline here means moving the light re-shades an existing buffer instead of re-rasterising the mesh.
- 06Linear blend skinning
- Deforming a mesh by a skeleton: each vertex is transformed by every joint that influences it and the results are averaged by weight. Cheap, and the standard way a character mesh follows a rig. It pinches at large angles, which is why the joints here have limits.
- 07Forward kinematics
- Walking a joint chain from the root outward, multiplying each joint's local rotation onto its parent's world transform. Turning the neck moves the head, the eyes and everything below them, because they are all downstream of it.SEE ALSO Linear blend skinning6
- 08Pose corrective
- A learned fix-up applied on top of skinning to repair the shapes it gets wrong — most obviously the collapse at the base of a turned neck. GNM reserves an array for one, but every coefficient in it is zero in the released head, so there is none to apply and the joints stop where plain skinning still holds up.SEE ALSO Linear blend skinning6
- 09Bounding sphere
- The smallest sphere containing a set of points, used here to place the camera: put it far enough back that the sphere fills the requested fraction of frame and the subject is framed at any field of view. Measured over the geometry that takes ink, not over the whole mesh.SEE ALSO Field of view10
- 10Field of view
- The angle the camera sees. A narrow one is a long lens: the head flattens, near and far features come out nearly the same size. A wide one exaggerates depth — the nose grows and the ears shrink. Changing it while keeping the subject framed means moving the camera in or out to match.SEE ALSO Bounding sphere9
- 11Backface culling
- Discarding triangles that face away from the camera, detected from the sign of their area once projected. It halves the work, and on a closed surface it throws away nothing that would have been visible.SEE ALSO Depth test13
- 12Barycentric coordinates
- A point inside a triangle written as a weighted blend of its three corners. The weights come out of the same cross products that test whether the pixel is inside at all, and they are what interpolates a normal, a depth or an ink allowance across the face.SEE ALSO Surface normal14
- 13Depth test
- Keeping, for each pixel, only the nearest surface written to it so far. It is what makes the far side of the skull disappear behind the near side without sorting the triangles first — and what makes the teeth and tongue not worth shipping.SEE ALSO Backface culling11 · G-buffer5
- 14Surface normal
- The direction a surface faces at a point, as a unit vector. Averaged from the faces around each vertex and interpolated across triangles, so a coarse mesh still shades smoothly. It is the only thing the lighting reads.
- 15Tone field
- The single-channel image of darkness that the hatcher draws from, 0 for bare paper and 1 for solid ink. Everything about pose, lens and light has collapsed into it by this point; nothing downstream knows there was ever a head.
- 16Lambertian shading
- Brightness proportional to how squarely a surface faces the light — the dot product of the normal and the light direction, clamped at zero. Physically the right model for a matte surface, and on its own the wrong one for a hatched drawing, because it leaves nothing to draw with over most of the head.
- 17Terminator
- The boundary between the lit and unlit sides of an object — where the surface turns exactly edge-on to the light. Under plain Lambertian shading it is a hard line; how much tone lives around it is what decides whether a hatch can describe form there.SEE ALSO Lambertian shading16 · Wrap lighting18
- 18Wrap lighting
- Letting light reach past the terminator by a fixed amount, then rescaling so full brightness still means full brightness. It is not physical, it is a cheat that stands in for the light bouncing around a real room — and it converts a two-region silhouette into a gradient a pen can follow.SEE ALSO Terminator17 · Lambertian shading16
- 19Facing ratio
- How squarely a surface faces the camera rather than the light — the z component of its normal in view space. Zero exactly at the silhouette, so a function of it is a way to darken only the last few degrees before an edge.SEE ALSO Surface normal14
- 20Run
- An unbroken stretch of a row where the tone stays above the paper threshold — and the unit the hatcher works in. One run becomes exactly one pen stroke, so every gap in the finished drawing is somewhere the head got light enough to lift the nib.SEE ALSO Hatching3 · Tone field15
- 21Bilinear sampling
- Reading a value between the pixels of an image by blending the four nearest. It is what lets a 540px tone field be sampled at 1080px canvas positions without the marks stepping from one field pixel to the next.SEE ALSO Tone field15
- 22Pressure
- The pen model's input for how hard the nib is being pressed, 0 to 1, carried per point along a stroke. On a tablet it comes from the stylus. Here the hatcher synthesises it from the tone under the nib, which is what turns a flat row into a mark that swells and thins.
- 23Nib
- The tip of the pen — the part touching the paper. Its width, and how much that width answers to pressure, is most of what separates a fineliner from a felt tip.SEE ALSO Flow24
- 24Flow
- How readily ink leaves the nib. Separate from width: pressing a ballpoint harder barely widens the line but pushes noticeably more ink out, so flow scales density rather than geometry.
- 25Mark
- The second stage of the pipeline: turning a path into geometry the GPU can rasterise. It decides where a stroke could possibly put ink, not how much — that is the next stage's job.SEE ALSO Deposit26
- 26Deposit
- The third stage: deciding how much pigment lands on each pixel. Paper texture, edge erosion and the pen's failure modes all apply here, and the answer accumulates as density.
- 27Density
- How much pigment is sitting on a patch of paper, added up across every stroke that crossed it. It is not an opacity and not a colour, and it is unbounded — eight passes really can be eight times the first. Only the render stage turns it into something visible.SEE ALSO Coverage28 · Beer–Lambert law29
- 28Coverage
- How much of a single pixel the mark covers, from 0 to 1. Read off the signed distance by a smooth ramp across the boundary; multiplied by flow, it becomes the density that gets deposited.SEE ALSO Density27
- 29Beer–Lambert law
- Light is absorbed exponentially with the amount of material it passes through. Applied to ink it means opacity approaches 1 without ever reaching it, so the first pass does nearly all the darkening and later ones add almost nothing — a saturation ceiling that falls out of the maths rather than being clamped in.SEE ALSO Density27