4. Procedural Texturing

Procedural Texturing #

Problem statement #

Exercise

Adapt other patterns from the book of shaders and map them as textures onto other 3D shapes.

Background #

A procedural texture is a texture created using a mathematical description like an algorithm rather than directly stored data. The advantage of this approach is low storage cost, unlimited texture resolution and easy texture mapping. These kinds of textures are often used to model surface or volumetric representations of natural elements such as wood, marble, granite, metal, stone, and others.

Usually, the natural look of the rendered result is achieved by the usage of fractal noise and turbulence functions. These functions are used as a numerical representation of the “randomness” found in nature.

Truchet #

Truchet tiles are square tiles decorated with patterns that are not rotationally symmetric. When placed in a square tiling of the plane, they can form varied patterns, and the orientation of each tile can be used to visualize information associated with the tile’s position within the tiling. Truchet tiles were first described in a 1704 memoir by Sébastien Truchet entitled “Mémoire sur les combinaisons”, and were popularized in 1987 by Cyril Stanley Smith.

Code #

Code Procedural Texturing .js
let pg;
let truchetShader, brickShader,colorShader;
let theShader;
let selectShader;
let selectShape;
let shapeValue = 0;

function preload() {
  // shader adapted from here: https://thebookofshaders.com/09/
  truchetShader = readShader('/vc_page/sketches/shaders/procedural/truchet.frag',
                             { matrices: Tree.NONE, varyings: Tree.NONE });
  brickShader = readShader('/vc_page/sketches/shaders/procedural/brick.frag',
                             { matrices: Tree.NONE, varyings: Tree.NONE });
  colorShader = readShader('/vc_page/sketches/shaders/procedural/color.frag',
                             { matrices: Tree.NONE, varyings: Tree.NONE });
}

function setup() {
  createCanvas(400, 400, WEBGL);
  // create frame buffer object to render the procedural texture
  pg = createGraphics(400, 400, WEBGL);
  textureMode(NORMAL);
  noStroke();
  
  pg.noStroke();
  pg.textureMode(NORMAL);
  
  selectShader = createSelect();
  selectShader.position(10, 10);
  selectShader.style('color', 'black');
  selectShader.option('Truchet', 0);
  selectShader.option('Brick', 1);
  selectShader.option('Color', 2);
  selectShader.changed(selectShaderEvent);
  
  selectShape = createSelect();
  selectShape.position(300, 10);
  selectShape.style('color', 'black');
  selectShape.option('Cylinder', 0);
  selectShape.option('Sphere', 1);
  selectShape.option('Box', 2);
  selectShape.changed(selectShapeEvent);
  
  theShader = truchetShader;
  // use truchetShader to render onto pg
  pg.shader(theShader);
  // emitResolution, see:
  // https://github.com/VisualComputing/p5.treegl#macros
  pg.emitResolution(theShader);
  // https://p5js.org/reference/#/p5.Shader/setUniform
  theShader.setUniform('u_zoom', 3);
  // pg clip-space quad (i.e., both x and y vertex coordinates ∈ [-1..1])
  pg.quad(-1, -1, 1, -1, 1, 1, -1, 1);
  // set pg as texture
  texture(pg);
  
}

function selectShaderEvent(){
  shaderValue = selectShader.value();
  if(shaderValue == 0){
      theShader = truchetShader;
    }else if(shaderValue == 1){
      theShader = brickShader;
      console.log('brick');
    }else if(shaderValue == 2){
      theShader = colorShader;
    }
  
  pg.shader(theShader);
  // emitResolution, see:
  // https://github.com/VisualComputing/p5.treegl#macros
  pg.emitResolution(theShader);
  // https://p5js.org/reference/#/p5.Shader/setUniform
  theShader.setUniform('u_zoom', 3);
  // pg clip-space quad (i.e., both x and y vertex coordinates ∈ [-1..1])
  pg.quad(-1, -1, 1, -1, 1, 1, -1, 1);
  // set pg as texture
  texture(pg);
}

function selectShapeEvent(){
  shapeValue = selectShape.value();
}

function draw() {
  background(33);
  orbitControl();
  
  if (shapeValue == 0){
    cylinder(100, 200);
  } else if (shapeValue == 1){
    sphere(120);
  } else {
    box(150,150,150);
  }
}

function mouseMoved() {
  // https://p5js.org/reference/#/p5.Shader/setUniform
  theShader.setUniform('u_zoom', int(map(mouseX, 0, width, 1, 30)));
  // pg clip-space quad (i.e., both x and y vertex coordinates ∈ [-1..1])
  pg.quad(-1, -1, 1, -1, 1, 1, -1, 1);
}
Code truchet.frag
// Author @patriciogv ( patriciogonzalezvivo.com ) - 2015

#ifdef GL_ES
precision mediump float;
#endif

#define PI 3.14159265358979323846

uniform vec2 u_resolution;
uniform float u_time;

uniform float u_zoom;

vec2 rotate2D (vec2 _st, float _angle) {
    _st -= 0.5;
    _st =  mat2(cos(_angle),-sin(_angle),
                sin(_angle),cos(_angle)) * _st;
    _st += 0.5;
    return _st;
}

vec2 tile (vec2 _st, float _zoom) {
    _st *= _zoom;
    return fract(_st);
}

vec2 rotateTilePattern(vec2 _st){

    //  Scale the coordinate system by 2x2
    _st *= 2.0;

    //  Give each cell an index number
    //  according to its position
    float index = 0.0;
    index += step(1., mod(_st.x,2.0));
    index += step(1., mod(_st.y,2.0))*2.0;

    //      |
    //  2   |   3
    //      |
    //--------------
    //      |
    //  0   |   1
    //      |

    // Make each cell between 0.0 - 1.0
    _st = fract(_st);

    // Rotate each cell according to the index
    if(index == 1.0){
        //  Rotate cell 1 by 90 degrees
        _st = rotate2D(_st,PI*0.5);
    } else if(index == 2.0){
        //  Rotate cell 2 by -90 degrees
        _st = rotate2D(_st,PI*-0.5);
    } else if(index == 3.0){
        //  Rotate cell 3 by 180 degrees
        _st = rotate2D(_st,PI);
    }

    return _st;
}

void main (void) {
    vec2 st = gl_FragCoord.xy/u_resolution.xy;

    st = tile(st,u_zoom);
    st = rotateTilePattern(st);

    // Make more interesting combinations
    // st = tile(st,2.0);
    // st = rotate2D(st,-PI*u_time*0.25);
    // st = rotateTilePattern(st*2.);
    // st = rotate2D(st,PI*u_time*0.25);

    // step(st.x,st.y) just makes a b&w triangles
    // but you can use whatever design you want.
    gl_FragColor = vec4(vec3(step(st.x,st.y)),1.0);
}
Code brick.frag
// Author @patriciogv ( patriciogonzalezvivo.com ) - 2015

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

uniform float u_zoom;

vec2 brickTile(vec2 _st, float _zoom){
    _st *= _zoom;

    // Here is where the offset is happening
    _st.x += step(1., mod(_st.y,2.0)) * 0.5;

    return fract(_st);
}

float box(vec2 _st, vec2 _size){
    _size = vec2(0.5)-_size*0.5;
    vec2 uv = smoothstep(_size,_size+vec2(1e-4),_st);
    uv *= smoothstep(_size,_size+vec2(1e-4),vec2(1.0)-_st);
    return uv.x*uv.y;
}

void main(void){
    vec2 st = gl_FragCoord.xy/u_resolution.xy;
    vec3 color = vec3(0.0);

    // Modern metric brick of 215mm x 102.5mm x 65mm
    // http://www.jaharrison.me.uk/Brickwork/Sizes.html
    // st /= vec2(2.15,0.65)/1.5;

    // Apply the brick tiling
    st = brickTile(st,u_zoom);

    color = vec3(box(st,vec2(0.9)));

    // Uncomment to see the space coordinates
    //color = vec3(st,0.0);

    gl_FragColor = vec4(color,1.0);
}

Code color.frag
// Author @patriciogv - 2015

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

uniform float u_zoom;

float circle(in vec2 _st, in float _radius){
    vec2 l = _st-vec2(0.5);
    return 1.-smoothstep(_radius-(_radius*0.01),
                         _radius+(_radius*0.01),
                         dot(l,l)*4.0);
}

void main() {
	vec2 st = gl_FragCoord.xy/u_resolution;
    vec3 color = vec3(0.0);

    st *= u_zoom;      // Scale up the space by 3
    st = fract(st); // Wrap around 1.0

    // Now we have 9 spaces that go from 0-1

    color = vec3(st,0.0);
    //color = vec3(circle(st,0.5));

	gl_FragColor = vec4(color,1.0);
}

Conclusions #

  • The advantage of using procedural texturing is low storage cost, unlimited texture resolution and easy texture mapping.

  • Manually drawing the geometries required to achieve textures can be challenging, and it can be even more challenging to scale and conform them to complex parts. Scalability is where procedural texturing can offer considerable advantages over manually modeling surface features.

References #