Draw on top of shader?

Hi there, how can I draw text or a primitive shape on top of a shader? resetShader() doesn’t appear to do anything. Every shape drawn after the shader is started is somehow under the shader.

I’m capturing video from the webcam, applying effects with a shader, and want to put text and shapes on top of the result.


let testCapture;
let stream;

function setup() {

  createCanvas(640, 480, WEBGL);

  //capture from webcam
  testCapture = createCapture(VIDEO);
  testCapture.size(640, 480);
  //hide raw capture video to be replaced with shader below
  testCapture.hide();

  //for capturing and recording the whole canvas later
  canvas = document.querySelector('canvas');
  stream = canvas.captureStream(); 
  console.log('Started stream capture from canvas element: ', stream);

}


function draw() {
  background(200);

  shader(theShader);

  theShader.setUniform('tex0', testCapture);
  theShader.setUniform('resolution', [640, 480]);

  rect(0,0,1,1);

  resetShader();

  fill(0,0,255);
  stroke(0,255,0);
  rect(0,0,10,10);
  //no rectangle visible, only the shader
}

If you comment out

rect(0,0,1,1);

which is the moment the shader is “applied”, the subsequent rectangle is visible underneath.

So how can it be drawn on top of the shader instead?

1 Like

Hello @toom,

It sounds like you want something similar to layers in an image editing software, right? Do the shader magic in one layer and, in another layer on top of that, draw text and other shapes. I can think of at least a couple of ways to accomplish that. p5.js is just HTML, CSS, and JavaScript, after all, so you could stack multiple canvases on top of each other. You could stack any elements, really, like position a <p>Hello World!</p>element on top of your canvas.

Another approach is to render the result of the shader to an off-screen graphics buffer, using createGraphics, and then combine that with text and other shapes on your canvas.

I tweaked the Basic Shader example a bit to illustrate this:

// this variable will hold our shader object
let theShader;
// this variable will hold our createGraphics layer
let shaderGraphics;

function preload() {
  // load the shader
  theShader = loadShader('assets/basic.vert', 'assets/basic.frag');
}

function setup() {
  createCanvas(710, 400);

  fill('white');
  textAlign(CENTER)
  textSize(36);

  // shaders require WEBGL mode to work
  shaderGraphics = createGraphics(710, 400, WEBGL);
  shaderGraphics.noStroke();
}

function draw() {
  // shader() sets the active shader with our shader
  shaderGraphics.shader(theShader);
  // rect gives us some geometry on the screen
  shaderGraphics.rect(0, 0, width, height);

  image(shaderGraphics, 0, 0, width, height);
  text('Hello World!', width / 2, height / 2)
}

2 Likes

Ahhhh thank you! This works. It seems very counter-intuitive that you can’t call resetShader() and continue drawing things, but I am very glad for the solution. :smile:

1 Like

Hello there ! I’ve just run into the same issue. Interestingly enough, the following Processing code works just fine

PShader myShader;

void setup() {
  size(800, 800, P2D);
  myShader = loadShader("myShader.frag"); 
}

void draw() {
  shader(myShader);
  rect(0, 0, width, height);
  resetShader();
  rect(0, 0, 200, 200);
}


While the equivalent p5 code fails

let myShader;

function preload() {
  myShader = loadShader('myShader.vert', 'myShader.frag');
}

function setup() {
  createCanvas(800, 800, WEBGL);
}

function draw() {
  shader(myShader);
  rect(0, 0, width, height);
  resetShader();
  rect(0, 0, 200, 200);
}

https://editor.p5js.org/JulesFouchy/sketches/qVKIHqkdD

So either I missed something in the p5 API, or this is a bug :confused:

1 Like

Hey @JulesFouchy,

Nice digging! It may well be a bug (I don’t know what the intended behavior is) and if you suspect so you should definitely open an issue in the GitHub repository:

Hello again !
After some more digging, I think I understand a little better what’s going on :slight_smile:
Because we are using WEBGL mode, everything is 3D (unlike in Processing where there is both P2D and P3D modes that are compatible with shaders).
Therefore, deciding what is on top of what depends on the z (depth) value, and not on the order of drawing anymore !
If you want to recover the default behavior, you can disable the glDepthTest with these two lines after createCanvas :

const gl = canvas.getContext('webgl')
gl.disable(gl.DEPTH_TEST)

Here is a simple example : https://editor.p5js.org/JulesFouchy/sketches/beiJNt342

Hope this helps !

2 Likes