Modelling a real product from photographs
Rebuilding an Exocet Breeze V2 145 windsurf board from four product images and a spec table. The finished hull measures 308 × 65 cm and displaces exactly 145 litres. Getting there took eleven rebuilds, and every one of the detours is a repeatable lesson — about tracing, about colour management, and about the difference between what you render and what the operator is looking at.
The exercise
Given: a deck photo, a bottom photo, two three-quarter shots, and a published spec — 308 cm long, 65 cm wide, 145 L, US box + 2 slot boxes, step tail. Wanted: a parametric model that matches, with the geometry driven by data extracted from the images rather than by eye.
The first attempt was built from a table of sixteen plausible-looking numbers typed by hand. It looked like a lozenge. That is the whole problem in miniature: a careful process applied to invented data produces confident nonsense.
Getting the shape
Use the alpha channel, not a brightness threshold
Cost: an outline with a 2.13 aspect ratio instead of 4.74
Product shots come with a drop shadow. Threshold on brightness and the shadow joins the silhouette, stretching the bounding box and corrupting every derived measurement. These WebP files carried a clean alpha channel all along — and the conversion to PNG had discarded it.
alpha.min(), alpha.max(), and what fraction exceeds the threshold.Verify you have the right curve before you trust the tracing
Cost: a complete rebuild
A supplied SVG contained four large paths. They were assumed to be the board outlines and traced meticulously at 64 stations. They were something else entirely — the resulting tail was 0.694 of maximum width against the real 0.238. Careful tracing of the wrong data.
Cluster your samples where the curvature is
Both ends came out as knife points
Uniform station spacing gives the nose and tail two or three slices each — exactly where the outline turns hardest. The tips render as straight tapers to a point instead of rounded ends.
t = 0.5(1 - cos(πi/N)). Dense at both ends, sparse in the middle, same total. Add explicit tip rounding over the last 1.5% of length.Colour, three ways wrong
Sampling the reference gave pink rgb(212, 82, 169) and blue rgb(1, 120, 205). Putting those numbers into Blender produced a washed-out pastel. Three separate faults, stacked:
c ≤ 0.04045 ? c/12.92 : ((c+0.055)/1.055)^2.4 — took the mean RGB distance from 113 to about 48. The AgX view transform then desaturates on top of that, so match colour under Standard or you are chasing two errors at once.| Fault | Symptom | Fix |
|---|---|---|
| sRGB fed as linear | Everything too bright and desaturated. sRGB 120 became linear 0.470 instead of 0.187 — two and a half times too bright. | Convert before assigning: c ≤ 0.04045 ? c/12.92 : ((c+0.055)/1.055)2.4 |
| AgX view transform | AgX deliberately desaturates highlights. Excellent for photographic renders, wrong for matching a product colour. | Use Standard when the job is colour accuracy. |
| Blown by lighting | Even with correct values, an over-bright key pushes the surface past its own albedo. | Measure the render against the reference at matched stations. Blue went from a mean RGB distance of 113 to 48. |
Measure the match, don't judge it
Sample both images at the same fractions along the object, mask out pads and text, and print the RGB triples side by side with the distance. “Looks about right” hid a 2.5× brightness error for three rounds.
Are we even looking at the same thing?
Viewport visibility and render visibility are separate
Two rounds chasing a fault that did not exist
A previous board was left in the file, hidden from renders only. It sat inside the new hull and showed through as a large white shape — in the viewport. Renders were clean, so the fault was diagnosed from a render as a specular blowout, and time was spent adjusting roughness and lighting that changed nothing the operator could see.
Material Preview ignores your scene
Blender's Material Preview lights with a built-in HDRI, not the scene's lights and world. Every lighting change made through a script is invisible there until Scene Lights and Scene World are enabled.
space.shading.use_scene_lights and use_scene_world on every 3D viewport before debugging any lighting problem.Compare like for like
A perspective render was compared against a perspective photograph and the mismatch attributed to the model. Two different distortions cannot be differenced meaningfully.
Booleans and boundaries
A boundary can only be as crisp as the mesh underneath it
The step tail is an arc crossing the hull at 68.4 cm from the tail on the centreline and 79.5 cm at the rails. Implemented as a height offset inside the surface generator, the edge can only land on existing slices — so it stair-steps at the station spacing.
Watertight is necessary, not sufficient
A boolean that reduced a 12,580-triangle hull to 22 triangles
The cutter was verified watertight — zero non-manifold edges, positive signed volume of 213 L. It still destroyed the model, because it extended past the tail where the board is only 16 mm thick, cutting through the entire section rather than shaving the underside.
Numbers that check themselves
The most valuable habit in the whole exercise: build measurements that can only agree if they are right.
| Check | What it caught |
|---|---|
| Two views against the spec. Deck aspect 4.818, bottom 4.596, published 4.738. | Confirmed the silhouette trace before anything was built on it. |
| Cross-file consistency. Outline file gave L/W; profile file gave L/H. Together they implied 11.6 cm thickness against a published 11 cm. | Proved two separately-drawn references were to the same scale. |
| Volume by bisection. Thickness solved so the hull displaces exactly 145.0 L. | Turns a guessed dimension into a derived one — and re-solves automatically whenever the shape changes. |
| Feature sanity. The mast track traced to 41.3 cm; a real one is ~40 cm. | An independent confirmation that the extraction pipeline was sound. |
| Independent re-trace. The step arc found by edge detection, then again by colour-region boundary. | Two methods, same answer — 0.222 / 0.258. |
The recipe
How to model a real object from photographs
1. Get the alpha, not a threshold. 2. Trace two independent views and require them to agree before building. 3. Check both against any published spec. 4. Cluster samples where curvature is highest. 5. Convert sRGB to linear, render on Standard, and measure the colour match numerically. 6. Solve one dimension from a known quantity so the model self-corrects. 7. Compare orthographic to orthographic. 8. Before debugging anything visual, confirm you and the operator are looking at the same picture.
Working rules
The technique above is worth little without the discipline around it. These four came out of the same evening and cost more than the modelling did.
| # | Rule | What it would have prevented |
|---|---|---|
| 1 | No measurement is used until a second, independent one agrees with it. | Meticulous tracing of the wrong SVG paths — a tail 0.694 wide against a real 0.238. |
| 2 | Related features are measured together, in one frame, and checked against each other. | Fin boxes at t 0.206–0.233 and a step arc at t 0.222–0.258 — measured separately, so the step now cuts through the boxes. |
| 3 | Render what the operator is looking at before reporting. Same shading mode, same visibility flags, orthographic for silhouettes. | Diagnosing a leftover white object as a specular highlight, from a render that was clean because that object was hidden from renders only. |
| 4 | Two failed attempts on the same problem → stop and re-measure. | Four successive fixes each computed from the previous one’s damage. |
And the honest one underneath
The feedback loop was self-referential: renders from cameras I placed, with visibility I set, judged against my own intent. The operator was the only real signal in the circuit, and was left doing the work of noticing. Verification costs a turn and produces nothing visible — which is exactly why it keeps losing to building, and exactly why it has to be a rule rather than a preference.
The tool we should have started with
Shape3D (and AKU Shaper) are what board shapers actually use. They take precisely the three things extracted here — outline, rocker, thickness — as editable curves, produce a proper lofted surface, and compute volume live as you edit. Features like fin boxes and step tails are first-class objects rather than offsets inside a mesh generator.
The extraction work on this page is not wasted: those curves are the input format such a tool wants. The lesson is about sequencing — check whether a domain-specific tool exists before writing a general-purpose one. A parametric mesh generator in Python was a reasonable idea and a poor choice.
Written 2026-08-16 after the Breeze V2 build. Companion pages: failure log · 30-day programme · the plan