Mol* illustrative parity
Reference for megane's illustrative representation and color mode, and where
they do and do not match Mol*. Every value below was read out of molstar
5.11.0 (npm view molstar), not from memory; the palette and the lightened
carbon colors were produced by running Mol*'s own mol-util/color code.
What "the Mol* illustrative look" actually is
It is not one setting. Mol*'s Illustrative appearance is three things applied together, and the recognisable Goodsell look needs all three:
| Piece | Where it lives in Mol* | megane |
|---|---|---|
| spacefill at full vdW radius | illustrative representation preset | ✅ representation: "illustrative" |
ignoreLight (flat, unlit fill) | same preset | ✅ same mode |
illustrative color theme | same preset | ✅ color: "illustrative" |
| occlusion (SSAO) | Illustrative quick style → canvas3d.postprocessing | ⚠️ approximated in-shader |
| outline (screen-space, depth-based) | same quick style | ⚠️ approximated in-shader |
In the Mol* UI these are two separate buttons: Apply Representation →
Spacefill applies the illustrative preset, and Apply Style → Illustrative
turns on the postprocessing. Note the Spacefill button applies the preset named
illustrative, not a plain spacefill preset.
The color theme (megane matches this exactly)
mol-theme/color/illustrative.ts reduces to two lines:
const baseColor = styleColor(location, false);
return typeSymbol === "C" ? Color.lighten(baseColor, props.carbonLightness) : baseColor;
Every atom takes the entity color. Carbon is only a lightened shade of that
same color. Non-carbon atoms do not get CPK element colors — that is Mol*'s
separate element-symbol theme. This is what makes each chain read as one flat
body rather than as red/blue confetti.
| Parameter | Mol* value |
|---|---|
default style | entity-id (the preset passes { overrideWater: true }) |
carbonLightness | 0.8, range −6…6 |
| palette | many-distinct, indexed serial % 25 — megane deviates, see below |
| unresolved entity | 0xFAFAFA |
water (overrideWater) | 0xFF0D0D |
| theme fallback (non-atomic location) | 0xEEEEEE |
Color.lighten
Color.lighten(c, amount) is Lab.darken(c, -amount), which is:
L* += Kn * amount (Kn = 18) a*, b* unchanged
So carbonLightness: 0.8 means L* + 14.4 in CIE Lab (D65 white point,
sRGB companding, chroma.js-derived breakpoints). It preserves hue and
saturation, unlike a blend toward white, which washes the color out. megane
ports this in src/colorSchemes.ts (lightenLab) including the 8-bit rounding,
so its output is bit-identical — tests/ts/colorSchemes.test.ts asserts all 25
palette entries against values generated by Mol* itself.
The palette — a deliberate deviation
Mol*'s default is its many-distinct list (Dark2 + Set1 + Set2, 25 entries,
cycled i % 25):
dark-2 1b9e77 d95f02 7570b3 e7298a 66a61e e6ab02 a6761d 666666
set-1 e41a1c 377eb8 4daf4a 984ea3 ff7f00 ffff33 a65628 f781bf 999999
set-2 66c2a5 fc8d62 8da0cb e78ac3 a6d854 ffd92f e5c494 b3b3b3
megane does not use it. It softens its own CHAIN_COLORS instead, so the
mode stays recognisably megane and keeps chain identity consistent with the
byChain color mode. The transform runs in CIE LCh, which preserves hue
exactly:
L* → L* + (92 - L*) * 0.55 C* → C* * 0.5
Mol*'s water color is put through the same transform: raw #ff0d0d is fully
saturated, and against a pastel palette it reads as a wall of red that swallows
the solute in a solvated system.
Everything else about the theme — the rule, the lightening math, the serial
indexing, the fallback color — follows Mol* exactly. lightenLab is asserted
bit-for-bit against values generated by Mol*'s own code in
tests/ts/colorSchemes.test.ts.
The postprocessing (megane approximates this)
The Illustrative quick style sets, verbatim:
outline: { scale: 1, color: 0x000000, threshold: 0.33, includeTransparent: true }
occlusion: { multiScale: off, radius: 5, bias: 0.8, blurKernelSize: 15,
blurDepthBias: 0.5, samples: 32, resolutionScale: 1,
color: 0x000000, transparentThreshold: 0.4 }
shadow: off
Mol*'s outline (mol-gl/shader/outlines.frag.ts) walks the 3×3 screen
neighbourhood of each pixel and marks it as outline when
abs(selfViewZ - sampleViewZ) > pixelSize * threshold && selfDepth > sampleDepth
Two consequences megane does not currently reproduce:
- the line is drawn on the farther pixel, so it hugs the outside of the nearer object rather than sitting on the object's own edge;
- a depth step below the threshold draws nothing, so where two spheres interpenetrate at similar depth there is no line. Outer silhouettes are strong and internal seams are weak — the look has a hierarchy.
megane has no post-processing stack (renderer.render(scene, camera) goes
straight to the canvas), so both effects are approximated inside the impostor's
own fragment shader:
- outline — a constant-width band at each sphere's own silhouette, sized
from
fwidth(). Uniform across every sphere, so it has no hierarchy and it draws every sphere-sphere intersection curve. - occlusion — a radial ramp toward that same silhouette. In a spacefill field the dominant occlusion term is contact with neighbouring spheres, which always sits near a sphere's rim, so this recovers most of the sculpted look. It cannot darken a large pocket the way a real SSAO pass does.
Both need no extra render target, so the mode behaves identically in the
offscreen render/capture path and on every host. Replacing them with a real
depth prepass + SSAO + outline pass is tracked separately; the impostors write
gl_FragDepth, so such a pass needs its own depth/normal variants of the atom
and bond shaders — a stock three.js SSAOPass would render the flat billboards
and produce garbage.
Other known deviations
- Entities. megane parses no
label_entity_id, so chain ID stands in for it. For most PDB entries the two coincide. - Water detection. Mol* keys
overrideWateroff the entity's water type; megane matches residue names (HOH,WAT,SOL,TIP*, …) from atom labels, and skips the override when a structure carries no labels. - vdW radii. megane's
VDW_RADIIdiffers from Mol*'sElementVdwRadiifor 75 of the 92 elements both define — notably H (1.20 vs 1.10) and most metals. The organic set (C, N, O, S, P, Si, halogens) agrees. megane's table is shared with ball-and-stick sizing and distance-based bond inference, so aligning it is a much wider change than this mode. - Single-entity structures. With one chain (most non-PDB formats) the theme
is near-monochrome by construction — that is what Mol* does too. Use the
byElementcolor mode for CPK coloring of materials.