* 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