Three.js: Multiple Materials on a Single Mesh

Created on 6 Sep 2017  路  20Comments  路  Source: mrdoob/three.js

We can already use this pattern:

var geometry = new THREE.BufferGeometry();
. . .
geometry.clearGroups();
geometry.addGroup( 0, Infinity, 0 );
geometry.addGroup( 0, Infinity, 1 );
geometry.addGroup( 0, Infinity, 2 );
geometry.addGroup( 0, Infinity, 3 );

var materials = [ material0, material1, material2, material3 ];
mesh = new THREE.Mesh( geometry, materials );
scene.add( mesh );

That is, we can render multiple materials on a single mesh - achieving the same result as:

var mesh = THREE.SceneUtils.createMultiMaterialObject( geometry, materials );

Here is a fiddle in which a decal is rendered with an underlying material -- also, a wireframe.

I wonder if we should modify the renderer so that if the material is an array -- and there are no groups specified -- the object is rendered with all materials.

It is an easy feature to implement for BufferGeometry, but does it make sense? Geometry does not support multiple materials per face.

Suggestion

All 20 comments

The only complication I can see is that, in theory, additional passes shouldn't work because of depth testing...

Additional passes do work, actually, because of the three.js default setting:

depthBuffer.setFunc( LessEqualDepth );

Also, users can always set polygonOffset to make wireframes look better.

I wonder if we should modify the renderer so that if the material is an array -- and there are no groups specified -- the object is rendered with all materials.

It's an interesting idea, but I'm not sure if it will be very useful.

Maybe not, but I think it is more elegant than THREE.SceneUtils.createMultiMaterialObject(), and achieves the same end.

I think it is more elegant than THREE.SceneUtils.createMultiMaterialObject()

Yes it is.

@WestLangley How would the modifications of the renderer basically look like?

I wonder if we should modify the renderer so that if the material is an array -- and there are no groups specified -- the object is rendered with all materials.

I like the idea. But i think we would need a new example and some documentation so users understand this implicit behavior of the renderer.

How would the modifications of the renderer basically look like?

It is a few-line change to projectObject() in WebGLRenderer.js. But the handling of groups when converting from Geometry to BufferGeometry would also have to be changed to set groups = [] if there is just a single group covering the entire geometry.

we would need a new example and some documentation

I did not want to go there yet without some evidence it would be worth it...

For now, see the fiddle posted above.

Hmm...

I think this requires a different abstraction. Something like:

var compound = new THREE.Compound( [
    new THREE.Mesh( geometry, material1 ),
    new THREE.Mesh( geometry, material2 ),
    new THREE.Mesh( geometry, material3 )
] );

So Compound would extend Object3D and would be a scene graph node. The objects passed to it would be rendered using the transform from Compound.

Something like this would actually be handy to map to Collada a bit better, because the format supports this:

var compound = new THREE.Compound( [
    new THREE.Mesh( geometry, material ),
    new THREE.Line( geometry, material )
] );

I think GLTF interop may benefit from something like this too.

/cc @donmccurdy @fernandojsg

Why repeat the same geometry three times when it can be abstracted like so:

var mesh = new THREE.Mesh( geometry, [ baseMaterial, decalMaterial ] );

var mesh = new THREE.Mesh( geometry, [ baseMaterial, wireframeMaterial ] );

var mesh = new THREE.Mesh( geometry, [ baseMaterial, shadowMaterial ] ); // it works, too

var mesh = new THREE.Mesh( geometry, [ baseMaterial, transparentMaterial ] );

To avoid colliding with the groups API.

If we follow this style, it would also be nice to be able to do this:

var mesh = new THREE.Mesh( [ geometry1, geometry2 ], material );

But it gets confusing quickly:

var mesh = new THREE.Mesh( [ geometry1, geometry2 ], [ material1, material2, material3 ] );

For GLTFLoader, I haven't switched to using BufferGeometry groups yet because of the theoretically possible case where a single glTF mesh might contain a combination of point/line/triangle primitives. I am asking now whether that case is actually valid in glTF: https://github.com/KhronosGroup/glTF/issues/1090

If yes, then the THREE.Compound idea would do what we need, replacing our use of THREE.Group. If not, then I think existing BufferGeometry groups are fine for glTF.

Is this a multi-pass shader?

Could you elaborate more on how this would be a replacement for Group?

var mesh = new THREE.Mesh( [ geometry1, geometry2 ], material );

What does this represent? ^

var compound = new THREE.Compound( [
    new THREE.Mesh( geometry, material ),
    new THREE.Line( geometry, material )
] );

How is this different from Object3D or Group? Does it guarantee that these two will render one after another (my understanding is that Object3D does not help here).

I didn't know about the group technique from the first post, and i've been struggling to do something like this, but it feels that there might be a better way to solve this somewhere deeper in the core, rather than extend yet another class.

I never figured out why Group should be used instead of Object3D though, so i might be missing the point completely.

My approach to th is was to hack the onBeforeRender call to allow me to make another call with that geometry and that node easily. (was everything but easy)

myMesh.addPass( 
  new THREE.RenderPass({
    onBefore,
    onAfter,
    onlyMaterialNoCallbacks,
  })
)

Something along those lines, where this use case would require only the material to be passed, but another would have the flexibility of for example, calling the stencil buffer. I'd rather see something that gives more features than syntactical sugar, if it's already possible to do this.

People who don't know shaders and webGL are already confused by three. This mesh with two geometries confused me, and i have a rough idea t least of how everything works.

Could you elaborate more on how this would be a replacement for Group?

This wouldn't be a replacement for Group.

How is this different from Object3D or Group?

The objects in the array wouldn't be part of the scene graph. Their matrices wouldn't be used, only the Compound matrix would be used.

I never figured out why Group should be used instead of Object3D though, so i might be missing the point completely.

When using Group the renderer no longer has to look for .geometry nor .material.

If yes, then the THREE.Compound idea would do what we need, replacing our use of THREE.Group. If not, then I think existing BufferGeometry groups are fine for glTF.

Oops, this caught my eye when I wrote it.

So to reiterate, is this supposed to be something like

https://docs.unity3d.com/Manual/SL-Pass.html

or something completely different?

Like the idea of a new Compound prototype. I've seen a similar concept in a scene graph library i had to work with recently.

I think this is a good idea, you can support 'ExtrudeGeometry'

Closing...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fuzihaofzh picture fuzihaofzh  路  3Comments

donmccurdy picture donmccurdy  路  3Comments

danieljack picture danieljack  路  3Comments

clawconduce picture clawconduce  路  3Comments

scrubs picture scrubs  路  3Comments