Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cymatics

Cymatics

Stylized concept art direction for Blender: an NPR surface shader, a fourteen stage compositing chain, a sky that places its own key light, and a camera set in photographic terms.

English  ·  Français

License GPL-3.0-or-later Blender 5.0+ CI Latest release


Cymatics is a Blender add-on for stylized concept art. It does not replace your textures. It replaces the way light reads on them, then rebuilds the image on top.

Four stages, and it is the combination that makes the render:

Stage What it decides Where it acts
Sky where the light comes from, what sits behind the subject world, plus a matching sun lamp
Surface shader how light reads on the volume per material
Image grade what the finished image looks like the whole render
Camera what is in frame, and how much of it is sharp the scene camera

Interface in English and French, switchable from the panel header. Blender 5.0 or newer. Developed and tested against Blender 5.2 LTS.


Looks

Raw EEVEE render Jade Dusk look
Left: the raw render. Right: the same frame with the Jade Dusk look.

Ink Wash look Storm Iron look
Ink Wash, with outline, flat bands and canvas. Storm Iron, with fog, film grain and streak glare.


Install

Blender, Edit > Preferences > Get Extensions > Install from Disk, then pick the zip from the releases page.

From a clone, python build.py writes the same zip into dist/.

Panel: 3D view, N sidebar, Cymatics tab.


Start here

  1. Select the objects.
  2. Click Do Everything, the large button at the top of the panel.

It chains: shader node group, sky and its matching sun lamp, the shader on the materials in scope keeping their textures, then the image grade.

Base Color, Normal, Roughness and Alpha are reconnected as they were. The original Principled is kept, only unplugged from the output, so Restore puts it back.

Two settings are worth doing next, and nothing else is urgent:

  • Light Gain, in Setup. The shader reads the real intensity of your lamps, which has no upper bound. Around 2 for a sun, around 9 for weak point lights. Everything flat and bright means lower it, everything in shadow means raise it.
  • Fit to Scene, in Image Grade. It measures the visible geometry from the camera and sets the mist range and the outline depth range from it. Without that calibration the mist pass reads zero or saturated, and the fog does nothing at all.

The image grade, in order

Fourteen stages, in the order the image goes through them. That is also the order of the sub-panels: what reads top to bottom is what the image goes through.

Stage What it does
Input exposure, black point, white point, gamma, white balance
Paint Filter anisotropic Kuwahara, the core of the painted look
Flat Bands value quantization, cel shading done on the image
Outline silhouettes from the depth pass, interior creases from the normal pass
Atmosphere aerial perspective from the mist pass
Depth Blur blur masked by distance from the focus plane
Color Grade shadows, midtones, highlights, plus lift, gain and gamma
Palette Remap luminance picks the color from an editable ramp
Glare bloom, fog glow, streaks, ghosts, star
Lens chromatic aberration and distortion
Vignette tintable, with adjustable shape
Film Grain animatable
Canvas paper weave overlay
Sharpen unsharp mask

Three build rules

Everything is always built, even when off. A stage's switch is a mix factor between the image before and the image after, not a rebuild. A stage left at zero comes back without breaking anything.

Every setting lives in a named Value node, grouped in a frame per stage at the top of the group. The panel writes into it: moving a slider changes the image without rebuilding the graph.

Every step exposes a diagnostic tap. The Show Stage menu sends any intermediate step to the group output, masks included: line mask, fog factor, shadow and highlight masks. It is the shortest path to understanding a setting that appears to do nothing.

Solo and reset

Each sub-panel carries two buttons in its header. Solo switches every other stage off, the way a mixing desk does: tuning one stage blind in the middle of thirteen others does not work. A second click brings them all back. Reset only touches that stage.

Palette remap

Palette remap
Luminance drives a color ramp. Duotone, risograph, limited palette.

Six starting palettes ship with it, and the ramp itself is editable directly in the panel.

Masks

Fog and blur on the background only Fog and blur on the subject only
The same fog and depth blur, restricted to the background, then to the subject.

Any stage can be restricted to one subject. Four mask sources live in the same graph, chosen by an index so switching is immediate: cryptomatte by object name, cryptomatte by material name, object pass index, material pass index. The mask can be inverted and its edge softened.


Sky and key light

Sky projected on the ground Night sky with moon and stars
Left: a ground that samples the same sky, so the horizon closes instead of leaving a seam. Right: night, with moon, stars and the Milky Way.

The sky lives in a shared node group rather than in the world, which is what lets anything else read it.

  • Background: three zone gradient with an adjustable horizon height, or Blender's physical sky
  • Sun and moon, each with its own direction, a disc, a glow, and a phase for the moon
  • Stars placed by Voronoi with varying brightness, horizon fade, and a Milky Way band
  • Clouds as a fractal noise band at an adjustable height
  • Matching lamp: a sun aligned on whichever body is lit, created and updated with the sky

That last point is what ties the sky to the rest. The surface shader needs a dominant key to show anything at all: a sky that does not place its lamp leaves the render flat, and the shader gets the blame.

Sun from View places the light where you are looking from. Turn the view until you see what you want lit, then click.

Project on Ground samples the sky on selected geometry, taking the direction from the eye point to the shaded point. With nothing selected, a plane is created.


Engine modes

An engine mode sets all four stages at once, because none of them is enough on its own.

The World of Warcraft mode follows the client's published equation:

light  = saturate(saturate(dot(-lightDir, N)) * diffuse + ambient)
color  = texture * light
result = mix(color, fogColor, fogFactor)

Three consequences, and they are what makes the look:

  • ambient light is additive, not multiplicative, so shadows sit on a colored floor and never reach black. This is the first thing missing when people try to imitate the game.
  • there is no specular in the model pipeline. Any added shine breaks the resemblance immediately.
  • fog is linear in distance and goes toward the sky color, which closes the set in and saturates the horizon.

The detail comes from the hand-painted texture, not from the shader, so the paint layer is switched off in these modes.

Modes: wow_ingame, wow_night, wow_cinematic.

Sources: wowdev.wiki/DB/Light and the Wrath shading notes from the Wowser project.


Camera

A focal length and an aperture say more than a field of view: 85 mm at f/2 is a portrait, 24 mm at f/8 is a wide shot of a set. The presets are written in those terms and set the lens, sensor, aperture, focus and output format in one go.

portrait_85, hero_50, story_35, environment_24, vista_18, compression_135, cinema_40, game_third_person, isometric.

Frame Selection moves the camera and keeps the lens. Backing off rather than zooming is what keeps the perspective, and therefore the shot.

Nothing is stored twice: the values live on Blender's camera, where Blender expects them. The panel shows them directly.


The surface shader

  1. The diffuse term goes through a Shader to RGB, normalized with a Reinhard curve, then thresholded. That gives a controllable light and shadow mask.
  2. The lit zone is multiplied by Light Tint. The shadow zone is darkened along a curve then takes hue and saturation from Shadow Tint in COLOR mode. A plain multiply would neutralize the color, COLOR mode moves it while keeping the albedo's value structure.
  3. Two hemispheric ambient terms: Sky Tint on upward faces, Bounce Color on downward faces.
  4. Fresnel rim, thresholded specular gated by the light mask, cavity darkening, shadow hatching, procedural hue and value breakup.
  5. Emission added after the sum, then mixed with a transparent shader according to Alpha.

The settings that carry the identity

Light Gain first, see above. Then Light Balance places the terminator, and Hue Split sets how far shadow and light diverge in hue. That last one is what carries the identity.

Light Wrap lets light spill past the geometric terminator without moving the threshold, which softens a volume without having to recalibrate Light Balance.

Rim Backlight concentrates the rim on the shadow side. At zero it wraps the whole silhouette and reads as a cutout, at one it only appears against the light.

Painted by hand

Paint Amount doses the whole thing. Edge Wobble deforms the shadow and light boundary with isotropic noise: that is what breaks the too-clean terminator of a 3D render, and it is most of the gain. Stroke Stretch stretches a second noise along one axis to lay a directional value weave on the flats, which reads as brush direction.

Paint Scale sets the size of the strokes, and it is the one to understand first: a high value gives fine strokes. The noise is in object space, so apparent size depends on mesh scale. On a sphere of radius 1, scale 7 gives a readable painted edge, scale 55 is invisible, scale 20 turns to grain. On an object ten times larger, divide the scale by ten.

Hatch Amount lays hatching in the shadows, orientable with Hatch Angle. It is confined to the shadow side of the terminator: hatching that spills onto the light reads as a surface texture rather than as drawn shading.

A limit worth knowing

The shader is a system of response to light. It needs a dominant key to show anything. A rig made of several point lights of similar power gives omnidirectional fill: no terminator, therefore no warm and cool split. The shader cannot invent a light direction the scene does not have.


Scripting

import bpy
from cymatics import camera, grade, shader, sky

shader.build_group()
shader.stylize_material(bpy.data.materials["Material"], "ember_forge", "metal")
shader.set_preset("ember_forge")             # push onto the whole file

sky.build_sky("golden_hour")
sky.set_sky("sun.intensity", 20.0)
sky.sync_sun_lamp()

grade.set_mist_range(9.0, 30.0)
grade.build_grade("ember_forge")
grade.set_control("outline.strength", 1.0)
grade.set_control("grade.saturation", 1.35)
grade.get_controls()                          # full state, by path
grade.set_output("CYM_Tap_paint")             # look at one step
grade.set_output(None)                        # back to the finished image

camera.apply_camera("cinema_40")
camera.frame_selection(margin=1.2)

Setting paths read stage.setting. The full list is in cymatics/params.py, or in the return of get_controls().


How the code is laid out

cymatics/
    params.py       declarative table of settings, the single source of truth
    shader.py       surface node group, palettes, materials, test bench
    grade.py        compositing graph construction
    sky.py          sky, celestial bodies, matching lamp, ground projection
    camera.py       shot setup in photographic terms
    engines.py      modes that imitate a game engine's render
    properties.py   scene properties, generated from the table
    operators.py    actions, automatic calibrations, presets on disk
    panels.py       interface, sub-panels generated from the table
    i18n.py         locales and the bridge to Blender's translations
    locales/        en.json, fr.json

params.py carries the settings table. Three modules read it: grade.py builds the graph, properties.py generates the scene properties by introspection, panels.py draws the sliders in table order.

Adding a setting is therefore one line in the table plus its wiring in grade.py. Neither the properties, nor the panel, nor the graph can drift apart, because none of the three keeps a list of its own.

Writing for several Blender versions

The compositor changed node names and socket shapes across the 5.x series. Everything therefore goes through tolerant access: _new() tries several types and returns None, _seti() writes into a socket or falls back to a property, _link() returns False when the socket does not exist. A missing node lets its stage pass the image through instead of failing the whole build.

Three concrete examples, all met on 5.2: CompositorNodeGamma is gone in favor of ShaderNodeGamma, whose socket is called Color and not Image. CompositorNodeTexture no longer exists, so the noise now comes from ShaderNodeTexNoise placed in image coordinates. And the Filter node lost its sharpen mode, hence the unsharp mask built by hand.


Languages

No visible string is hardcoded. Everything goes through t(key), resolved from locales/en.json and locales/fr.json.

Two translation paths coexist, and that is deliberate. t() serves the labels the add-on draws and follows the language picked in the panel. bpy.app.translations serves tooltips, which Blender freezes when classes register: the dictionary is built from the same JSON files, so there is only one source.

Adding a language means dropping an xx.json into locales/. Nothing in the code changes.


Tests

blender --background --factory-startup --python tests/test_headless.py
blender --background --factory-startup --python tests/test_sky.py
blender --background --factory-startup --python tests/test_engine.py
blender --background --factory-startup --python tests/test_i18n.py

Each suite exits non zero on the first failure, so they double as CI steps. CI runs them against Blender 5.0 and 5.2.


License

GPL-3.0-or-later. See LICENSE.

About

Stylized concept art direction for Blender: NPR surface shader, 14 stage compositing chain, procedural sky with matching key light, and photographic camera presets

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages