Material.setParameter not making visual changes sometimes first time after app starts?

Hi

I’m loading a texture from an url and setting it as an emissive map for a material on a model like so:

var asset = new pc.Asset(assetName, "texture", { url: jpgFileUrl });
this.app.assets.add(asset);
this.app.assets.load(asset); 
asset.on("load", function (asset) {
    entity.model.model.meshInstance[0].material.setParameter("texture_emissiveMap", asset.resource); 
}, this);

The problem is that right after page reload this works only some of the time. I’ve put in a console.log to check the emissiveMap with getParameter after setParameter and the texture appears to be the correct one loaded from the url at least judging by asset.resource.name, but visually it is still the old texture on the model on the screen so something is going wrong. The weird thing is that after the first time the same function always works correctly. It is only after page reload that it fails and even then only around 50% of the time.

Is this a bug with playcanvas or what could be causing this weird behavior?

Hi @MikkoK,

The setParameter() will update a material uniform in the compiled shader, meaning that the shader needs to include the relevant shader chunks otherwise it will silently fail.

To change the emissive map using that method in this case means that the material should already be using an emissive map that you will be swapping.

A common trick is to provide a default one e.g. a small white one or one of your existing textures. That will force the engine to include the emissive shader chunk to the shader and the method will work.

1 Like

Another way to avoid using setParameter() the first time, is to set the map using standard material properties assignments and calling material.update() in the end.

That will force the material to recompile the shader and on subsequent runs you can use setParameter().

material.emissiveMap = asset.resource;
material.update();

Hi, thanks for the quick reply. After some investigating I’ve noticed that even though the entities have a material with an emissive map set in the editor from the start, entity.model.model.meshInstances[0].material.getParameter("texture_emissiveMap") returns undefined at start.

The entities are disabled in the editor at the start and I thought that might have something to do with it but even with having them enabled in the editor at the start getParameter still returns undefined for texture_emissiveMap. So apparently for some reason it’s not compiling the shader with the emissive map even though the material has them?

I would like to avoid using material.update as that seems to be slower than just using setParameter and creates a noticeable stutter when changing a texture.

I think that’s the issue, the materials even though they may be set to preload, they will compile the first time they are used in scene (e.g. a model that uses them renders).

You can try to use both in your code without materia.update().

material.emissiveMap = asset.resource;
entity.model.model.meshInstance[0].material.setParameter("texture_emissiveMap", asset.resource); 

I think, though you have to try it, that the first time the material is used and uses update() internally to compile the shader, it will pick up your new texture here.

That did the trick, thanks!

For future reference, at what point are models rendered so that shaders get compiled? Apparently it is not enough to have them enabled in editor if they are then disabled at the start in the initialize function. I guess one frame has to pass with them enabled?

1 Like

I think materials generate their shaders as soon as models that using them are rendered. If you are using frustum culling in your camera that means when they come into view. @mvaligursky

And yes that doesn’t have to do with the enabled state of an entity.

1 Like