<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Pipe on blog</title>
    <link>https://jpcharalambosh.co/tags/pipe/</link>
    <description>Recent content in Pipe on blog</description>
    <image>
      <title>blog</title>
      <url>https://jpcharalambosh.co/papermod-cover.png</url>
      <link>https://jpcharalambosh.co/papermod-cover.png</link>
    </image>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Thu, 09 Apr 2026 15:52:34 -0500</lastBuildDate>
    <atom:link href="https://jpcharalambosh.co/tags/pipe/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Post-effects pipeline</title>
      <link>https://jpcharalambosh.co/posts/post_effects/</link>
      <pubDate>Thu, 09 Apr 2026 15:52:34 -0500</pubDate>
      <guid>https://jpcharalambosh.co/posts/post_effects/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Multi-pass post-processing with &lt;a href=&#34;https://beta.p5js.org/tutorials/intro-to-p5-strands/&#34;&gt;p5.strands&lt;/a&gt; and &lt;a href=&#34;https://github.com/VisualComputing/p5.tree&#34;&gt;&lt;code&gt;pipe()&lt;/code&gt;&lt;/a&gt;. Three filter passes — depth-of-field blur, value-noise warp, and pixelation — are chained over a scene framebuffer. Each pass is a &lt;code&gt;baseFilterShader().modify()&lt;/code&gt; callback; &lt;a href=&#34;https://github.com/VisualComputing/p5.tree&#34;&gt;&lt;code&gt;createPanel&lt;/code&gt;&lt;/a&gt; wires their uniforms automatically. Press &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, or &lt;code&gt;3&lt;/code&gt; to rotate the pass ordering at runtime.&lt;/p&gt;
&lt;/blockquote&gt;
















&lt;iframe
  id=&#39;p5-0&#39;
  style=&#39;border:none; display:block; width:0; height:0;&#39;
  srcdoc=&#34;
    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;style&gt;body{margin:0;padding:0;overflow:hidden}&lt;/style&gt;
        &lt;script src=&#39;https://cdn.jsdelivr.net/npm/p5@2.2.3/lib/p5.min.js&#39;&gt;&lt;/script&gt;
        
        &lt;script src=&#39;https://cdn.jsdelivr.net/npm/p5.tree/dist/p5.tree.min.js&#39;&gt;&lt;/script&gt;
        
        
        
        
        
        
        
        
        &lt;script&gt;
          
&amp;#39;use strict&amp;#39;

const _dir = new Float32Array(3)
const _loc = new Float32Array(3)

let layer, models
let dofFilter, noiseFilter, pixelFilter
let uiDof, uiNoise, uiPixel
let focusVal = 0
let order = 0   // 0=dof→noise→pixel  1=noise→pixel→dof  2=pixel→dof→noise

// ── Strands callbacks ─────────────────────────────────────────────────────────

function dofCallback() {
  const depthTex      = uniformTexture(() =&amp;gt; layer.depth)
  const focus         = uniformFloat(() =&amp;gt; focusVal)
  const blurIntensity = uniformFloat(&amp;#39;blurIntensity&amp;#39;)
  const getBlurriness = (d) =&amp;gt; abs(d - focus) * 40 * blurIntensity
  const maxBlurDist   = (b) =&amp;gt; b * 0.01
  getColor((inputs, canvasContent) =&amp;gt; {
    let colour = getTexture(canvasContent, inputs.texCoord)
    let samples = 1
    const centerDepth = getTexture(depthTex, inputs.texCoord).r
    const blur = getBlurriness(centerDepth)
    for (let i = 0; i &amp;lt; 20; i&amp;#43;&amp;#43;) {
      const angle  = float(i) * TWO_PI / 20
      const dist   = float(i) / 20 * maxBlurDist(blur)
      const offset = [cos(angle), sin(angle)] * dist
      const sd     = getTexture(depthTex, inputs.texCoord &amp;#43; offset).r
      if (sd &amp;gt;= centerDepth || maxBlurDist(getBlurriness(sd)) &amp;gt;= dist) {
        colour &amp;#43;= getTexture(canvasContent, inputs.texCoord &amp;#43; offset)
        samples&amp;#43;&amp;#43;
      }
    }
    colour /= float(samples)
    return [colour.rgb, 1]
  })
}

function noiseCallback() {
  const frequency = uniformFloat(&amp;#39;frequency&amp;#39;)
  const amplitude = uniformFloat(&amp;#39;amplitude&amp;#39;)
  const speed     = uniformFloat(&amp;#39;speed&amp;#39;)
  const t         = uniformFloat(() =&amp;gt; millis() / 1000)
  const hash      = (p) =&amp;gt; fract(sin(dot(p, [127.1, 311.7, 74.7])) * 43758.5453123)
  const fade      = (t) =&amp;gt; t * t * (3 - 2 * t)
  const valueNoise = (p) =&amp;gt; {
    const i = floor(p), f = fract(p), u = fade(f)
    const n000=hash(i&amp;#43;[0,0,0]), n100=hash(i&amp;#43;[1,0,0])
    const n010=hash(i&amp;#43;[0,1,0]), n110=hash(i&amp;#43;[1,1,0])
    const n001=hash(i&amp;#43;[0,0,1]), n101=hash(i&amp;#43;[1,0,1])
    const n011=hash(i&amp;#43;[0,1,1]), n111=hash(i&amp;#43;[1,1,1])
    const nx00=mix(n000,n100,u.x), nx10=mix(n010,n110,u.x)
    const nx01=mix(n001,n101,u.x), nx11=mix(n011,n111,u.x)
    return mix(mix(nx00,nx10,u.y), mix(nx01,nx11,u.y), u.z) * 2 - 1
  }
  getColor((inputs, canvasContent) =&amp;gt; {
    const s  = frequency * inputs.texCoord.x
    const v  = frequency * inputs.texCoord.y
    const n1 = valueNoise([s,      v, speed * t])
    const n2 = valueNoise([s &amp;#43; 17, v, speed * t])
    return [getTexture(canvasContent,
              inputs.texCoord &amp;#43; [amplitude*n1, amplitude*n2]).rgb, 1]
  })
}

function pixelCallback() {
  const level = uniformFloat(&amp;#39;level&amp;#39;)
  getColor((inputs, canvasContent) =&amp;gt; {
    const snapped = floor(inputs.texCoord * level) / level
    return [getTexture(canvasContent, snapped).rgb, 1]
  })
}

// ── Setup ─────────────────────────────────────────────────────────────────────

function setup() {
  createCanvas(600, 400, WEBGL)

  layer       = createFramebuffer()
  dofFilter   = baseFilterShader().modify(dofCallback)
  noiseFilter = baseFilterShader().modify(noiseCallback)
  pixelFilter = baseFilterShader().modify(pixelCallback)

  uiDof = createPanel({
    blurIntensity: { min: 0, max: 4,   value: 1.5, step: 0.1, label: &amp;#39;blur&amp;#39;      }
  }, { target: dofFilter,   x: 10,  y: 10, width: 130, labels: true, title: &amp;#39;DOF&amp;#39;,   color: &amp;#39;white&amp;#39; })

  uiNoise = createPanel({
    frequency: { min: 0, max: 10, value: 3,   step: 0.1,  label: &amp;#39;frequency&amp;#39; },
    amplitude: { min: 0, max: 1,  value: 0.3, step: 0.01, label: &amp;#39;amplitude&amp;#39; },
    speed:     { min: 0, max: 1,  value: 0.3, step: 0.01, label: &amp;#39;speed&amp;#39;     }
  }, { target: noiseFilter, x: 155, y: 10, width: 130, labels: true, title: &amp;#39;Noise&amp;#39;, color: &amp;#39;white&amp;#39; })

  uiPixel = createPanel({
    level: { min: 2, max: 600, value: 300, step: 1, label: &amp;#39;level&amp;#39; }
  }, { target: pixelFilter, x: 300, y: 10, width: 130, labels: true, title: &amp;#39;Pixel&amp;#39;, color: &amp;#39;white&amp;#39; })

  const trange = 200
  models = []
  for (let i = 0; i &amp;lt; 50; i&amp;#43;&amp;#43;) {
    models.push({
      position: createVector(
        (random() * 2 - 1) * trange,
        (random() * 2 - 1) * trange,
        (random() * 2 - 1) * trange
      ),
      size:  random() * 25 &amp;#43; 8,
      color: i === 0 ? color(255, 0, 220)
                     : color(int(random(256)), int(random(256)), int(random(256))),
      type:  i === 0 ? &amp;#39;ball&amp;#39; : i &amp;lt; 25 ? &amp;#39;torus&amp;#39; : &amp;#39;box&amp;#39;
    })
  }
}

// ── Draw ──────────────────────────────────────────────────────────────────────

function draw() {
  layer.begin()
  background(0)
  axes()
  orbitControl()
  noStroke()
  ambientLight(100)
  mapDirection(p5.Tree._k, { out: _dir, from: p5.Tree.EYE, to: p5.Tree.WORLD })
  directionalLight(255, 255, 255, _dir[0], _dir[1], _dir[2])
  specularMaterial(255)
  shininess(150)
  models.forEach(m =&amp;gt; {
    push()
    fill(m.color)
    translate(m.position)
    m.type === &amp;#39;box&amp;#39; ? box(m.size) : m.type === &amp;#39;torus&amp;#39; ? torus(m.size) : sphere(m.size)
    pop()
  })
  mapLocation(models[0].position, { out: _loc, from: p5.Tree.WORLD, to: p5.Tree.SCREEN })
  focusVal = _loc[2]
  layer.end()

  const passes = [[dofFilter, noiseFilter, pixelFilter],
                  [noiseFilter, pixelFilter, dofFilter],
                  [pixelFilter, dofFilter, noiseFilter]][order]
  pipe(layer, passes)

  beginHUD()
  fill(255)
  noStroke()
  textSize(12)
  text([&amp;#39;dof → noise → pixel&amp;#39;,
        &amp;#39;noise → pixel → dof&amp;#39;,
        &amp;#39;pixel → dof → noise&amp;#39;][order] &amp;#43; &amp;#39;  (1/2/3 to rotate)&amp;#39;,
       10, height - 10)
  endHUD()
}

function keyPressed() {
  if (key === &amp;#39;1&amp;#39; || key === &amp;#39;2&amp;#39; || key === &amp;#39;3&amp;#39;)
    order = (order &amp;#43; 1) % 3
}

const mouseWheel = () =&amp;gt; false

        &lt;/script&gt;
        
        &lt;script&gt;
          (function() {
            var t = setInterval(function() {
              var c = document.querySelector(&#39;canvas&#39;)
              if (c &amp;&amp; c.offsetWidth &gt; 0) {
                clearInterval(t)
                window.parent.postMessage({ type: &#39;p5resize&#39;, id: &#39;p5-0&#39;, w: c.offsetWidth, h: c.offsetHeight }, &#39;*&#39;)
              }
            }, 100)
          })()
        &lt;/script&gt;
      &lt;/head&gt;
      &lt;body&gt;&lt;/body&gt;
    &lt;/html&gt;
  &#34;
&gt;&lt;/iframe&gt;
&lt;script&gt;
window.addEventListener(&#39;message&#39;, function(e) {
  if (e.data &amp;&amp; e.data.type === &#39;p5resize&#39; &amp;&amp; e.data.id === &#39;p5-0&#39;) {
    var f = document.getElementById(&#39;p5-0&#39;)
    if (f) { f.style.width = e.data.w + &#39;px&#39;; f.style.height = e.data.h + &#39;px&#39; }
  }
})
&lt;/script&gt;



&lt;hr&gt;
&lt;h1 id=&#34;shader-passes-as-strands-callbacks&#34;&gt;Shader passes as strands callbacks&lt;/h1&gt;
&lt;p&gt;Each post-processing pass is a &lt;code&gt;baseFilterShader().modify()&lt;/code&gt; callback — a plain JavaScript function using the &lt;a href=&#34;https://beta.p5js.org/tutorials/intro-to-p5-strands/&#34;&gt;p5.strands&lt;/a&gt; DSL. The framework compiles each callback to WebGL2 at startup; no raw GLSL strings are needed.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
