PlayCanvas does not have this concept of groups that can be faded in or out (or in general groups where the parent can have material properties that affect child Entities).
So the best way to do this is to have a generic function to fade a material in / out and then apply it on all the materials that you want. You can get creative with the way you want to get those materials but you could just iterate all the child Entities and if they have a model component then get the material for all the mesh instances of the model and fade it in / out.
Like you said you shouldn’t use setInterval for things that are changing over time, because that won’t be smooth, and it will not pause when the application is paused for example etc. You always want to use the application’s dt for these things.
Now there are lots of ways one could do this. In your case you have a parent Entity and you want to fade all its children. So you could have a script on the parent Entity and in the update method, go through all the children of the Entity, and call set_parameter on all the mesh instances of the materials. For example something like this (I haven’t tested this so might have some typos):
fadeIn = function (duration) {
this.opacityStart = 0;
this.opacityEnd = 1;
this.duration = duration;
this.timer = 0;
this.fade = true;
}
fadeOut = function (duration) {
this.opacityStart = 1;
this.opacityEnd = 0;
this.duration = duration;
this.timer = 0;
this.fade = true;
}
update = function (dt) {
if (this.fade) {
this.timer += dt;
if (this.timer >= this.duration) {
this.fade = false;
}
var opacity = pc.math.lerp(this.opacityStart, this.opacityEnd, Math.min(this.timer / this.duration, 1));
for (var i = 0; i < this.entity.children.length; i++) {
var child = this.entity.children[i];
if (! child.model) continue;
var meshInstances = child.model.meshInstances;
for (var mi = 0; mi < meshInstances.length; mi++) {
var material = meshInstances[mi].material;
if (! material) continue;
material.setParameter('material_opacity', opacity);
}
}
}
}
Up until now I was using the dt value to set and update the material opacity directly. I think it gets around 6 updates over the course of 2 seconds, so it fades in 6 steps that is. Any pros or cons of using Lerp instead?
These things are usually done by interpolating a value between 2 other values. For example in your case you interpolate between 0 (transparent) and 1 (opaque). So what you usually do is have a variable that acts as a timer on which you add dt, another variable that specifies the total duration of the interpolation and then you calculate a value t that goes between 0 and 1 by dividing your timer with the duration. To achieve different easing methods you pass the t from an easing function and then finally you call lerp. Here are some easing functions:
And some pseudocode:
this.timer += dt;
var t = easing(this.timer / this.duration);
var opacity = pc.math.lerp(0, 1, t);
setParameter just changes uniform value directly, and nothing else.
While setting value on material does does not changes parameter, then calling update will revalidate whole material and re-create shader, possibly leading to recompilation - sometimes it is desired by user.
But if you do know that your shader has opacity already (should be not 1.0 initially), then setParameter - is way better from performance point of view.
Material tries to be clever, if it has opacity 1.0, then it wont even include uniform for regulating opacity into a shader. For performance reasons.
So you wont be able to regulate opacity property from setParameter. But if you set opacity to 0.999 for example, then it will include it into shader code, and setParameter will have effect.
Does setParameter still defines uniforms even if material has no uniform in shader? Yes it does, but it is not used.
As said before, both methods work, but update on material - is expensive, and if you do call update every loop for many materials - you will start loosing FPS and potentially lead to shader recompilations which lead to frame freezes, depending on complexity of scene.
Actually, I’m not following anymore. But this is the code I used, and it seems to work perfectly.
Fade.prototype.fadeIn = function() {
console.log("ENTER fadeIn()");
this.opacityStart = 0;
this.opacityEnd = 1;
this.timer = 0;
this.fade = true;
};
Fade.prototype.fadeOut = function() {
console.log("ENTER fadeOut()");
this.opacityStart = 1;
this.opacityEnd = 0;
this.timer = 0;
this.fade = true;
};
// update code called every frame
Fade.prototype.update = function(dt) {
//console.log("ENTER update, this.fade=" + this.fade);
if (this.fade) {
// Increase timer with dt
this.timer += dt;
// Break fade if timer reached duration
if (this.timer >= this.duration) {
this.fade = false;
}
// Quint easing
var t = this.ease(this.timer / this.duration);
var opacity = pc.math.lerp(this.opacityStart, this.opacityEnd, t);
// Linear interpolation easing lerp
//var opacity = pc.math.lerp(this.opacityStart, this.opacityEnd, Math.min(this.timer / this.duration, 1));
// Loop through meshes of this model.
for (var i = 0; i < this.meshes.length; i++) {
var material = this.meshes[i].material;
if (! material) continue;
material.setParameter('material_opacity', opacity);
}
}
};
// Easing function
Fade.prototype.ease = function(t) {
return (t<0.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t);
};
I also added this in Initialize to be able to set opacity on the materials:
this.meshes = this.entity.model.meshInstances;
// Loop throug materials and enable blending
// Set blend type to normal to allow for opacity blending.
for (i = 0; i < this.meshes.length; i++) {
this.meshes[i].material.blendType = pc.BLEND_NORMAL;
}
Bear in mind, that material - might be shared between models.
If you want to override uniform for specific mesh, then use: this.meshes[i].setParameter(...