How can I lerp between two texture in material?

Hi guys. I want to make transition when updating material texture fields. Just like:

uniform sampler2D fromTex;
uniform sampler2D toTex;
uniform float percent;

vec4 fromColor = texture2D(fromTex, uv);
vec4 toColor = texture2D(toTex, uv);
vec4 finalColor = mix(fromColor, toColor, percent);

But I have no idea how to do it in playcanvas. Can anyone help ? Thanks.

Hi @swh,

You can override a shader chunk on a material to apply your custom shader code. Check this tutorial to get started:

https://developer.playcanvas.com/en/tutorials/warp-a-sprite-with-glsl/

1 Like

Thanks for your tips.

Is that mean if I want to support interpolation for all texture fields(diffuseMap, emissiveMap, opacityMap etc.), I need to override all shader chunks of a material ?

I am looking a way to implement this globally, just like:

pc.StandardMaterial.prototype.updateProps = function (key, value) {
  var oldValue = this[key];
  this[key] = value;
  
  this.setParameter(`material_${key}_old`, oldValue);
  this.setParameter(`material_${key}_timer`, 0);
};

pc.shaderChunks.diffusePS = `...hack by me`;
pc.shaderChunks.emissivePS = `...hack by me`;
pc.shaderChunks.opacityPS = `...hack by me`;
// ...

This way is clearer than the wrapper script in the demo.

What do you think ?

Yes you can’t get interpolation automatically working without custom GLSL, and yes you will have to override each material channel you are interested in applying this effect.

I think you got it right, that’s the way I would approach it:

  • pass the old map as a shader uniform using setParameter on the material
  • pass a timer the same way that controls the effect
  • have the shader calculate the interpolation.
1 Like

Just a note, I am not sure you can override globally the pc.StandardMaterial that way.

I think you can use a special callback that is available for that purpose, to update the shader after it’s being generated, per material:

https://developer.playcanvas.com/en/api/pc.StandardMaterial.html#onUpdateShader

1 Like

My purpose is to support interpolation for all number/vector/color/texture fields. The main problem is lerp between two textures, other type of values are done.

I don’t think the onUpdateShader interface will help, because what I need is add timer and material_xxxMap_old into the original shader.

If the engine built in this feature, it will be great. :sob: @will

1 Like

You will have to rebuilt a lot of things to support this, the standard material class is quite extensive.

For arithmetic values, that can be done on the JS side and that’s better for performance (instead of doing it per pixel on a shader).

Still a valid feature request I think, you can submit it on the engine repository in addition.

2 Likes

You will have to rebuilt a lot of things to support this, the standard material class is quite extensive.

No, number/vector/color fields can be updated in the update callback, it doesn’t matter. What I need to do is hack the texture related shader chunks. Such as diffusePS, emissivePS etc.

1 Like

We’re busy implementing node graph shader system - when this is ready, it will be a lot easier to make customizations you ask for.

If you need this texture lerp only for small number of textures, you could consider writing a simple shader which just lerps two textures. And then you could use it to blend each set of two textures into render target, and attach the result to material.

That would use a lot of memory / performance for many textures though, but would be relatively simple to implement.

There is also a support for detail maps. It exists in engine (and so you can use it with script), but not in Editor yet. https://github.com/playcanvas/engine/pull/1968. I think it only handles diffuse / normal maps./

But it does not have lerp mode to blend textures, so that would have to be added (you could possibly overwrite detailModes.frag to replace existing mode by lerp.

3 Likes

I’ve noticed the detail map months ago. It is really great!.

BTW, when will the node graph shader system ready? I am really excited about this system! :smiley:

It’s a while off unfortunately. You can see progress ongoing here: https://github.com/playcanvas/engine/pull/2206

2 Likes

I actually added this exact thing on my own project, took it the next level with a progressive transition using a “virtual sphere”. The code itself actually isn’t that complicated. I had a transition variable that updates in my update() and lerp the texture based on the transition float passed into the diffuse shader chunk. Very briefly this is what’s going on (don’t hold me to best practices)

Script

var transitionProgress = 0, transitionDuration = 2;
script.prototype.setupDiffuseChunk = function() {
    this.materialTransitioning.setParameter('tex1', this.app.assets.getByName('tex1').resource);
    this.materialTransitioning.setParameter('tex2', this.app.assets.getByName('tex2').resource);
    this.materialTransitioning.chunks.diffusePS = this.app.assets.getByName('diffuseChunk').resource;
    this.materialTransitioning.update();
}
script.prototype.update = function(dt) {
    transitionProgress += dt;
    this.materialTransitioning.setParameter('transition', transitionProgress / transitionDuration);
    this.materialTransitioning.update();
}

diffuseChunk shader

uniform sampler2D tex1, tex2;
uniform float transition;

vec3 lerp(vec3 a, vec3 b, float t) {
    return a + t * (b - a);
}

void getAlbedo() {
    dAlbedo = gammaCorrectInput(addAlbedoDetail(lerp(texture2D(tex1, $UV).$CH, texture2D(tex2, $UV).$CH, transition)))
}
3 Likes

Hmm, your solution looks like the wrap-a-sprite-with-glsl mentioned by @Leonidas .