playcanvas / engine

JavaScript game engine built on WebGL, WebGPU, WebXR and glTF
https://playcanvas.com
MIT License
9.7k stars 1.36k forks source link

Cloning the element material and assigning it back to the element causes it to 'disappear' #3534

Open yaustar opened 3 years ago

yaustar commented 3 years ago

Repro: https://playcanvas.com/editor/project/836052 Forum: https://forum.playcanvas.com/t/ui-elements-are-not-being-rendered-after-cloning-material/22214

Hey hey :slight_smile:

We are currently working on a custom shader chunk to add to one of our ui elements. The chunk itself works fine, but we noticed that it also affects all of our other ui elements. They all share the same material, so this makes sense. We tried cloning the material, adding the chunk, setting the new material on our element and updating it. But now the ui element is not visible at all and we have no idea how to fix it.

After a bit of testing, this is the minimal code to try and reproduce the issue:

TestScript.prototype.initialize() {
    var material = this.entity.element.material.clone();
    this.entity.element.material = material;
    material.update();
}

Our element originally has a sprite reference, so maybe by setting the material property we are actually overriding the sprite asset? Is there a different way to change the material? Or are we missing something else?

Thanks for the help

jpauloruschel commented 3 years ago

The issue seems to be around these 2 lines in image-element.jswhen assigning a new material to the element: https://github.com/playcanvas/engine/blob/master/src/framework/components/element/image-element.js#L986

...
// if this is not the default material then clear color and opacity overrides
if (this._hasUserMaterial()) {
    this._renderable.deleteParameter('material_opacity');
    this._renderable.deleteParameter('material_emissive');
} 
...

The material that you are setting is a clone from the StandardMaterial, so even though it is technically a "user material" (not the default one), it still requires those 2 parameters. A temporary workaround is to manually re-set those values after you set the element's material, but please note that the following code uses private APIs.

...
this.entity.element._image._renderable.setParameter('material_opacity', 1);
this.entity.element._image._renderable.setParameter('material_emissive', [1, 1, 1]);

A slightly better client-side workaround is to trigger a call that also resets those values, such as:

...
this.entity.element.opacity = 0;
this.entity.element.opacity = 1;
this.entity.element.color = new pc.Color(0, 0, 0);
this.entity.element.color = new pc.Color(1, 1, 1);

Or:

...
var texture = this.entity.element.texture;
this.entity.element.texture = null;
this.entity.element.texture = texture;

There are multiple other references to those two parameters (material_opacity and material_emissive), so I'll dig deeper in order to find a more elegant approach on the engine side. I'll keep this thread updated.

jpauloruschel commented 3 years ago

As mentioned in AliMoe's post, the default UI material that the code is cloning from defines non-default emissive and opacity values (on _createBaseImageMaterial). The reason this was done is so that the shader compiler doesn't optimise those values out. When you do this.entity.element.opacity or .color, you are not setting the material parameters, but rather the meshInstance's (which overrides the material's).

So actually the best way to deal with this is to set the opacity and emissive parameters directly on the cloned material, like so:

...
material.emissive.set(1, 1, 1);
material.opacity = 1;
material.update();

This will avoid having to deal with custom mesh instances overrides, and resetting to different values before setting to the values you actually want. We're still looking into a better engine-side fix.

mvaligursky commented 3 years ago

related: https://github.com/playcanvas/engine/issues/3549

yaustar commented 2 years ago

Did reach a conclusion or fix for this in the end?

yaustar commented 2 years ago

From the duplicate ticket:

Repro: https://playcanvas.com/editor/scene/1331078

Launch the project and the image element will not be shown

This is due to when a material is set on the element, two parameters are deleted: https://github.com/playcanvas/engine/blob/dev/src/framework/components/element/image-element.js#L980

After talking to @jpauloruschel, we think that the best route to go here is in this check, also check if the material has the same parameters used in the default image element material (material_emissive and material_opacity) and if so, set those parameters on the renderable. If not, then delete the parameters.

Workaround for now is to dirty the color and opacity values:

    // Dirty the element to reapply values to parameters to the renderable mesh
    var tempColor = this.entity.element.color.clone();
    this.entity.element.color = new pc.Color(tempColor.r, tempColor.g + 0.01, tempColor.b);
    this.entity.element.color = tempColor;

    var tempOpacity = this.entity.element.opacity;
    this.entity.element.opacity = 0;
    this.entity.element.opacity = tempOpacity;