Shaders and Images

Solandra can render GLSL fragment shaders to images and draw them in a sketch. You can also render a sketch to an image and pass it to a shader.

These APIs are intended for adding textures and image effects to sketches.

These APIs require a browser with OffscreenCanvas support.

renderShader

renderShader({ w, h, shader }) compiles a GLSL fragment shader, renders it offscreen at the given pixel size and returns an ImageBitmap. Your shader gets a u_resolution uniform, and should set gl_FragColor in main. Draw the result with s.drawImage:

import { renderShader } from "solandra"
 
const image = renderShader({
  w: 800,
  h: 800,
  shader: `
  void main() {
    vec2 pos = gl_FragCoord.xy / u_resolution;
    float d = distance(pos, vec2(0.5));
    gl_FragColor = vec4(pos.x, 0.4 + 0.4 * cos(d * 40.0), pos.y, 1.0);
  }
  `,
})
 
const sketch = (s: SCanvas) => {
  s.drawImage({ image }) // fills the canvas by default
}

drawImage accepts optional at, w and h values in canvas units:

s.drawImage({ image, at: [0.1, 0.1], w: 0.35, h: 0.35 })

Shader library includes

Add built-in GLSL helpers with includes:

  • "rand" — a hash-based rand(vec2) pseudo-random function
  • "palette" — Inigo Quilez's cosine palette(t, a, b, c, d) function (the same idea as the palette API)
const image = renderShader({
  w: 800,
  h: 800,
  includes: ["rand", "palette"],
  shader: `
  void main() {
    vec2 pos = gl_FragCoord.xy / u_resolution;
    vec3 color = palette(
      pos.x + 0.15 * rand(pos),
      vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.0, 0.33, 0.67)
    );
    gl_FragColor = vec4(color, 1.0);
  }
  `,
})
s.drawImage({ image })

render: sketches as images

render({ w, h, sketch, seed, time }) renders a sketch offscreen as an ImageBitmap. Pass images to renderShader through its images option; each becomes a sampler2D uniform:

import { render, renderShader } from "solandra"
 
// 1. draw something with ordinary Solandra
const source = render({
  w: 800,
  h: 800,
  sketch: (s) => {
    s.background(215, 70, 25)
    s.setFillColor(45, 90, 65)
    s.fill(new Star({ at: s.meta.center, r: 0.35, n: 5 }))
  },
})
 
// 2. distort it with a shader
const warped = renderShader({
  w: 800,
  h: 800,
  images: { source },
  shader: `
  void main() {
    vec2 pos = gl_FragCoord.xy / u_resolution;
    pos.x += 0.08 * sin(pos.y * 20.0);
    pos.y = 1.0 - pos.y; // textures are y-flipped
    gl_FragColor = texture2D(source, pos);
  }
  `,
})
 
// 3. use it in a final sketch
const sketch = (s: SCanvas) => {
  s.drawImage({ image: warped })
}

Combining shaders and shapes

Shader output is an image, so you can clip it, draw shapes over it or apply blend modes:

const texture = renderShader({
  w: 800,
  h: 800,
  includes: ["rand"],
  shader: `
  void main() {
    vec2 pos = gl_FragCoord.xy / u_resolution;
    float n = rand(floor(pos * 60.0) / 60.0);
    gl_FragColor = vec4(vec3(0.1 + 0.15 * n), 1.0);
  }
  `,
})
s.drawImage({ image: texture })
s.withBlendMode("lighten", () => {
  s.times(16, (n) => {
    s.setFillColor(180 + n * 8, 80, 50, 0.6)
    s.fill(
      new RegularPolygon({
        at: s.randomPoint(),
        n: 6,
        r: 0.04 + s.random() * 0.1,
      })
    )
  })
})

See the shader examples for simplex noise and rendering in multiple stages.

Solandra was made by James Porter.

Check out the GitHub page or install with npm i solandra