Reputation: 1442
I have the .obj of a T-Shirt, it contains a few meshes and materials and I'm coloring it using a CanvasTexture
fed by an inline svg.
Now I should add a logo at a specific location (more or less above the heart), but I'm struggling to understand which is the best/proper way of doing it (I'm quite new to 3D graphics and Three.js). This is what I tried so far:
since I'm coloring the T-Shirt through a CanvasTexture
fed by an inline svg, I thought it would have been easy to just draw the logo into the svg at specific coordinates. And it was easy indeed, but the logo gets not rendered (or is not visible in some way) on the texture/mesh, although it is visible in the inline svg. So CanvasTexture
probably doesn't work with embedded images (I tried both base64 and URL)
so, I started looking into more 3d "native" ways of doing it, but I haven't found one that really makes sense to me. I know there's ShaderMaterial
in threejs, which I could use to selectively render pixels of the logo or pixels of the cloth, but that means making a lot of complex computation to figure out where the logo should be and I can't believe drawing a simple JPEG or PNG with specific coordinates and size can be so complex... I must have missed an obvious solution.
EDIT
Here is how I'm adding the image to the inline svg (option 1 above).
const groups = Array.from(svg.querySelectorAll('g'));
// this is the "g" tag where I want to add the logo into
const targetGroup = groups.find((group: SVGGElement) => group.getAttribute('id') === "logo_placeholder");
const image = document.createElement('image');
image.setAttribute('width', '64');
image.setAttribute('height', '64');
image.setAttribute('x', '240');
image.setAttribute('y', '512');
image.setAttribute('xlink:href', `data:image/png;base64,${base64}`);
targetGroup.appendChild(image);
static drawSvgToCanvas = async (canvas: HTMLCanvasElement, canvasSize: TSize, svgString: string) => {
return new Promise((resolve, reject) =>
canvas.width = canvasSize.width;
canvas.height = canvasSize.height;
const ctx = canvas.getContext('2d');
const image = new Image(); // eslint-disable-line no-undef
image.src = `data:image/svg+xml;base64,${btoa(svgString)}`;
image.onload = () => {
if (ctx) {
ctx.drawImage(image, 0, 0);
resolve();
} else {
reject(new Error('2D context is not set on canvas'));
}
};
image.onerror = () => {
reject(new Error('Could not load svg image'));
}
});
};
const texture = new Three.CanvasTexture(canvas);
texture.mapping = Three.UVMapping; // it's the default
texture.wrapS = Three.RepeatWrapping;
texture.wrapT = Three.RepeatWrapping; // it's the default
texture.magFilter = Three.LinearFilter; // it's the default
texture.minFilter = Three.LinearFilter;
texture.needsUpdate = true;
[...add texture to material...]
Upvotes: 0
Views: 953
Reputation: 13047
For some reason, canvases don't like SVGs with embedded images, so for a similar project I had to do this in two steps, rendering the SVG and the image separately:
First, render the SVG on the canvas, and then render the image on top of that (on the same canvas).
const ctx = canvas.getContext('2d');
ctx.drawImage(imgSVG, 0, 0);
ctx.drawImage(img2, 130, 10, 65, 90);
const texture = new THREE.CanvasTexture(canvas);
Example: https://jsfiddle.net/0f9hm7gx/
Upvotes: 0