How to properly change GLB model color in PlayCanvas React?

I’m trying to implement color changing functionality for a GLB model in PlayCanvas React. The goal is to change the model’s color when clicking on color swatches in a menu, specifically implementing a “tint without textures” approach.

What I’ve done so far:
Created a custom hook useModelColor and a component ColorableModel

// useModelColor.ts
import { useCallback } from 'react';
import { Color } from 'playcanvas';

export const useModelColor = () => {
  const changeModelColor = useCallback((meshInstances: any[], color: number[]) => {
    if (!meshInstances?.length) {
      console.warn('No meshInstances available');
      return;
    }

    meshInstances.forEach(mesh => {
      if (mesh?.material) {
        const newColor = new Color(color[0], color[1], color[2]);
        mesh.material.diffuse = newColor;
        mesh.material.update();
      }
    });
  }, []);

  return { changeModelColor };
};

// ColorableModel.tsx
import { Entity } from '@playcanvas/react';
import { Render } from '@playcanvas/react/components';
import { useModel } from '@playcanvas/react/hooks';
import { useRef, useEffect } from 'react';
import { useModelColor } from '../hooks/useModelColor';

interface ColorableModelProps {
  src: string;
  selectedColor?: number[];
}

export const ColorableModel = ({ src, selectedColor }: ColorableModelProps) => {
  const { asset } = useModel(src);
  const { changeModelColor } = useModelColor();
  const entityRef = useRef<any>(null);

  useEffect(() => {
    if (!entityRef.current || !selectedColor) return;

    const tryAccessMeshInstances = () => {
      const render = entityRef.current?.components?.render;
      if (render?.meshInstances?.length) {
        console.log('MeshInstances found:', render.meshInstances);
        changeModelColor(render.meshInstances, selectedColor);
      } else {
        console.log('No meshInstances yet, retrying...');
        setTimeout(tryAccessMeshInstances, 100);
      }
    };

    tryAccessMeshInstances();
  }, [selectedColor, changeModelColor]);

  if (!asset) return null;

  return (
    <Entity ref={entityRef}>
      <Render asset={asset} />
    </Entity>
  );
};

The Problem:

The model loads successfully, but I can’t access meshInstances to change the color. I’ve tried several approaches:

  1. Initially tried accessing meshInstances directly through asset.resource

  2. Then attempted to access through Entity’s render component

  3. Implemented a retry mechanism with setTimeout

  4. Added useRef to get access to the Entity

  5. Added detailed logging for debugging

Despite these attempts, meshInstances remains inaccessible even after multiple retry attempts. The model renders correctly, but I can’t modify its color.

Questions:

  1. What is the correct way to access and modify meshInstances in PlayCanvas React?

  2. Is there a proper event or callback I should wait for before attempting to modify the model’s material?

  3. Are there any alternative approaches to changing a GLB model’s color in PlayCanvas React?

  4. Could you please share any examples of implementing similar color-changing functionality?

I would be incredibly grateful for any guidance, examples, or best practices you could share. Thank you in advance for your time and assistance!

@Mark_Lundin might have some opinion here.

Hey @Anton_L, you’re super close. You just need to add the asset as a dependancy of your effect hook. The asset is initially undefined, so the hook needs to run again when the asset is available.

I’ve mocked up a simple model configurator where you can can set material properties on a loaded asset.

image

It basically boils down to something like this…

export function Model() {
  const { asset } = useModel('/model.glb');
  const entityRef = useRef();

  useEffect(() => {
      if (!asset || !entityRef.current) return;

      // Get the components
      const components = entityRef.current.findComponents('render');
  
      // Get the Mesh Instances
      const meshInstances = [];
      for (const comp of components) {
        meshInstances.push(...comp.meshInstances);
      }
      
      // loop over mesh instances  
      meshInstances.forEach(({ material }) => {
        material.diffuse.set(1, 0.5, 0);
        material.update();
      });

    }, [asset]);

  if (!asset) return null

  return <Entity ref={entityRef} >
    <Render type='asset' asset={asset}
  </Entity>
}
2 Likes

Thank you, your answer and example was very helpful! :blush: