Texture transform bug?

I’m trying to do uv offset animations at runtime. My script looks like this:

import { Script } from 'playcanvas';

/** @enum {string} */
const TextureMap = {
    DIFFUSE: 'diffuseMap',
    EMISSIVE: 'emissiveMap',
    OPACITY: 'opacityMap'
};

export class UvOffsetAnimation extends Script {

    static scriptName = 'uvOffsetAnimation';

    /**
     * @attribute
     * @type {TextureMap}
     */
    map = TextureMap.EMISSIVE;

    /**
     * @attribute
     */
    uSpeed = 1.0;

    /**
     * @attribute
     */
    vSpeed = 1.0;

    uTime = 0;
    vTime = 0;

    initialize() {
        // Need to set map offset to some other value than 0. Else texture map transforms wont be defined in vertex shader
        this.entity.render.material[`${this.map}Offset`].set(1.0, 1.0);
    }

    /**
     * Called for enabled (running state) scripts on each tick.
     * 
     * @param {number} dt - The delta time in seconds since the last frame.
     */
    update(dt) {
        this.uTime += dt * this.uSpeed;
        this.vTime += dt * this.vSpeed;
        this.uTime %= 1.0;
        this.vTime %= 1.0;

        this.entity.render.meshInstances.forEach((meshInstance) => {
            meshInstance.setParameter(`texture_${this.map}Transform0`, [1.0, 0.0, this.uTime]);
            meshInstance.setParameter(`texture_${this.map}Transform1`, [0.0, 1.0, this.vTime]);
        });
    }
}

This will work as long as I only animate 1 texture per material. However when using the same material on multiple entities and wanting to animate diffuse on one entity and emissive on the other this breaks and only the diffuse entity is animated.

Inspecting with spectorjs shows the texture_emissiveMapTransform is missing in the vertex shader. Why?

Check this example:
https://playcanvas.com/project/1429322/overview/uvoffsetanimation

PS: I know I can use emissiveMapOffset API and similar, however I’d like to only have one material with different uv animation offsets per entity/meshinstance.

I think the shader generator checks and only uses unique matrices. If two matrices are the same, only a single will end up in the shader.

Try changing this line:
this.entity.render.material[${this.map}Offset].set(1.0, 1.0);

to different values per channel. or just random values maybe.

Yup that seems to be the issue. Works after changing it to the following:
this.entity.render.material[${this.map}Offset].set(Math.random(), 1.0);

It is kind of weird behaviour if I explicitly set both diffuseMapOffset and emissiveMapOffset to [1.0, 1.0], that there is only one offset defined in the vertex shader.

I’d love to have some API to instruct the StandardMaterial to force the inclusion of specific transforms. Something similar to the setDefine API. Then we also wouldn’t need to set the offset to some value which is not zero.
What do you think about that?

It’d be a very rarely used API that I would probably prefer to avoid. I see the usefulness, but not sure it’s worth it given a simple (but not very nice) workaround. There are plans for larger refactor of material internals as well as uniform overrides from mesh instance, so some of this might change already.

1 Like