Blend mode "difference" for multiple meshes in the same scene

Hey, three.js community!

I have a brainteaser that seemed trivial initially and has somewhat spiralled out of control for my brain – I could really use your help and any hint or idea is MUCH appreciated. I’ve been doing a lot of experiments and also research here on the forum and elsewhere but couldn’t find a good solution.

Let me describe the problem as short as possible: I have a varying amount of meshes in my scene. The idea is that I want them to visually blend, when overlapping, in a way that is similar to Photoshop’s “difference” mode.

A bit more detail: When you add one mesh with a white color on top of a white background, a “difference” blend operation would result in the mesh appearing as the inverse, so black. If you add a second mesh and have it overlap/intersect the other mesh, a “difference” would lead to the overlapping parts being white. If you add a third mesh and have all three overlap, you can see that the part where all three overlap becomes black again. And so forth… Here is an image:

So, one can see that the order of the elements is important to arrive at the correct color.

  • Use Three.js’s integrated custom blend equations: While it gives you a lot of opportunities, I feel this wasn’t a helpful route as they essentially always use a singular source and destination. I’m not sure it’s possible to replicate the “difference” blend mode with just one source and destination as the order of elements feels important. I couldn’t find a solution where this approach makes sense.
  • Use render-targets and the PostProcessing composer: That seemed like a smart move, I can simply write my own shader to replicate the equation. The actual operation should be as simple as “vec3 diff = abs(background.rgb - overlay.rgb);”. Right? Well, it seems not really as I run into the same problem: There is no order – all meshes are part of the overlay renderTarget, so the operation inverts the total shapes, not singular ones in the correct order.
  • Use a custom ShaderMaterial per mesh: It feels, like this needs to happen per mesh. But the problem is: What texture-reference do I use within the mesh-shader? Would that mean that I need to create one texture-render/renderTarget per object and gradually render each mesh on top of the next one? I am not sure if there is a solution for some kind of ping-pong approach… But would that mean that for, lets say, 20 meshes I would need to do 20 consecutive renders per frame? That seems very inelegant and like a performance nightmare.

I have set up a very basic codepen here: https://codepen.io/patrikHuebner/pen/MWRewRv
Any help is much appreciated.

Here are some relevant resources I checked out:

https://discourse.threejs.org/t/scene-with-custom-blend-mode-difference-invert/5039/11
The problem is, that the OP is only trying to blend two elements, not multiple as I do.

https://www.youtube.com/watch?v=AxopC4yW4uY
Great introduction to blending in Three.js. But again, it’s only about blending two elements.

1 Like

you could maybe have a render target with these meshes all having additive material with the color of 1 and then a custom shader on that target that would render white or black based on input color % 2 :bulb:

1 Like

You could do a similar approach using the stencil buffer, without a render target:

  1. Clear canvas to white
  2. Draw each shape, with material.colorWrite disabled, using THREE.InvertStencilOp
  3. Draw a fullscreen quad in black, testing for pixels where the stencil buffer is 0
2 Likes

Hey! Thanks for your idea.
Could you explain a bit more what you mean? I set up a demo codepen if it helps to demonstrate your idea in code? https://codepen.io/patrikHuebner/pen/MWRewRv

Hey!
That sounds really interesting. Could you elaborate a bit more – especially on the third point? Either wide pseudocode, some references or editing the codepen?
I’ve set up a test codepen here: https://codepen.io/patrikHuebner/pen/MWRewRv

I’m not sure how to test the pixels where the stencil buffer is 0. Without that final step, my result – following your steps – currently looks like this:

This should do:

const torusMaterial = new THREE.MeshBasicMaterial( { 
    color: 0x000000,
    depthWrite: false,
    stencilWrite: true,
    stencilZPass: THREE.InvertStencilOp,
} ); 

const planeMaterial = new THREE.MeshBasicMaterial( { 
    color: 0xFFFFFF,
    depthTest: false,
    stencilWrite: true,
    stencilFunc: THREE.EqualStencilFunc,
    stencilRef: 0
} );

Result:

3 Likes

That’s amazing. Thank you so much for your help! I need to read up on how this exactly works technically but it definitely creates the effect I was looking for.

1 Like

kek, I thought I liked stencil idea better and spent like 10 minutes fixing your code and enabling orbit controls only to find out I did not scroll the page down :smiley: oh well. to make the reply interesting, here are some funny angles:



2 Likes

ah right, I did not put depthWrite: false, - that’s why :sweat_smile:

1 Like

May I trouble you once more? I’ve been trying to combine this stencil approach with text and was hoping that there is a chance to use a canvas as a texture on a plane and blend that with geometry. The desired outcome would be that the text becomes white where there is a black background and vice versa. Do you know if that is even possible? And if not: If there is any other approach? SDF-text, shaders?

I’ve set up a quick codepen where I’ve added a simple texture. I tried multiple stencil passes and operations but the texture doesn’t blend with the rest. Here is the codePen: https://codepen.io/patrikHuebner/pen/wvZXXrq

And a screenshot of the current state

This is what it should look like (not pretty, but it’s for demo purposes :))

Hey!
I thought I had thanked you for your work – sorry for not actually doing that !! :slight_smile:
Maybe I can trouble you once more? I just replied at the bottom of this thread with the question if there is a chance to the stencil operations on textures as well. So that I can blend text with geometry. If you have any ideas, I’d be thrilled !

Thanks again and in advance.

I think transparent texture and a transparent: false material with alphaTest: 0.5 should work?

2 Likes

Amazing, it does work. I’ve updated the codePen for others to follow along.
I really appreciate the help !! Thank you so much and have a great weekend.

1 Like