Post-Process Nodes

Node Materials for Post-Processes and Particles

A node material's mode decides what its graph shades: meshes (Material, 0), the whole frame (PostProcess, 1, through a CurrentScreenBlock), particle sprites (Particle, 2, with ParticleTextureBlock), or a texture (ProceduralTexture, 3); newer modes serve Gaussian splats and the Smart Filter Editor. Each mode has a starter graph and a factory returning an ordinary engine object (both ran here):

A post-process and a particle shader from node graphsJavaScript
const grade = new BABYLON.NodeMaterial('grade', scene);
grade.setToDefaultPostProcess();                    // samples the rendered frame
grade.build();
const pass = grade.createPostProcess(camera);        // a PostProcess on this camera
const sparks = new BABYLON.NodeMaterial('sparks', scene);
sparks.setToDefaultParticle();
sparks.build();
sparks.createEffectForParticles(dust);               // replaces the particle shader

The Node Particle Editor (The Node Particle Editor) goes further, building whole particle systems, not only their shaders, as graphs.

One node material grading the whole frame as a post-process, another shading dust particlesHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
  canvas { width: 100%; max-width: 600px; height: 340px; display: block; touch-action: none; }
  #bar { max-width: 600px; padding: 6px 8px; }
</style>
<canvas id="view"></canvas>
<div id="bar"><label><input type="checkbox" id="grade" checked> node post-process (warm grade)</label></div>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#cfd8dcff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, 1.3, 6, new BABYLON.Vector3(0, 1.1, 0), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.5), scene);
const paint = (hex) => {
  const m = new BABYLON.StandardMaterial(hex, scene);
  m.diffuseColor = BABYLON.Color3.FromHexString(hex);
  m.specularColor = BABYLON.Color3.Black();
  return m;
};
BABYLON.MeshBuilder.CreateGround('floor', { width: 8, height: 6 }, scene).material = paint('#b08d6a');
const wall = BABYLON.MeshBuilder.CreatePlane('wall', { width: 8, height: 4 }, scene);
wall.position.set(0, 2, 1.2);
wall.material = paint('#fff3e0');
['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((hex, i) => {
  const book = BABYLON.MeshBuilder.CreateBox('book' + i, { width: 0.5, height: 1.4, depth: 0.2 }, scene);
  book.position.set((i - 2.5) * 0.62, 0.7, 0.6);
  book.material = paint(hex);
});

// 1. PostProcess mode: the graph shades the whole rendered frame through a CurrentScreenBlock
const grade = new BABYLON.NodeMaterial('grade', scene);
const position = new BABYLON.InputBlock('position');
position.setAsAttribute('position2d');                 // the full-screen quad
const one = new BABYLON.InputBlock('one');
one.isConstant = true;
one.value = 1;
const position3D = new BABYLON.VectorMergerBlock('position3D');
position.connectTo(position3D);
one.connectTo(position3D, { input: 'w' });
const vertexOut = new BABYLON.VertexOutputBlock('vertexOut');
position3D.connectTo(vertexOut);
const uv = new BABYLON.RemapBlock('uv');               // -1..1 to 0..1
position.connectTo(uv);
const screen = new BABYLON.CurrentScreenBlock('currentScreen');   // the frame rendered so far
uv.connectTo(screen);
const tint = new BABYLON.InputBlock('tint');
tint.value = new BABYLON.Color3(1.12, 0.95, 0.72);     // warm, slightly faded
const graded = new BABYLON.MultiplyBlock('graded');
screen.rgb.connectTo(graded.left);
tint.output.connectTo(graded.right);
const fragmentOut = new BABYLON.FragmentOutputBlock('fragmentOut');
graded.output.connectTo(fragmentOut.rgb);
grade.addOutputNode(vertexOut);
grade.addOutputNode(fragmentOut);
grade.mode = BABYLON.NodeMaterialModes.PostProcess;
grade.build();
let pass = grade.createPostProcess(camera);           // an ordinary PostProcess on this camera

// 2. Particle mode: the default particle graph (texture x color) replaces the particle shader
const dot = new BABYLON.DynamicTexture('dot', { width: 64, height: 64 }, scene);
const g = dot.getContext().createRadialGradient(32, 32, 0, 32, 32, 32);
g.addColorStop(0, 'rgba(255,255,255,1)'); g.addColorStop(1, 'rgba(255,255,255,0)');
dot.getContext().fillStyle = g;
dot.getContext().fillRect(0, 0, 64, 64);
dot.update();
dot.hasAlpha = true;
const dust = new BABYLON.ParticleSystem('dust', 600, scene);
dust.particleTexture = dot;
dust.emitter = new BABYLON.Vector3(0, 1.4, 0);
dust.minEmitBox = new BABYLON.Vector3(-3, -1.2, -1.5);
dust.maxEmitBox = new BABYLON.Vector3(3, 1.5, 0.5);
dust.color1 = new BABYLON.Color4(1, 0.95, 0.8, 0.9);
dust.color2 = new BABYLON.Color4(1, 0.85, 0.6, 0.6);
dust.colorDead = new BABYLON.Color4(1, 0.9, 0.7, 0);
Object.assign(dust, { minSize: 0.03, maxSize: 0.08, minLifeTime: 3, maxLifeTime: 6, emitRate: 120,
  gravity: new BABYLON.Vector3(0, -0.02, 0), minEmitPower: 0.02, maxEmitPower: 0.08 });
dust.preWarmCycles = 200;                              // start with the air already full
const sparks = new BABYLON.NodeMaterial('sparks', scene);
sparks.setToDefaultParticle();
sparks.build();
sparks.createEffectForParticles(dust);
dust.start();

document.getElementById('grade').addEventListener('change', (event) => {
  if (event.target.checked) pass = grade.createPostProcess(camera);
  else pass.dispose();
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>