The ScentArt renderers, and why we kept replacing them
ScentArt turns a perfume's composition into a picture. We have written eleven renderers to do that since March 2026, and four of them have been replaced. This is what each one did, what it got right, and what made us start again - taken from the code that is still in the repository rather than from memory.
In this guide
- March 2026: drawn on the server
- March 2026: dots on a flow field
- March 2026: the fluid simulation
- June 2026: the pattern engine
- June 2026: the fractal flame
- August 2026: the material renderer
- September 2026: Liquid v2
- 2026: the four that were finished and shelved
- The research underneath
- What made replacing them cheap
- See them for yourself
One thing has been constant. Every renderer draws from the same per-note data - the notes we hold for a perfume, their weights, and the 34 facets behind each one. None of them is a picture a model imagined for a smell. What changed each time is how that data becomes an image.
March 2026: drawn on the server
The first version was not in your browser at all. A Node script rendered a fixed-size image with a 2D canvas library and the page showed the finished file. It still lives in the repository as the offline render workflow.
Rendering ahead of time has one large advantage, which we have since had to relearn: a finished image costs the person looking at it nothing. Its problem was the opposite of a performance problem. A baked image is one fixed size, so it softened on a large display, and every change to the artwork meant re-rendering the whole catalogue. The external render service it depended on went offline in May 2026.
March 2026: dots on a flow field
The first thing that ran in a browser. Every note became a dot travelling a path through a flow field, trailing colour behind it, and its speed was its volatility: top notes darted across and were gone, base notes crawled. Watching it, you were watching the perfume evaporate.
It was two files - a path-and-palette algorithm written in ES5 so that the same code could run in the browser and on the server through Jint, and a p5.js wrapper that animated the result. It was deleted in June 2026 when the fluid simulation replaced it, and it was the only renderer we ever lost. We brought it back in September 2026 from the commit that removed it; it is in the dropdown as Origin, with the algorithm untouched and the p5 dependency dropped for plain Canvas2D.
It has the same flaw as everything animated that came after it, which is the flaw that drove the whole sequence below: there is no single frame to show.
March 2026: the fluid simulation
The first browser renderer was a WebGL2 fluid simulation - a Navier-Stokes solver based on Pavel Dobryakov's open-source WebGL-Fluid-Simulation, used under its MIT licence and extended with visible particles, splats driven by a note's phase, and profile-mapped bloom and tone mapping.
It is genuinely beautiful and it moves, which is why it is still on the site as Liquid. It is also the reason we needed more than one renderer: a fluid simulation is a living thing, not a picture. There is no single frame to show on a product page, and no still to put in a search result or a share card. It answers "what does this feel like" rather than "what is in this".
June 2026: the pattern engine
Next came a single-frame shader we called the incense-glass engine, and which the full-screen page now labels Classic. It builds an image from layered value noise warped by the perfume's olfactory axes, with a seed and a per-perfume "DNA" making every render repeatable - the same perfume always draws the same picture.
This was the first renderer that produced a still, deterministic image cheaply enough to put on every perfume page, and it carried the site for nearly three months. Its limitation is that it is a pattern rather than a substance: it reads a handful of summary axes, so two perfumes that are broadly woody and sweet look broadly alike, however different their materials are.
For roughly two and a half months this engine drew the wrong axes. A refactor in June cut the axis list from ten names to eight and left the shader reading the old positions, so "fresh" was driven by floral, "green" by fruity, and two axes were fed nothing at all. Nobody could see it, because the output is abstract and there was nothing to compare it against. It was found and fixed in August 2026 when the axes were realigned. It is a good argument for pictures that are legibly made of something.
June 2026: the fractal flame
Three days after the pattern engine we built a fractal-flame renderer - the technique Scott Draves published as flam3, running as a chaos game on the GPU with each particle carrying its own orbit through an iterated function system.
Flames make superb images. They are also slow to converge, because quality is a function of how many samples you accumulate, and they are hard to steer: small changes to the transforms move the picture a long way, which is the wrong property when the picture is supposed to describe one specific perfume. It stayed as its own Flame page rather than becoming the default.
August 2026: the material renderer
The renderer running today is a WebGL2 element-field engine. Rather than mapping a perfume onto a pattern, it resolves the composition into 34 facets - citrus, aldehydic, amber-resinous, animalic and so on, one for each facet we hold - and gives each one a scanned material with its own optical behaviour. A frame amalgamates the strongest few.
The point of the change is legibility. A resinous perfume and a green one no longer differ only in hue: they are made of visibly different stuff. Because each facet carries a real scanned surface, the image says what it is made of in a way a noise field cannot.
It is also the most expensive renderer we have shipped, which is a trade we made deliberately and then had to pay for. Compiling its shader and drawing one frame took several seconds of a browser's main thread, which froze the page while it happened. In September 2026 we moved the whole render onto a worker thread, so the work still takes as long but no longer stops anything. The page stays responsive while the art appears.
September 2026: Liquid v2
The fluid is Liquid v1's, kept intact - the swirls are what made it beautiful and nothing about them changed. What changed is what drives them. A perfume is sprayed and worn in front of you: a burst leaves the atomiser in a cone and lands on the skin along the bottom of the frame, and for the rest of the wear its notes lift off that skin into the fluid.
The first fluid simulation gave every note in a phase the same hand-drawn curve on a 30-second loop. The spray gives each note its own staying power - the same presence model the drydown timeline on the perfume page draws, on the same axis - so the fluid at two hours holds what the timeline says is there at two hours. Volatile notes are pushed upward and are gone within the hour; tenacious ones settle back toward the skin and are what is left at the end. Notes blend more as the wear goes on. The 34 facets decide how each note's dye goes in: how hard it swirls, how wide it spreads, how sharp its edge, how long a streak it draws. The strength of the currents is the projection, the density of the dye the intensity, the length of the wear the longevity.
It also honours the same contract as the still renderers, which the first fluid engine never did: its still is the fluid at the composition's loudest moment, run from the spray with a fixed step, so it can be rendered off the main thread, in a thumbnail, or anywhere a single frame is asked for, as well as animated.
2026: the four that were finished and shelved
Four more were written and finished - each with its own mapper, a still render and an off-thread entry - and then never added to the picker. They sat in the repository, complete and unreachable, until September 2026. They are in the dropdown now, marked as studies, because a renderer nobody can open is not a decision anyone can argue with.
They are the cheap ones. All four are Canvas2D or CPU-side rather than WebGL, so there is no shader to compile, nothing to contend for the GPU, and they still draw on a machine whose browser has fallen back to a software driver.
- Pigment - ink through water, recorded where it settled rather than animated. Dense beds where a note is heavy, bare paper where nothing reached.
- Chromatography - the perfume run up a strip of paper the way a lab separates one. Each note climbs as far as its own volatility carries it, so the picture is the separation.
- Facet - flat regions of literal colour meeting at a clean edge, one per olfactory facet. No shading and no simulation: the areas are the proportions.
- Morphogenesis - reaction-diffusion, the chemistry that patterns a shell. Each note is a species competing for the same space.
The research underneath
Two pieces of work sit under all eleven of them, and they are worth reading before judging any single renderer.
Why we do not let AI draw ours is the reason there is a renderer at all rather than an image model. It also carries the finding that made colour the first thing every one of these engines decides: in the study we read, colour did more of the work than shape or texture.
What colour is a note is where the palette comes from - how we decided that bergamot is that yellow-green and oakmoss is that grey-green, and where we disagree with the convention. Change that page's conclusions and every renderer above changes with it.
What made replacing them cheap
Eleven renderers in seven months only worked because of one decision: every engine implements the same small contract, and the pages know nothing about which one they are driving. A page asks for "the engine" and gets whichever module is named in a single file. Swapping renderers is a one-line change, and the previous ones keep working because nothing about them was special-cased.
That is also why the old renderers are still here rather than deleted. They cost nothing to keep, and being able to put two of them side by side is the only honest way to judge whether a new one is actually better.
See them for yourself
Open any perfume's ScentArt full screen and use the renderer dropdown in the top bar. It opens on the current renderer, with every other one underneath it - Liquid v2, Liquid v1, Flame, Origin, the four studies and Classic - all drawing the same perfume from the same data. Switching does not reload the page: the screen stays put and only the art in the frame changes. The same dropdown, with the same entries, sits on the two-perfume comparison screen.
Every perfume has one - append /scentart to any perfume URL.
For what ScentArt is and how the current renderer reads a perfume, start with the ScentArt guide.