Canvas filter on threejs canvas

I was trying to add some normal canvas options like rectfill and filter to my main threejs canvas

var canvas = document.getElementById(“Main_Canvas”); //threejs canvas
var ctx = canvas.getContext(‘2d’);
ctx.fillStyle=“rgba(255,0,0,0.5)”;

I do this in my main render loop

but I get this error
TypeError: Cannot set properties of null (setting ‘fillStyle’)

Any ideas as to how to pull this off? Seems if there is a way it might be a way to add postprocessing with canvas filters, just not sure if this is even possible.

Thanks
Jeff (aka MrBodean)

Whenever a context is created on a canvas, you can only get the same context. For example, if you get a WebGL context, you cannot get a 2D context from the same canvas. This is fixed in the specification of the canvas elements.

Possible ways to mimic what you need:

  • use CSS filters on the canvas – they work fine, you can do blur, saturate, grayscale the canvas
  • render the Three.js scene, get an image and draw the image on another 2D canvas, which can use all canvas 2D features
3 Likes

The error you’re encountering suggests that the getContext('2d') call is returning null, which means you can’t manipulate the canvas as a 2D context. This is because Three.js uses WebGL, which takes over the canvas entirely.

However, there are ways to achieve what you’re trying to do. Here are a few approaches:

  1. Use Three.js post-processing effects: Three.js has its own post-processing system that you can use to apply various effects. This is generally the preferred method when working with Three.js.
  2. Render to an offscreen canvas: You can render your Three.js scene to an offscreen canvas, then draw that onto a 2D canvas where you can apply additional effects.
  3. Use a shader: You can create a custom shader to apply effects directly in WebGL.
    Here’s an example of how you might use the second approach:
// Your existing Three.js setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Create an offscreen canvas for Three.js rendering
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = window.innerWidth;
offscreenCanvas.height = window.innerHeight;
const offscreenRenderer = new THREE.WebGLRenderer({ canvas: offscreenCanvas });

// Create a 2D canvas for post-processing
const canvas2D = document.createElement('canvas');
canvas2D.width = window.innerWidth;
canvas2D.height = window.innerHeight;
document.body.appendChild(canvas2D);
const ctx2D = canvas2D.getContext('2d');

// Your Three.js scene setup goes here
// ...

function animate() {
    requestAnimationFrame(animate);

    // Render your Three.js scene to the offscreen canvas
    offscreenRenderer.render(scene, camera);

    // Draw the offscreen canvas onto the 2D canvas
    ctx2D.drawImage(offscreenCanvas, 0, 0);

    // Apply 2D canvas effects
    ctx2D.fillStyle = "rgba(255,0,0,0.5)";
    ctx2D.fillRect(0, 0, canvas2D.width, canvas2D.height);

    // Apply canvas filters if desired
    ctx2D.filter = 'blur(5px)';
    ctx2D.drawImage(canvas2D, 0, 0);
    ctx2D.filter = 'none';  // Reset the filter
}

animate();

This approach allows you to render your Three.js scene and then apply 2D canvas operations on top of it. However, be aware that this method can be performance-intensive, especially for complex scenes or effects.
For better performance and more advanced effects, I’d recommend looking into Three.js’s built-in post-processing system. It provides a wide range of effects that can be applied efficiently within the WebGL context.
I’m not sure this can be helpful to you.
Thanks.

Thanks guys, will give those a shot