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?
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.
// 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)
}
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.
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
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 :