Function to extend Materials

Hello @Fyrestar,

This extension is just what I was looking for. I’ve been wrangling with onBeforeCompile but it’s been tricky to get exactly what I need.
I’m having a bit of trouble passing in maps and uniforms and I’m wondering if you can help with the syntax.

My MeshStandardMaterial looks like this:

    const surfMat = new THREE.MeshStandardMaterial({
        map: earthAlbedoTex,
        bumpMap: earthBumpTex,
        bumpScale: 0.02,
        roughness: 0.4,
    });

and I’m not sure how to pass this info into the extended shader.

I tried:

    const surfMat = THREE.extendMaterial(THREE.MeshStandardMaterial, 
    {
        class: THREE.CustomMaterial,
        material: {
            map: earthAlbedoTex,
            bumpMap: earthBumpTex,
            bumpScale: 0.02,
            roughness: 0.4,
        }
    });

which errors out with:

three.module.js:22738 Uncaught TypeError: Cannot set property 'value' of undefined
    at refreshUniformsStandard (three.module.js:22738)
    at Object.refreshMaterialUniforms (three.module.js:22286)
    at setProgram (three.module.js:24565)
    at WebGLRenderer.renderBufferDirect (three.module.js:23631)
    at renderObject (three.module.js:24192)
    at renderObjects (three.module.js:24165)
    at WebGLRenderer.render (three.module.js:23959)

I also tried:

    const surfMat = THREE.extendMaterial(THREE.MeshStandardMaterial, 
    {
        class: THREE.CustomMaterial,
        uniforms: {
            map: earthAlbedoTex,
            bumpMap: earthBumpTex,
            bumpScale: 0.02,
            roughness: 0.4,
        }
    });

which errors out with:

three.module.js:1051 Uncaught TypeError: Cannot read property 'elements' of undefined
    at Matrix3.copy (three.module.js:1051)
    at refreshUniformsCommon (three.module.js:22491)
    at Object.refreshMaterialUniforms (three.module.js:22278)
    at setProgram (three.module.js:24565)
    at WebGLRenderer.renderBufferDirect (three.module.js:23631)
    at renderObject (three.module.js:24192)
    at renderObjects (three.module.js:24165)
    at WebGLRenderer.render (three.module.js:23959)

I’m also a bit new to three.js so might be missing something obvious.

Thanks for the help!

The second one is right, the first though you only pass those material settings like side, wireframe, visible properties with the material property.

The right approach:

  const surfMat = THREE.extendMaterial(THREE.MeshStandardMaterial, 
    {
        class: THREE.CustomMaterial,
        uniforms: {
            map: earthAlbedoTex,
            bumpMap: earthBumpTex,
            bumpScale: 0.02,
            roughness: 0.4,
        }
    });

Which version of THREE are you using? Are you sure your textures are correct? At first glance it looks to me like they might be not texture objects so THREE cannot setup uniforms related to them (matrix specifically).

1 Like

Thanks for the quick reply @Fyrestar

I’m using THREE v0.125.1
I’m pretty sure the textures are correct because they are working with the MeshStandardMaterial

If I log the textures to the console, they look like proper objects and have their matrix field set (to the identity matrix). Is there anything else I can do to troubleshoot?

TIA

It would be good if you could try to replicate your issue in a pen, you can use the demo pen of this as template: https://codepen.io/Fyrestar/pen/jOqyppp other than the guess of invalid values/uniforms it’s hard to tell.

  1. You can check if the material created with extendMaterial has the maps in it’s uniforms set correctly, so the values are actual textures
  2. You might set a conditional breakpoint at the beginning of the function (three.module.js:1051) with the condition m === undefined so you’re able to backtrace what uniform is the issue, depending on your setup it might be something else than textures

Good call, I put a snippet up on extendmat-earth - CodeSandbox

It has both versions of the material in it. You’ll see it doesn’t work with extendMaterial but is fine with the regular version just above it.

@Fyrestar just wanted to check if you saw the last message with the code sandbox. Thanks!

Sorry for the late reply, yes i checked it and apparently there was a issue that probably sneaked in in the previous update, so the uniforms were assigned instead their values to the custom material, it should work now.

Just a small update if you had trouble using it with THREE 127+, i updated it to work with es6 classes as well as previous releases and added a module file to use as module include or with node.

CustomMaterial class is now used by default (you can still tell which one to use such as ShaderMaterial) as in order to use in-built features the material needs to disguise as such anyway.

1 Like

Hello @Fyrestar

For some reason I can’t apply any type of map to the extended Material.

See:

You need to assign the textures to the uniforms when extending the material, at least declare them with null as extendMaterial will add uniforms needed for the type of maps used such as aoMapIntensity, normalScale, bumpScale etc. It tries to only use the uniforms needed rather than always use the entire set of a material.

Declaring at least the maps that will be used, unless you know what special uniforms they will need later such as normalMap requires the normalScale uniform.

extendMaterial( THREE.MeshPhongMaterial, {
	uniforms: {
		map: null,
		normalMap: null,
		...
	}
})

When assigning them later then you also need to assign them to the material so THREE internally recognizes them to setup constants, it treats them as in-built ones.
myMaterial.map = myMaterial.uniforms.map.value = myTexture;

I might add a setter for the maps on the material, but usually you only need it once providing the textures when extending, otherwise do it as above but declare what textures are going to be used.

I also might add a bool to tell if you just want use the entire uniforms set, but it’s inefficient to use just all.

Hello @Fyrestar,

First of all thank you for your quick response! I’m always amazed by how you guys can keep up such dedicated support and answer questions that fast.

I initialized the uniforms object with null values.

If i assign myMaterial.map = myMaterial.uniforms.map.value = myTexture;, I get the error: Uncaught TypeError: uniforms.aoMapIntensity is undefined. If I just do myMaterial.uniforms.map.value = myTexture, I get no error but the texture is not applied

You can watch both behaviours in the pen, in lines 60 and 61

I suppose I’m still doing it wrong

It’s great to get feedback to improve it. I changed a line in extendMaterial so it doesn’t makes the internal in-built uniform refresh from material to uniforms copy which isn’t needed anyway, however i need to check if at such an part there are other things setup in different version of THREE, but so far everything works.

I need to correct: if not used you need to declare then with a empty uniform object { value: null } will adapt it to accept null next time.

There also were some issues in your pen as you assigned to the uniform values another uniform object.

2 Likes

I can’t thank you enough! Everything works now