Change material diffuse tint color

I got a red damage tint/flash working with a shared material across many entities. The trick is to set a custom render instance parameter on each entity’s mesh instances. Then override the shader chunk for the diffuse. feel free to use my code here. Hope this helps someone.

import * as pc from 'playcanvas';

export class DamageTint {
    //time to fade back to regular color
    static readonly DMG_FADE_DURATION = 0.4;
    //max tint amount (1.0 is fully red)
    static readonly MaxTintAmount = 0.8; //1.0 is fully red

    static showDamageTintOnEntity(entity: pc.Entity) {
        this.addTintScriptIfNeeded(entity);
        //reset tint timer
        (entity.script.get("damageTint") as DamageTintFade).resetTint();

        this.applyRedTintToEntity(entity, 1.0);
    }


    static initializeRedTintShader(material: pc.StandardMaterial) {
        if (!(material as any).hasRedTintShader) {

            //limit tint to certain materials
            if (!material.name.startsWith("MasterMaterial")) {
                console.log("Only tint master material");
                return;
            }

            console.warn(`Adding a new shader to material '${material.name}' to handle red tinting`);

            // Check if material chunks are available
            if (!material.chunks) {
                console.log("Material does not have chunks");
                return;
            }

            material.chunks.diffusePS = `
            uniform float red_tint_amount;
            uniform vec3 material_diffuse;
            uniform sampler2D texture_diffuseMap;
    
            void getAlbedo() {
                // Start with white as the base color
                vec3 baseColor = vec3(1.0, 1.0, 1.0);
                #ifdef MAPTEXTURE
                vec2 uv = $UV;
                baseColor *= gammaCorrectInput(texture2DBias(texture_diffuseMap, uv, textureBias).$CH);
                #else
                baseColor *= material_diffuse;
                #endif
    
                // Apply the red tint only if the red_tint_amount is greater than 0.0
                if (red_tint_amount > 0.0) {
                    baseColor = mix(baseColor, vec3(1.0, 0.0, 0.0), red_tint_amount);
                }
    
                dAlbedo = baseColor;
            }
        `;

            //parameter for all instances of this material
            material.setParameter('red_tint_amount', 0);

            // Mark the material as updated
            material.update();

            // Mark the material to indicate it has been modified
            (material as any).hasRedTintShader = true;
        }
    }





    static applyRedTintToEntity(entity: pc.Entity, amount: number) {
        // Find all render components in the entity
        var renders: pc.RenderComponent[] = entity.findComponents('render') as pc.RenderComponent[];

        for (var i = 0; i < renders.length; ++i) {
            var meshInstances = renders[i].meshInstances;
            for (var j = 0; j < meshInstances.length; j++) {
                var material = meshInstances[j].material as pc.StandardMaterial;

                // Ensure the shader has been initialized for this material
                this.initializeRedTintShader(material);

                // Apply the red tint amount as a per-instance uniform
                meshInstances[j].setParameter("red_tint_amount", amount);
                meshInstances[j].updateKey();  // This ensures that the instance-specific uniform is applied correctly

            }
        }
    }


    // Static method to add the DamageTint script to the entity
    static addTintScriptIfNeeded(entity: pc.Entity) {
        if (!entity.script) {
            entity.addComponent('script');
        }
        // Check if the entity already has a DamageTint script
        if (!entity.script.has('damageTint')) {
            entity.script.create('damageTint');
        }
    }



    // Static method to reset the red tint on an entity by finding its material
    static resetRedTintOnEntity(entity: pc.Entity) {
        this.applyRedTintToEntity(entity, 0.0);
    }
}

// The DamageTint script that handles fading the red tint over time
class DamageTintFade extends pc.ScriptType {
    private fadeDuration: number;
    private elapsedTime: number;

    initialize() {
        this.fadeDuration = DamageTint.DMG_FADE_DURATION; // Use the standardized fade duration
        this.elapsedTime = 0.0;

    }

    update(dt: number) {
        if (this.elapsedTime < this.fadeDuration) {
            this.elapsedTime += dt;
            let tintAmount = 1.0 - (this.elapsedTime / this.fadeDuration);

            tintAmount = pc.math.clamp(tintAmount, 0.0, 1.0) * DamageTint.MaxTintAmount;


            DamageTint.applyRedTintToEntity(this.entity, tintAmount);
        }
    }

    resetTint() {
        this.elapsedTime = 0.0;
    }
}

pc.registerScript(DamageTintFade, 'damageTint');
export default { DamageTintFade, DamageTint };