Stencil materials to fake solid geometry in Babylon

Hi everyone,

I am new to the forum and to Babylon.js, but like the framework very much. I have worked with Three.js for the past several years.

I am trying to achieve the same effect of “solid” geometry using clipping planes and stencil materials as is done in Three.js here:
https://threejs.org/examples/#webgl_clipping_stencil

I want to do it in real time, same way they do, so CSG is an overkill and not an option for me.

Here is my playground with what I have so far, using what I have learned about Babylon and what I have seen in other playgrounds:
https://www.babylonjs-playground.com/#95MJI8#129

I am basically importing StanfordBunny.obj into the scene (as I want to be able to do this “slicing” with imported meshes), then add a clipping plane with normal pointing in positive Y direction and an actual plane mesh, whose material I want to show only “inside” the bunny. GUI slider moves the clipping and actual mesh planes up an down in Y direction (similar to how planeY constant does for Three.js demo).

I have spent some time now trying to achieve the same effect as in Three.js and I am sure it’s possible with Babylon. Any help to guide me in the right direction would be very appreciated!

Thank you.

1 Like

Hey and welcome to the forum!

I think you are almost done :slight_smile:
Here is how I see it:

  • You turn stencil writing on
  • You render your mesh with the clip plane activated
  • You turn stencil testing on
  • You remove the clip plane
  • You render your mesh again with a new material containing the color you want for the inside
  • Done :slight_smile:

You can leverage these observables:

  • mesh.onBeforeDrawObservable <== set up the stencil writing
  • mesh.onAfterRenderObservable <== setup the stencil testing and draw the mesh again with new material

Examples of stencil usage in bjs: https://www.babylonjs-playground.com/#LT9C8Q#3

Hey @Deltakosh,

Thank you for the help! I do believe I am getting very close. :slight_smile:
Here is the updated PG:
https://www.babylonjs-playground.com/#95MJI8#159

  • Line 81 - mesh.onBeforeDrawObservable ==> I am turning stencil writing on and setting stencil function reference for the mesh rendering group id
  • Line 87 - mesh.onBeforeRenderObservable ==> activating the clip plane here, since doing it in onBeforeDrawObservable above doesn’t seem to work
  • Line 96 - mesh.onAfterRenderObservable ==> setting up stencil testing and removing the clip plane. what I am not sure how to do is how to draw the mesh again here, but now using the meshInsideMaterial

Any chance you could have another glance at it and see how the last step can be done and if I am missing something or doing something wrong?

Thank you.

Almost there :slight_smile:
to render again:

  • mesh.material = newMaterial
  • mesh.subMeshes[0].render();
  • restore material

(beware though because the observables will be raised again)

Ok, I think I have it implemented in the sequence you recommended, removing observers for the second drawing of the mesh (with inside color), but it’s still not there, so I’m thinking something’s wrong with my stencil setup/testing… :
https://www.babylonjs-playground.com/#95MJI8#175

I have actually come up with a new way, based on my experimenting and advice from @Deltakosh and wanted to share it with everyone.

https://www.babylonjs-playground.com/#95MJI8#178

This basically achieves the same result as the Three.js demo from my initial post with just one clipping plane (so far) - planeY, parallel to XZ plane.

Stanford bunny mesh that I used before had holes under its feet, so what should have looked like solid geometry slice had corresponding holes in it as well, as stencil test relies on back faces of the mesh to draw the “inside” material:


So I switched to use a better model - Georgia Tech Dragon, which works beautifully, just takes a tad longer to load.

Hope this helps someone as well! Cheers!

5 Likes

You rock man!! Congratulations!

1 Like

You know what could be awesome? Writing a doc page for the documentation explaining what you did

I would love to add that to the documentation

Absolutely, would love to.

This may not be the most efficient solution, but it does the job. So I will document this up, and should the solution improve in the future - I will update the docs accordingly.

Now, as I’m a noob in adding a doc page for Babylon, is this the right place to start?
https://doc.babylonjs.com/how_to/contribute_to_documentation

Assuming a new page itself will need to be added here:
https://doc.babylonjs.com/how_to/
under Material (or Mesh) topic?

Absolutely!

Yeah, I would create the page in the how_to under material. Something like “How to create a stencil material” :slight_smile:

It took me a while, but I finally created a 3-plane version of the demo and cleaned up the code:
https://www.babylonjs-playground.com/#95MJI8#240

Ironically, one of the biggest struggles I faced was to position GUI controls inline next to each other (e.g. along the lines of display:inline-block for CSS). Still couldn’t do it, so will leave it as is. Would love to be able to display “Helper Visible” text blocks next to checkboxes, but they always go onto a next line for me.

Later on this week will add the demo and page to the documentation.

A Selection Panel may help with the GUI Use the Selection Panel Helper - Babylon.js Documentation

EDIT Redesign of playground means height of selectBox needs increasing in PG examples.

Hi @JohnK and thank you for referring me to the Selection Panel - works nicely!

Now as it turns out my solution is not complete…

Here is the latest PG: https://www.babylonjs-playground.com/#95MJI8#246

And here is the approach that I use:

  1. Load a mesh and clone it,
  2. Assign material with only front faces to the original mesh (“meshOutside”)
  3. Assign material with only back faces to the mesh clone (“meshInside”)
  4. For each of the 3 slicing directions create a white wireframe plane helper and a plane I will use for stencil testing (“stencilPlane”) with a distinctive (pink) material color.
  5. Draw meshOuside first, meshInside second and stencil planes last.
  6. For meshOutside onBeforeRender simply create scene clipping planes for X, Y, and Z directions (this will clip only this mesh)
  7. For meshInside onBeforeRender create clipping planes as well, position corresponding plane helpers and stencil planes along those clipping planes and turn stencil testing on.
  8. For each of the stencil planes onBeforeRender turn stencil testing on as well and set stencil function to EQUAL (to only draw stencil plane material on top of the meshInside material).

Now this works well as long as you use only one clipping plane. As soon as you have 2 or more clipping planes you get angles where stencil plane material is not drawn (and it makes sense why - there is no meshInside material behind it), for instance this situation with Y and Z clip planes used:

One of the ideas I tried was to not apply clipping planes for the meshInside material, and have back sides of stencil planes have something to render their materials against, but now I think need some combination of stencil and depth testing to not render parts of the meshInside material that are positioned “in front” of stencil planes:

I feel like I’m dancing around a solution, but still cannot fully wrap my head around stencil testing (with all its functions, pass, fail tests and its connection to depth testing).

Any help and advice is greatly appreciated.
Thank you.

I feel like it is more something like having both meshes with no culling no?

@Anton thanks for your efforts!

I tried to improve your last solution a bit by looking at the threejs implementation, which seems to be inspired by this one: More OpenGL Game Programming - Bonus - Advanced Clip Planes.
It is getting close, but still has some issue left. Maybe it will help someone:
https://www.babylonjs-playground.com/#95MJI8#371

I think it is a bit difficult to handle, since it creates multiple meshes and rendering groups and therefore quite some draw calls for this effect, though…

For one plane, this version takes in consideration obstacles such as a box. I think in other versions, the obstacle would have been rendered within the section otherwise. https://www.babylonjs-playground.com/#95MJI8#368

2 Likes