* Functional Approach to Texture Generation
Jerzy Karczmarczuk
PADL02 talk (January 2002, Portland, OR)
The talk describes 'Clastic', a system for generation of _procedural_
textures.
http://users.info.unicaen.fr/~karczma/Work/Clastic_distr/clastic.html
The web site refers to a tutorial with many beautiful pictures.
Raster graphics algorithms can be partitioned into "active" and
"passive". Active algorithms take a pixelmap and actively modify it:
they traverse the pixelmap or a subset of it -- often in complex ways
-- and examine and set pixel values. Bresenham line drawing and
contour filling algorithms belong to that class. Passive algorithms on
the other hand do not actively draw anything. The algorithm is
expressed as a function f(x,y). A rendering engine passes the function
coordinates of a point and expects in return the color value at that
point. The values of x and y of two consecutive invocations of f(x,y)
are generally unpredicable. The function f(x,y) does not have access
to a pixelmap and can't examine pixel values. Texture mapping
algorithms belong to the passive category. Such algorithms -- shaders
-- are used in animation and ray tracing pipelines.
Clastic is an exploratory tool for passive raster graphics. The tool
can generate regular (geometric) or random textures _described_ as
relationships between 2D points and their colors. In Clastic, a
texture is a function R^2 -> R^3, a function from points to RGB color
vectors. The relationship is described purely declaratively.
In Clastic, a texture function can be specified in a low-level way,
for example
circle (x,y) = if x*x + y*y <= 1.0 then tx (x,y) else rgb_white (x,y)
which creates a circle filled with a texture tx(x,y) on a white
foreground. However, if we define a texture combinator
fmask h f g = \x -> if (h x == 0.0) then g x else f x
and an overloaded function
step x | x < zero = zero
| x > zero = one
| otherwise = half
then we can write 'circle' as
circle = fmask (\p -> step (1.0 - norm2 p)) tx rgb_white
or even
circle = fmask (step . (sone .- norm2)) tx rgb_white
where
sone p = 1.0
norm2 (x,y) = x*x+y*y
and .- is subtraction lifted to (Float,Float)->Float functions.
What is remarkable about the latter definition of 'circle' is that
point coordinates do not appear at all. The shader 'circle' is
defined as a pure combination of primitive textures. This
coordinate-free style of programming is strongly encouraged by Clastic
[*]. In Clastic, we transform things rather than coordinates. The
coordinates should be hidden inside higher-order operators such as
translate, rotate, scale; inside primitive textures such as rgb_white;
inside 'soft objects' Point->Float such as 'sone'; and inside blending
combinators such as fmask and `over`.
As the paper and the Clastic tutorial show, combining a few primitives
yields surprisingly complex textures such as impressive geometric
patterns, tesselations and wallpaper including Escher reptiles, and
woven patterns.
Clastic uses purely functional, stateless random number generators
(advocated by Ward). The generators are pure functions Integer->Float
with a property that function values do not visibly correlate even for
neighboring arguments. The "random noise" generators let Clastic
produce dithering, fractal patterns (e.g., clouds), turbulence, and
more complex marble-like textures and bump-maps.
Finally, Clastic can apply transformers to other transformers:
deformers. Examples include warping, lenses and random displacement to
generate irregular wooden grain. The problem of inverting a
transformation is generally rather complex. Still Clastic can deal
with it. As the paper stresses, warping and deformation can be used
abstractly, can be used generically and can be composed. This may make
the coding by an order of magnitude shorter and easier than in the
imperative approach.
[*] Clastic uses programming version of Clean for Windows OS. The
examples mentioned above (and collected below) are paraphrased in Haskell.
class (Ord a) => Step a where
one, zero, half:: a
step:: a->a
step x | x < zero = zero
| x > zero = one
| otherwise = half
instance Step Float where
one = 1.0
zero = 0.0
half = 0.5
fmask h f g = \x -> if (h x == 0.0) then g x else f x
type Pt = (Float, Float)
type Ptf = Pt->Float
norm2::Ptf
norm2 (x,y) = x*x+y*y
newtype RColor = RColor (Float, Float, Float) deriving Show
rgb_white p = RColor (255.0,255.0,255.0)
tx p = RColor (100.0,100.0,100.0)
-- circle :: Pt->RColor
-- circle = fmask (\p -> step (1.0 - norm2 p)) tx rgb_white
sone:: Ptf
sone p = 1.0
infixl 6 .-
(.-)::Ptf->Ptf->Ptf
(.-) f g = \x -> (f x) - (g x)
circle = fmask (step . (sone .- norm2)) tx rgb_white