# Functional rendering ## vs Procedural rendering by Gaëtan Renaudeau - [@greweb](http://twitter.com/greweb)
# Principle *Procedural* rendering VS *Functional* rendering
### "Procedural" rendering The classical way. A set of procedures to draw primitive shapes on a viewport. ![](./img/shapes.png) **Example:** *HTML Canvas, Java Graphics, Processing,...*
### "Functional" rendering A function... **`ƒ : Point => Color`** ...for drawing all pixels > **`∀ p, image.data[p] ← ƒ(p)`** ![](./img/webgl-logo.png) **Example:** *(OpenGL or WebGL) **GLSL***
# Some history...
## Atari 2600 ![](./img/atari-2600.jpg) How rendering was during that time?
## Analog TV: Scan line ![](./img/raster-scan.png) e.g. SECAM: 625 lines, 64 µs/line [(wikipedia)](http://en.wikipedia.org/wiki/Analog_television)
## Rendering was about * Figuring out what color to set for the current point * jump to a position = waiting a given time * **Repeat** each frame rate
### `Position => Color`
# Then came faster CPU...
## Allowing us to ### Make easier rendering abstraction ![](./img/shapes.png)
## But what about that **`Position => Color` function** with modern techs? Let's call that ***Functional Rendering***.
# HTML5 games ### Different web techs * DOM-based * Canvas 2D * WebGL
## Blazing Race ![](./img/blazing-race.png) [http://greweb.me/blazing-race/](http://greweb.me/blazing-race/)
didn't finish that game and made a library instead... ## Illuminated.js ![](./img/illuminatedjs.jpg) Today using Procedural rendering, would be easier and more realistic to use Functional rendering (+ maybe faster in GLSL). http://github.com/gre/illuminated.js
played with WebGL shaders (GLSL) and made ## glsl.js a subset of a WebGL library which focus on making the GLSL (OpenGL Shading Language) easy and accessible for vizualisation and game purposes (2D or 3D). **http://github.com/gre/glsl.js**
## Some games ### using WebGL & GLSL * http://greweb.me/dta/ * http://greweb.me/glsl.js/examples/pong/ * http://greweb.me/glsl.js/examples/snake/ * http://greweb.me/js13k/
# Functional rendering ## needs utility functions
## Example of GLSL toolkit [GLSL spec](http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf)
## Types GLSL has some useful types. * **float** * **vectors** : `vec2`, `vec3`, `vec4` * **matrices** : `mat2`, `mat3`, `mat4` * **texture** : `sampler2D`
## Vectors used for position or color ```glsl vec3 red = vec3(1.0, 0.0, 0.0); // rgb // red.r , red.g , red.b vec4 reda = vec4(red, 1.0); // rgba // red === reda.rgb vec4 color = vec4(0.0, 1.0, 0.0, 1.0); // color.rgb is equivalent to color.xyz ``` * `{x, y, z, w}` : points/normals * `{r, g, b, a}` : colors * `{s, t, p, q}` : texture coordinates
## Arithmetic operations on those types ```glsl vec2 a = vec2(1.0, 2.0); vec2 b = vec2(3.0, 4.0); a + b a - b a * b a / b ```
## Functions GLSL has a lot of functions to work with these types. They are simple functions but very powerful all together. Most of them works on all dimension types (`float`, `vec2`, `vec3`, `vec4`), `genType` is used to refer to these types.
## Classical math functions * min, max * floor, ceil * mod * fract * pow * sqrt * exp * log * ...
## distance *compute the distance between two values (float or vectors).* ```glsl float distance (genType p0, genType p1) ``` implementation: ```glsl length (p0 – p1) ```
## Much more vector functions * length * dot * cross * normalize * ...
## Classical trigonometric functions * cos, sin, tan * acos, asin, atan * radians, degrees * ...
## texture2D *Lookup the pixel color of a position in a sampler2D.* ```glsl vec4 texture2D (sampler2D sampler, vec2 coord) ``` *(There is obviously interpolation)*
## mix *mixes two values `x` and `y` (linear blend) with a criteria `a`.* ```glsl genType mix (genType x, genType y, float a) ``` will returns ```glsl x*(1.-a)+y*a ```
## clamp *constraints a value between a min and a max value.* ```glsl genType clamp (genType x, genType minVal, genType maxVal) ``` will returns ```glsl min (max (x, minVal), maxVal) ```
## smoothstep *normalize, constraints and smooth-interpolate a value.* ```glsl genType smoothstep (genType edge0, genType edge1, genType x) ``` implementation: ```glsl genType t; t = clamp ((x – edge0) / (edge1 – edge0), 0, 1); return t * t * (3 – 2 * t); ``` --- **very important one**, also useful for animations.
![](./img/smoothstep_schema.png)
# Some examples to help you understand both paradigms
### Canvas 2D ```javascript var canvas = document.getElementById("domcanvasid"); var ctx = canvas.getContext("2d"); ```
### WebGL GLSL Javascript ```javascript var ctx = canvas.getContext("webgl"); // ... much more JS to initialize things.. ``` and the GLSL: ```glsl void main (void) { // gl_FragCoord.xy is the current position gl_FragColor = vec4(r, g, b, a); // set the color } ```
## Rectangle ![](./img/rect.png)
### Canvas 2D ```javascript ctx.fillStyle = "red"; ctx.fillRect(x, y, w, h) ```
### GLSL ```glsl bool inRect (vec2 position, float x, float y, float w, float h) { return position.x > x && position.y > y && position.x < x+w && position.y < y+h; } ``` ```glsl void main (void) { if (inRect(gl_FragCoord.xy, 100.0, 100.0, 200.0, 150.0)) { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // red } else { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); // black background } } ```
### Summary Trivial in both cases.
## Circle ![](./img/circle.png)
### Canvas 2D ```javascript ctx.fillStyle = "red"; ctx.beginPath(); ctx.arc(x, y, r, 0, 2*Math.PI, false) ctx.fill(); ```
### GLSL ```glsl bool inCircle (vec2 position, float x, float y, float r) { return distance(position, vec2(x, y)) < r; } ``` ```glsl void main (void) { if (inCircle(gl_FragCoord.xy, 100.0, 100.0, 50.0)) { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // red } else { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); // black background } } ```
### Summary Trivial in both cases.
## Line ![](./img/line.png)
### Canvas 2D ```javascript ctx.strokeStyle = "red"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); ```
### GLSL ```glsl bool onLine (vec2 p, float a, float b, float lineWidth) { // y = a * x + b return distance (p.y, a*p.x+b) < lineWidth; } ``` Exemple: ```glsl uniform vec2 resolution; const vec4 RED = vec4(1.0, 0.0, 0.0, 1.0); const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); void main( void ) { vec2 p = gl_FragCoord.xy / resolution.xy; gl_FragColor = onLine(p, 1.0, 0.0, 0.01) ? RED : BLACK; } ``` [Playground](http://glsl.heroku.com/e#11721.0)
### Summary The procedural Path is quite trivial and easy to use. Making a Segment in functional rendering is much more complex but still possible.
## Curve ![](./img/cos.png)
### Canvas 2D ```javascript ctx.strokeStyle = "red"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); // ... ctx.stroke(); ``` ![](./img/bezier.png)
### GLSL ```glsl bool onCos (vec2 p, float mag, float freq, float y, float lineWidth) { return distance (p.y, y+mag*cos(p.x*freq)) < lineWidth; } ``` [Playground](http://glsl.heroku.com/e#11721.1)
### Summary Implementing a math formula in Functional rendering is very straighforward. A bit more work for the Procedural one: converting the formula to path and bezier curves.
## Gradient in a Disc ![](./img/gradient_disc.png)
### Canvas 2D ```javascript var g = ctx.createLinearGradient(x-r, 0, x+r, 0); // left to right gradient g.addColorStop(0, "red"); g.addColorStop(1, "green"); ctx.fillStyle = "red"; ctx.beginPath(); ctx.arc(x, y, r, 0, 2*Math.PI, false) ctx.fill(); ```
### GLSL ```glsl bool inCircle (vec2 position, float x, float y, float r) { return distance(position, vec2(x, y)) < r; } vec3 linearGradientX (vec2 p, vec3 from, vec3 to, float x1, float x2) { return mix(from, to, smoothstep(x1, x2, p.x)); } ``` ```glsl const vec3 BLACK = vec3(0.,0.,0.); const vec3 RED = vec3(1.,0.,0.); const vec3 GREEN = vec3(0.,1.,0.); void main (void) { vec2 p = gl_FragCoord.xy / resolution.xy; if (inCircle(p, 0.5, 0.5, 0.3)) { gl_FragColor = vec4(linearGradientX(p, RED, GREEN, 0.2, 0.8), 1.0); } else { gl_FragColor = vec4(BLACK, 1.0); } } ``` [Playground](http://glsl.heroku.com/e#11738.0)
### Summary In Canvas 2D we can also do some `createRadialGradient(x0, y0, r0, x1, y1, r1)`, but that's all for gradient possibilities, there is some gradients you can't do with it, for instance a "Circular Gradient".
## Image ![](./img/nyancat.jpg)
### Canvas 2D Stretch the image in the plain canvas: ```javascript var img = new Image(); img.src="nyancat.jpg"; img.onload = draw; function draw () { ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height); } ```
### GLSL ```glsl uniform sampler2D img; // uniform means external variables which is setted from JS void main (void) { vec2 p = gl_FragCoord.xy / resolution.xy; gl_FragColor = texture2D(img, p); // stretch it to the full size } ```
## Exercice in style: Spritesheets http://greweb.me/glsl.js/examples/mario_sprites/
## Text
### Canvas 2D ```javascript ctx.font = "bold 160px Helvetica"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "black"; ctx.fillText("Hello World!", ctx.canvas.width/2, ctx.canvas.height/2); ```
### GLSL Nope! But, You can draw a text with Canvas 2D and use it as a texture. http://greweb.me/glsl.js/examples/canvas-text/
# Composability
## Combine filter conditions ```glsl if (!inRect(...) && inCircle(...) && !underCurveA(...)) { gl_FragColor = // fill some color here } ``` Easily make shapes from union/intersection of other shapes. ![](./img/intersection.png)
## Combine colors You can also do arithmetic on colors! like add and multiply. ```glsl vec3 c = vec3(0.0); c += mainBackground(p); c += colorForPlayer(p, player); for (var i=0; i < NB_BALLS; ++i) { //> c += colorForBall(p, balls[i]); } c *= mainMask(p); // multiply on colors gl_FragColor = vec4(c, 1.0); ``` Also perfect for after-effect. The code is inspired from [glsl.js' Pong example](http://greweb.me/glsl.js/examples/pong/).
## Trick the initial position Here is a typical use-case of post-processing effects: translate a bit the position from the initial one. **You can easily make non-linear transformations!** E.g.: ```glsl uniform float time; uniform sampler2D img; void main (void) { vec2 p = gl_FragCoord.xy / resolution.xy; p += 0.05 * vec2(cos(4.0*p.x+time*0.005), sin(4.0*p.y+time*0.003)); // do more stuff with p, eventually? gl_FragColor = texture2D(img, p); } ``` [See it here](./examples/imagetranslate/index.html)
# Summary ## of both approaches
| Procedural | Functional | |---------------- |:------------:| | **set of drawing primitives** | **ƒ: `position => color`** | | **bitmap** (generally) | **vectorial** (by design) | | *"draw here"* | *"am I here?"* | | what? / where? | how? | | simple, imperative | math, functional | | restricted to primitives | powerful, composable | | generally faster | generally slower,... | | | but easily scalable on GPU |
# Other examples of functional rendering in different techs
## Wrapping Java2D API in Scala ### for functional rendering ```scala import java.awt._ import java.awt.image._ case class Vec2 (x: Double, y: Double) object Java2dPixelWrapper { def render (width: Int, height: Int, getColor: Vec2 => Color) = { var image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) for { x <- 0 until width; y <- 0 until height } { var c = getColor(Vec2(x.toDouble / width.toDouble, y.toDouble / height.toDouble)) image.setRGB(x, y, c.getRGB) } image } } ``` Iterates a scala function on all pixels.
## Use case: ### Functional rendering for generative logo ![](./img/prismicon.png) made at my work for [prismic.io](http://prismic.io)
### Animated blog header Uses functional rendering style inside procedural rendering. ![](./img/blog-header.png) http://greweb.me/javascripts/header.js
### Explore and Experiment Yourself! * http://glsl.heroku.com * http://shadertoy.com * http://webglplayground.net a lot of examples + playground for GLSL
# Demo time! ## Mandelbrot fractal [Result](http://glsl.heroku.com/e#18.0)
# Questions? * twitter: [@greweb](http://twitter.com/greweb) * http://greweb.me/ --- Slides: http://greweb.me/prez-functional-rendering