Create Grouting from GLSL shaders / draw line around the texture image

I am creating the grouts around the image texture before repeating it. in this way if color or thickness needs to changed of grout then also I have to create textures again and repeat them.

now trying to find a way by which create a separate layer / mesh just before the actual wall mesh to avoid creating textures every single tile when updating the grout properties / params.

only grout properties should be updated and not texture needs to be as it is.

grouts are the lines around the actual image texture.

any suggestion or guidance helps

Thanks :slight_smile:

codepen : https://codepen.io/Vatsal-Pandya-the-solid/pen/PovEqgp

texture function :

function updateTexture({ element, rotateAngle = 90 }) {
  const tileData = tilesData[Math.floor(Math.random() * tilesData.length)];
  let imageUrls = tileData.imgUrl;
  // Array to hold loaded textures
  let textures = [];
  // Load images asynchronously
  Promise.all(imageUrls.map(loadImage))
    .then((images) => {
      // Process each loaded image
      images.forEach((image) => {
        // Create a canvas and its context
        const canvas = document.createElement("canvas");
        const context = canvas.getContext("2d");
        // Draw the image on the canvas
        canvas.width = nextPowerOf2(image.width);
        canvas.height = nextPowerOf2(image.height);
        context.drawImage(image, 0, 0, canvas.width, canvas.height);
        // Draw grout lines if they are enabled
        if (grout_v_width > 0 || grout_h_width > 0) {
          context.strokeStyle = grout_color; // Use updated grout color
          context.globalAlpha = grout_alpha;

          // Draw vertical grout lines
          if (grout_v_width > 0) {
            context.lineWidth = grout_v_width;
            context.beginPath();
            context.moveTo(0, 0);
            context.lineTo(canvas.width, 0);
            context.closePath();
            context.stroke();
            context.beginPath();
            context.moveTo(0, canvas.height);
            context.lineTo(canvas.width, canvas.height);
            context.closePath();
            context.stroke();
          }

          // Draw horizontal grout lines
          if (grout_h_width > 0) {
            context.lineWidth = grout_h_width;
            context.beginPath();
            context.moveTo(0, 0);
            context.lineTo(0, canvas.height);
            context.closePath();
            context.stroke();
            context.beginPath();
            context.moveTo(canvas.width, 0);
            context.lineTo(canvas.width, canvas.height);
            context.closePath();
            context.stroke();
          }

          context.globalAlpha = 1;
        }
        const texture = new THREE.Texture(canvas);
        texture.premultiplyAlpha = false;
        texture.needsUpdate = true;
        texture.wrapS = THREE.RepeatWrapping;
        texture.wrapT = THREE.RepeatWrapping;

        texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

        texture.repeat.set(5, 10);

        textures.push(texture);
      });
      // Apply textures randomly to the element's material
      const randomTextureIndex = Math.floor(Math.random() * textures.length);
      element.material.map = textures[randomTextureIndex];
      element.material.needsUpdate = true; // Important to update the material
      renderer.render(scene, camera);
    })
    .catch((error) => {
      console.error("Error loading images:", error);
    });

  // Function to load an image
  function loadImage(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = "anonymous"; // Ensure cross-origin loading
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = url;
    });
  }
}