CanvasTexture doesn't show loaded images on mesh

Hello,
I want to dynamicaly combine two images into a texture that will be assigned to a glb model. I suppose the best solution for this should be creating a canvas and then assign it via the CanvasTexture.

I display the canvas for debugging purposes and I have no trouble rendering any kind of shapes (like arcs and rectangles) and even the desired images. The issue is that I see only the shapes from the canvas on the model, no images whatsoever. I believe they are loaded correctly as I can see them rendered on the canvas.

On a side note, the model is meshopt compressed, if that helps to solve this.

Here is my code snippet:

// Set up the canvas
const canvas = document.getElementById("canvas");
if (canvas.getContext) {
    canvas.width = 4096;
    canvas.height = 4096;
    ctx.fillStyle = "#ffffff";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const radius = 1024;

    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = "green";
    ctx.fill();
    ctx.lineWidth = 5;
    ctx.strokeStyle = "#003300";
    ctx.stroke();

    var img = new Image();
    img.onload = function () {
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
     };
    img.src = "https://upload.wikimedia.org/wikipedia/commons/d/d6/5120x2880-dark-blue-solid-color-background.jpg";
}

const bodyTexture = new THREE.CanvasTexture(canvas);
bodyTexture.needsUpdate = true;

// to display the texture correctly on the meshopt compressed mesh
const modelMaterial = node.material;
bodyTexture.repeat.copy(modelMaterial.map.repeat);
bodyTexture.offset.copy(modelMaterial.map.offset);

// assign the material to the mesh
const material = new THREE.MeshPhysicalMaterial({map: bodyTexture});
node.material = material;

Thanks in advance!

Its an async problem, basically the CanvasTexture is generated before the image get loaded and drawn, you can solve this by using a Promise and await for the image to be loaded and drawn.

Or you can move this line const bodyTexture = new THREE.CanvasTexture(canvas); up, and set the texture texture.needsUpdate = true at the onLoaded callback :

const canvas = document.getElementById("canvas");
const bodyTexture = new THREE.CanvasTexture(canvas);
if (canvas.getContext) {
    ...
    img.onload = function () {
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        bodyTexture.needsUpdate = true;
    };
    ...
}

Thanks a lot! Sometimes it’s hard to wrap my head around async calls.

1 Like