Stylize
Hand-made looks for the layer inside: a watercolor wash, a chalk drawing on a board, ASCII
characters, and copper-plate line engraving. Each look is a recipe of two or three words. A
stage is one piece of the look that reads the layer once it is rendered to a texture (a
wobbled sample position, a brush, a ring of edge samples, a character grid). Build a stage once
and hand the same one to every word that needs it, so they agree. The closing word (wash,
chalkSketch, linework) returns the effect for effect:. The ASCII look closes with
glyphTint, which returns a fragment for gpu.fragment because it also needs a character
atlas drawn on the CPU.
The output is straight alpha. Watercolor, chalk and ASCII keep the alpha of the layer inside;
engraving replaces the layer, so its definition sets blendWithChildren: false.
import {effects} from 'shaders/std'
Reach for it when
| When you need | Use |
|---|---|
| a painted, softened watercolor version of the layer | wash with kuwahara, bleedWarp, paperGrain |
| paint that bleeds past hard edges | bleedWarp, shared as the sample position by kuwahara and wash |
| a chalk drawing with outlines and cross-hatching | chalkSketch with sobelTaps |
| the layer redrawn as text characters | charGrid, cellSample, glyphFor, glyphTint |
| an engraved, banknote-style line drawing | linework |
Example
import {defineShader, p, effects, crosses, transformColor} from 'shaders/std'
const {bleedWarp, kuwahara, paperGrain, wash} = effects.stylize
// One bleed stage, shared by the brush and the wash so the taps and the original sample agree.
const wet = bleedWarp({amount: p('bleed')})
// Watercolor: soft colour patches over paper grain, mixed back toward the original by strength.
export const Watercolor = defineShader({
name: 'Watercolor',
props: {
radius: {default: 3, compileTime: true},
bleed: {default: 1, recompile: crosses(0)},
strength: {default: 1, recompile: crosses(0)},
paper: {default: 0.35, recompile: crosses(0)},
paperColor: {default: '#fbf7ec', transform: transformColor},
},
effect: wash({
at: wet,
brush: kuwahara({at: wet, radius: p('radius')}),
grain: paperGrain({amount: p('paper')}),
paper: p('paper'),
paperColor: p('paperColor'),
strength: p('strength'),
}),
})
Types
| Type | What it is |
|---|---|
FragmentStage | A stage of the ASCII look: one value per pixel, computed in the fragment. |
GatherStage | A stage that reads the layer inside (rendered to a texture) and gives one value per pixel. |
KuwaharaBrush | The brush kuwahara builds: four overlapping patches of samples around each pixel. |
SobelRing | The ring of edge samples sobelTaps builds around each pixel. |
Words
- washThe watercolor look: the layer inside flattened into soft patches of color over paper grain.
- kuwaharaA painter's brush: the flattening that turns fine detail into soft patches of one color.
- bleedWarpA sample position that drifts slowly past hard edges, so paint bleeds instead of stopping.
- paperGrainFine paper grain for the pigment to settle into, one brightness value per screen pixel.
- chalkSketchThe chalk-drawing look: edges become chalk strokes and dark areas fill with cross-hatching.
- sobelTapsEight brightness samples in a ring around each pixel, for finding edges.
- lineworkThe engraving look: the layer inside redrawn as ink lines that swell where it is dark.
- glyphTintThe finished ASCII look: each character tinted with its cell's color, everything else transparent.
- glyphForThe character drawn in each cell, picked by how bright the cell is.
- charGridThe character grid: which cell each pixel falls in, and where it sits inside that cell.
- cellSampleThe color of the layer inside at the centre of each character cell.