How part rough part specular?

Hey all,

I’ve tested Standard, Phong and Physical material but I end up with the same problem in all.

Taking Standard for one, I have an object that has parts 100% specular and others 100% rough.

I have used a roughness map but the specular areas are 100% rough if I set roughness =1. That to me kind of makes sense, because the roughness texture is black for the specular areas, but nowhere can I tell the engine that these areas are specular - hence the full object looks rough.

The opposite happens - object fully specular whilst I wanted the area in the red rectangle rough - when I set roughness =0 with the roughness map, the roughness map will be ignored.
AllSpecular

Can you please give me some guidance how I can achieve an object 100% spec in some areas 100% rough in others?

Thanks a bunch for your help,

Sergio

You have to set material.roughness to 1 and pass a roughness texture in material.roughnessMap. You can use https://threejs.org/examples/#webgl_materials_standard as reference.

1 Like

Thanks a bunch for the reference @mrdoob

Where I fail is that I can’t reference the same texture file for two different maps:

          Promise.all([
						new Promise((resolve, reject) => basisLoader.load( 
	    "Photog_Glace1/Cerberus_A.basis", resolve, undefined, reject ) ),
						new Promise((resolve, reject) => basisLoader.load( 
	    "Photog_Glace1/Cerberus_N.basis", resolve, undefined, reject ) ),
						**new Promise((resolve, reject) => basisLoader.load( 
	    "Photog_Glace1/Cerberus_RM.basis", resolve, undefined, reject ) ),**
	**					new Promise((resolve, reject) => basisLoader.load( 
	    "Photog_Glace1/Cerberus_RM.basis", resolve, undefined, reject ) ),**
	**					]).then(([albedoM, normalMap, metalMap, roughMap]) => {**

Yields error:

 BasisTextureLoader.js:152 Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Worker': ArrayBuffer at index 0 is already neutered.
at examples/jsm/loaders/BasisTextureLoader.js:152:13
at new Promise (<anonymous>)
at examples/jsm/loaders/BasisTextureLoader.js:146:12

I tried this code but I only get a full metal textured object without roughness (both roughness and metalness settings=1). Can I please get some guidance how to build this with Promise please

Promise.all([
new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/Cerberus_A.basis", resolve, undefined, reject ) ),
new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/Cerberus_N.basis", resolve, undefined, reject ) ),
new Promise((resolve, reject) => basisLoader.load(Photog_Glace1/Cerberus_RM.basis", resolve, undefined, reject ) ),
			]).then(([albedoM, normalMap, metalMap, roughMap]) => {
			albedoM.encoding = THREE.sRGBEncoding;
							    albedoM.wrapS = THREE.RepeatWrapping;
							    albedoM.wrapT = THREE.RepeatWrapping;
			albedoM.repeat.x = 1;
			      				albedoM.repeat.y = 1;

			normalMap.wrapS = THREE.RepeatWrapping;
							    normalMap.wrapT = THREE.RepeatWrapping;
							    normalMap.repeat.x = 1;
			      				normalMap.repeat.y = 1;

			metalMap.wrapS = THREE.RepeatWrapping;
							    metalMap.wrapT = THREE.RepeatWrapping;
							    metalMap.repeat.x = 1;
			      				metalMap.repeat.y = 1;
            roughMap=metalMap;

             vaseMeshMaterial = new THREE.MeshStandardMaterial({
					roughnessMap: roughMap,
					metalnessMap: metalMap,
					map: albedoM,
					normalMap: normalMap,
					roughness: settings.roughness,
					metalness: settings.metalness,

Um, I can actually reproduce the error with this fiddle: Edit fiddle - JSFiddle - Code Playground

The code loads the same texture twice. The second call of BasisTextureLoader.load() causes the following runtime errors:

DOMException: Failed to execute ‘postMessage’ on ‘Worker’: ArrayBuffer at index 0 is already neutered.

Uncaught (in promise) DOMException: Failed to execute ‘postMessage’ on ‘Worker’: ArrayBuffer at index 0 is already neutered.

@donmccurdy Could this be a bug in BasisTextureLoader?

See https://github.com/mrdoob/three.js/issues/16524#issuecomment-500916095.

Caching is handled at the FileLoader level, which makes sure to return the same ArrayBuffer for the same URL, without making duplicate requests. BasisTextureLoader transfers that ArrayBuffer to the worker for transcoding, making sure not to copy the whole thing around in memory. But once it’s transferred, the ArrayBuffer is neutered on the main thread and can’t be transmitted again. When the second load call tries to transcode it, it fails.

So the ways to fix this are:

(a) Always copy the ArrayBuffer, wasting memory, just in case the texture is requested again.
(b) Add a new second layer of caching in BasisTextureLoader, separate from what FileLoader already does, so that later requests wait for the first.

I definitely don’t like (a). I’m not sure about (b). The easier solution is to call load once, and reuse the texture.

1 Like

Thanks for looking into this @Mugen87 @donmccurdy i’m happy to call only once but tried this code but I only get a full metal textured object without roughness (both roughness and metalness settings=1). Can I please get some guidance how to build this with Promise please

Promise.all([
                new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/Cerberus_A.basis", resolve, undefined, reject ) ),
                new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/Cerberus_N.basis", resolve, undefined, reject ) ),
                new Promise((resolve, reject) => basisLoader.load(Photog_Glace1/Cerberus_RM.basis", resolve, undefined, reject ) ),
            ]).then(([albedoM, normalMap, metalMap, roughMap]) => {
            albedoM.encoding = THREE.sRGBEncoding;
                                albedoM.wrapS = THREE.RepeatWrapping;
                                albedoM.wrapT = THREE.RepeatWrapping;
                                albedoM.repeat.x = 1;
                                albedoM.repeat.y = 1;

                                normalMap.wrapS = THREE.RepeatWrapping;
                                normalMap.wrapT = THREE.RepeatWrapping;
                                normalMap.repeat.x = 1;
                                normalMap.repeat.y = 1;

                                metalMap.wrapS = THREE.RepeatWrapping;
                                metalMap.wrapT = THREE.RepeatWrapping;
                                metalMap.repeat.x = 1;
                                metalMap.repeat.y = 1;
             
                                roughMap=metalMap;

                 vaseMeshMaterial = new THREE.MeshStandardMaterial({
                    roughnessMap: roughMap,
                    metalnessMap: metalMap,
                    map: albedoM,
                    normalMap: normalMap,
                    roughness: settings.roughness,
                    metalness: settings.metalness,
                    side: THREE.DoubleSide
                    });

                    roughMap.wrapS = THREE.RepeatWrapping;
                    roughMap.wrapT = THREE.RepeatWrapping;
                    roughMap.repeat.x = 1;
                    roughMap.repeat.y = 1;

That code looks OK to me, can you share a live demo or the model and textures?

I don’t think you need any of the ‘wrapS’, ‘wrapT’, or ‘repeat’ settings in there, since you’re configuring them to repeat only once, i.e. not repeat, which is the default.

And the earlier line resolving the promise could just be…

.then(([albedoM, normalMap, metalRoughMap]) => {

… where that same metalRoughMap variable gets used both for roughnessMap and metalnessMap.

Great yes, that did it for me @donmccurdy ! Thanks a bunch both @donmccurdy and @Mugen87 :slight_smile:

In case someone comes accross the need to know how to line up this code here’s the implementation that worked for me as suggested above,

Thanks a lot for your help !

Promise.all([
					new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/albedo.basis", resolve, undefined, reject ) ),
					new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/normal.basis", resolve, undefined, reject ) ),
					new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/cavity.basis", resolve, undefined, reject ) ),
					new Promise((resolve, reject) => basisLoader.load( "Photog_Glace1/rough_metal_Map.basis", resolve, undefined, reject ) ),
						]).then(([albedoM, normalMap, aoMap, metalRoughMap]) => {

					albedoM.encoding = THREE.sRGBEncoding;
				   
					vaseMeshMaterial = new THREE.MeshStandardMaterial({
						roughnessMap: metalRoughMap,
						metalnessMap: metalRoughMap,
						map: albedoM,
						normalMap: normalMap,
						roughness: settings.roughness,
						metalness: settings.metalness,
						aoMap: aoMap,
						aoMapIntensity: 1,
						flatShading: false,
					
						side: THREE.DoubleSide
					});
					
    				console.log("Materials Finished Loading");
					myObjectLoader.load( "Photog_Glace1/Glace1_Model.obj", function ( group ) {
						var geometry = group.children[ 0 ].geometry;
						geometry.attributes.uv2 = geometry.attributes.uv;
						geometry.center();

						vaseMesh = new THREE.Mesh( geometry, vaseMeshMaterial );
						vaseMeshMaterial.normalScale.x = -1;
						vaseMesh.castShadow = true;
						vaseMesh.receiveShadow = true;
						vaseMesh.position.set(28,0,0);
					} , onProgress,onError);
				});