Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Stencil Parameter to Materials #15611

Open
wants to merge 22 commits into
base: dev
from

Conversation

Projects
None yet
6 participants
@gkjohnson
Copy link
Contributor

gkjohnson commented Jan 19, 2019

Adds a stencil field to materials to enable simpler use of stencil operations as suggested in #15603.

The stencil state initializes to null, which means the stencil operation is disabled. You can enable it by setting the field to an object like so:

var frontMat = new THREE.MeshBasicMaterial();
frontMat.stencilWrite = true;

frontMat.stencilFunc = THREE.AlwaysStencilFunc;
frontMat.stencilRef = 0;
frontMat.stencilMask = 0xff;

frontMat.stencilFail = THREE.KeepStencilOp;
frontMat.stencilZFail = THREE.KeepStencilOp;
frontMat.stencilZPass = THREE.IncrementWrapStencilOp;

I made a basic example showing that it works here. (source).

image

Documentation Updates

I've updated the docs with information about the stencil operations:

materials/Material.html

constants/Materials.html

@WestLangley

This comment has been minimized.

Copy link
Collaborator

WestLangley commented Jan 19, 2019

@gkjohnson Do you have interest in creating an example of clipping with caps? If so, I think that would be an awesome demo.

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Jan 19, 2019

@WestLangley Absolutely! If it starts looking like this is going to get merged I'll look into updating the docs and creating a better example.

@WestLangley

This comment has been minimized.

Copy link
Collaborator

WestLangley commented Jan 19, 2019

In the mean time, I suggest the following change to your temporary example so it is clearer to others what your program is doing.

// intersection highlight
var geometry = new THREE.BoxBufferGeometry( 2, 2, 0.1 );
geometry.translate( 1, 0, 0 );
...
var displayMat = new THREE.MeshPhongMaterial();
displayMat.opacity = 0.5;

Just a suggestion...

@pailhead

This comment has been minimized.

Copy link
Contributor

pailhead commented Jan 20, 2019

Why put it in stencil ={} ? I think this is different than what is there with blending now, and you have to pass the entire object with everything in it otherwise you don't know what defaults are used internally. How about just default values for all of them? (like blending does)

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Jan 20, 2019

@pailhead

You're right -- I think I prefer the nested style here but it's not necessarily in family with the other fields. Same with the constants. So should they be named more like so?

const frontFaceMat = new MeshBasicMaterial();
frontFaceMat.stencilWrite = true;

frontFaceMat.stencilFunc = AlwaysStencilFunc;
frontFaceMat.stencilRef = 0;
frontFaceMat.stencilMask = 0xff;

frontFaceMat.stencilFail = KeepStencilOp;
frontFaceMat.stencilZFail = KeepStencilOp;
frontFaceMat.stencilZPass = IncrementWrapStencilOp;
@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Jan 22, 2019

I've updated the material parameters to line up closer with the existing ones on Material. However, I'm not quite happy with the name stencilWrite because whether or not the buffer is actually written to depends on the stencil operations. For that reason stencilTest doesn't feel right either. Maybe it should be changed to stencilEnable?

@pailhead

This comment has been minimized.

Copy link
Contributor

pailhead commented Jan 22, 2019

yeah I think the other tricky thing here is that it’s not just the material saying what the state should be but the renderer too. I’m talking about the opaque and then transparent rendering. If using the same api as transparent maybe there should be a stencil pass which may not make a whole lot of sense.

I’m also not sure what happens when you have the test just set to pass vs no test. Ie if you have just one material with some stencil write should all the others also perform an always passing stencil test? Or should you be enabling and disabling as you go through the draw list?

@mrdoob mrdoob added this to the r102 milestone Jan 30, 2019

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Feb 4, 2019

yeah I think the other tricky thing here is that it’s not just the material saying what the state should be but the renderer too. I’m talking about the opaque and then transparent rendering. If using the same api as transparent maybe there should be a stencil pass which may not make a whole lot of sense.

I don't think a separate stencil pass quite makes sense because the order in which you render your stencil passes is completely dependent on the desired effect. renderOrder is really the only way to get that control. The transparent vs opaque binning in the renderer complicates this a little bit and means the user will have to be aware that all stencil renders will have to be binned in the same transparent or opaque grouping to properly guarantee render order.

I’m also not sure what happens when you have the test just set to pass vs no test. Ie if you have just one material with some stencil write should all the others also perform an always passing stencil test? Or should you be enabling and disabling as you go through the draw list?

Can you elaborate on this a bit more? If the the stencil test is disabled then it shouldn't matter what values have been set via setFunc and setOp functions because the stencil test isn't being done at all. I think it's best to only turn on the stencil test "as-needed" by the material. I'm not sure if there are any performance differences, though.

@pailhead

This comment has been minimized.

Copy link
Contributor

pailhead commented Feb 9, 2019

Can you elaborate on this a bit more?

Let me give it a shot, i was thinking:

mesh.onBeforeRender = ()=>{
  gl.enable( gl.STENCIL_TEST )
  ....
  gl_disable( gl.STENCIL_TEST)
}

vs just having gl.ALWAYS and always enabled. In 100 meshes, we could have 30 writing and doing some kind of stencil test and 70 not doing any writes or tests.

The difference between enabling the stencil once before all 100, and then having some just always pass (but doing the test), vs enabling / disabling 30 times. but not doing any kind of stencil test for the other 70.

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Feb 10, 2019

I see. So basically is it better to just keep the stencil test enabled throughout the entire draw instead of turning it on and off between draws. Even if you decide to keep the stencil test enabled you'll still have to be at least changing the stencilFunc state when going from drawing a stenciled material to a non stenciled one. In the worst case you'll have to set the function and the operation with two calls so it doesn't seem like you'd be saving a whole lot to me.

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Feb 10, 2019

I've also updated the material and constants documentation to reflect the new stencil capabilities:

materials/Material.html

constants/Materials.html

@WestLangley

This comment has been minimized.

Copy link
Collaborator

WestLangley commented Feb 11, 2019

@gkjohnson Thanks for this!

@Mugen87

This comment has been minimized.

Copy link
Collaborator

Mugen87 commented Feb 11, 2019

I think it's also important to update Material.toJSON() and Material.copy() otherwise the change seems incomplete to me. Adding an example would also be good. As far as I can see, you already have the code for this.

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Feb 11, 2019

@Mugen87 I agree! Added.

Regarding the example I plan to add one that includes clip planes when I get a chance.

Thanks!

@Mugen87

This comment has been minimized.

Copy link
Collaborator

Mugen87 commented Feb 11, 2019

Okay! Many thanks for the other changes.

@mrdoob mrdoob modified the milestones: r102, r103 Feb 28, 2019

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Mar 4, 2019

I haven't been able to to get around to creating an example yet but I may have time tomorrow to start. Would it be preferable to modify one of the existing examples (webgl_clipping or webgl_clipping_advanced) and add a toggle to enable clipped surfaces or create a new one that focuses on stenciled clip planes?

@Mugen87

This comment has been minimized.

Copy link
Collaborator

Mugen87 commented Mar 4, 2019

I would create a new one.

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Mar 10, 2019

I added an example with solid clip planes here (it requires a new build so the link is to a different branch):

https://raw.githack.com/gkjohnson/three.js/add-material-stencil-example/examples/webgl_clipping_stencil.html

image

It doesn't handle the case where the camera enters the geometry but I'll have to look into that another time.

@WestLangley

This comment has been minimized.

Copy link
Collaborator

WestLangley commented Mar 10, 2019

The example is awesome. Thanks!

I'd suggest referring to the plane constant as "constant" in the API (instead of "distance"), and as in https://threejs.org/examples/webgl_clipping_intersection.html, adding a GUI toggle to show the clipping planes. This gives the viewer a frame-of-reference.

// helpers

var helpers = new THREE.Group();
helpers.add( new THREE.AxesHelper( 5 ) );
helpers.add( new THREE.PlaneHelper( planes[ 0 ], 2, 0xff0000 ) );
helpers.add( new THREE.PlaneHelper( planes[ 1 ], 2, 0x00ff00 ) );
helpers.add( new THREE.PlaneHelper( planes[ 2 ], 2, 0x0000ff ) );
helpers.visible = true;
scene.add( helpers );

Screen Shot 2019-03-09 at 8 00 35 PM

<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - clipping planes</title>

This comment has been minimized.

@WestLangley

WestLangley Mar 10, 2019

Collaborator

webgl - clipping stencil -- or whatever

}
// Handle camera entering model

This comment has been minimized.

@WestLangley

WestLangley Mar 10, 2019

Collaborator

Can you explain what is going on here? Is it important, or can you just set controls.min/maxDistance?

This comment has been minimized.

@gkjohnson

gkjohnson Mar 10, 2019

Author Contributor

Heh, an extraneous comment. I'll clamp the camera position so it can't enter the geometry, though.

This comment has been minimized.

@WestLangley

WestLangley Mar 10, 2019

Collaborator

also maxDistance, please.

This comment has been minimized.

@gkjohnson

gkjohnson Mar 10, 2019

Author Contributor

good idea!

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Mar 10, 2019

@WestLangley Thanks! I've added the helpers, camera clamp, and other suggestions.

https://raw.githack.com/gkjohnson/three.js/b8146f2b6/examples/webgl_clipping_stencil.html

scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 36, window.innerWidth / window.innerHeight, 1 );

This comment has been minimized.

@WestLangley

WestLangley Mar 10, 2019

Collaborator

Please add constructor far argument.

This comment has been minimized.

@gkjohnson

gkjohnson Mar 10, 2019

Author Contributor

Done

gkjohnson added some commits Mar 10, 2019

@tonifds

This comment has been minimized.

Copy link

tonifds commented Mar 15, 2019

I added an example with solid clip planes here (it requires a new build so the link is to a different branch):

https://raw.githack.com/gkjohnson/three.js/add-material-stencil-exam
Caps are rendered with sharp pixels. Anti-aliasing does not seem to work. Specifying antialias:true on WebGLRenderer is not enough.

Is there any way of doing this?

I added an example with solid clip planes here (it requires a new build so the link is to a different branch):

https://raw.githack.com/gkjohnson/three.js/add-material-stencil-example/examples/webgl_clipping_stencil.html

image

It doesn't handle the case where the camera enters the geometry but I'll have to look into that another time.

Caps are rendered with sharp pixels. Anti-aliasing does not seem to work. Specifying antialias:true on WebGLRenderer is not enough.

Is there any way of doing this?

@gkjohnson

This comment has been minimized.

Copy link
Contributor Author

gkjohnson commented Mar 16, 2019

Caps are rendered with sharp pixels. Anti-aliasing does not seem to work. Specifying antialias:true on WebGLRenderer is not enough.

I noticed this, as well. My best guess is that it's an artifact of the way that the stencil buffer and anti aliasing work and that more or less fragments affected by stencil operations can't be anti aliased in the same way as other pixels.

I'd suggest setting "antialias" to false and using any other type of AA approach to smooth edges out, including FXAA or TAA. You can easily improve the edges by bumping the pixel ratio of the renderer, but that will definitely be more performance intensive.

renderer.setPixelRatio( 2 );

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.