0

I'm building a custom racing silks customizer using THREE.js. My app allows users to: By the way it is something like this website here: https://hylandsportswear.com/kitbuilder/#/customise/80325870?basketIndex=1 my one is just a 2d model and uses a color picker.

Select a base color. Apply a pattern (e.g., stripes, chequered, heart). Adjust the pattern's color. Adjust the base color once pattern is also applied color changes are done using a color picker However, I'm facing an issue where the base color appears to change when a pattern is applied.

The Problem Expected Behavior: The base color remains exactly as selected (e.g., #ff3370), and the pattern is simply overlaid on top of it. Actual Behavior: When a pattern is applied, the visible base color shifts (e.g., from pink to red; for some reason it always goes darker than the intended color but eh primary colors dont have the same issue), although the hex code remains unchanged. When switching back to "solid," the base color is displayed correctly.

my setup includes this: Base Color: User-selected via an HTML . Pattern: A transparent PNG (e.g., a black heart with a transparent background). (used gimp to create it). ShaderMaterial: Used to overlay the pattern on the base color.

I have tried adjusting the fragment shader and vertexshader, i have also made sure my pattern is not the problem. It has a fully transparent background (0 opacity) and then the pattern has (1 opacity and is in the black color).

this is my code for displaying the pattern on top of the base.

const updateMaterials = () => { if (!silkModel) return;

const sections = [
  { name: "jacketmaterial", colorId: "jacketColor", patternId: "jacketPattern", patternColorId: "jacketPatternColor" },
  { name: "lsleevematerial", colorId: "sleevesColor", patternId: "sleevesPattern", patternColorId: "sleevesPatternColor" },
  { name: "rsleevematerial", colorId: "sleevesColor", patternId: "sleevesPattern", patternColorId: "sleevesPatternColor" },
  { name: "capmaterial", colorId: "capColor", patternId: "capPattern", patternColorId: "capPatternColor" },
];

sections.forEach(({ name, colorId, patternId, patternColorId }) => {
  const baseColorValue = document.getElementById(colorId)?.value || "#ffffff";
  const patternColorValue = document.getElementById(patternColorId)?.value || "#ffffff";
  const selectedPattern = document.getElementById(patternId)?.value || "solid";

  const baseColor = new THREE.Color(baseColorValue);
  const patternColor = new THREE.Color(patternColorValue);

  silkModel.traverse((child) => {
    if (child.isMesh && child.material && child.material.name === name) {
      console.log("Updating material for:", name);
      console.log("Base Color:", baseColorValue);
      console.log("Pattern Color:", patternColorValue);
      console.log("Selected Pattern:", selectedPattern);

      let texture =
        selectedPattern === "custom" ? customPatterns.current[name] : patterns[selectedPattern];

      console.log("Texture Info:", texture);

      if (!texture || selectedPattern === "solid") {
        child.material = new THREE.MeshStandardMaterial({
          color: baseColor,
          emissive: baseColor,
          emissiveIntensity: 0.5,
          side: THREE.DoubleSide,
          name: child.material.name,
        });
      } else {
        child.material = new THREE.ShaderMaterial({
          uniforms: {
              baseColor: { value: baseColor },
              patternColor: { value: patternColor },
              patternTexture: { value: texture },
          },
          vertexShader: `
              varying vec2 vUv;
              void main() {
                  vUv = uv;
                  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
              }
          `,
          fragmentShader: `
          uniform vec3 baseColor;
          uniform vec3 patternColor;
          uniform sampler2D patternTexture;
          varying vec2 vUv;
        
          void main() {
            vec4 texelColor = texture2D(patternTexture, vUv);
            float alpha = texelColor.a; // Transparency of the pattern
        
            // If alpha is 0, use the base color only. If alpha > 0, blend the pattern color.
            vec3 outputColor = mix(baseColor, patternColor, alpha);
        
            gl_FragColor = vec4(outputColor, 1.0);
          }
        `,
        
          side: THREE.DoubleSide,
          transparent: true, // Ensures blending with the base color
          name: child.material.name,
      });
      
      }
      child.material.needsUpdate = true;
    }
  });
});

};

heart: textureLoader.load("/textures/black_heart_pattern.png", (texture) => {
  texture.colorSpace = THREE.SRGBColorSpace;
  texture.flipY = false;
}),
4
  • If you add the line #include <colorspace_fragment> after assigning gl_FragColor in your fragment shader, do you see a better result? Commented Jan 21 at 16:48
  • Thank you so much! That helped there is still a slight color difference but its much better! Commented Jan 21 at 19:52
  • Possibly the remaining difference could be due to the emissive in your base material, or tone mapping (if enabled – React Three Fiber would have this on by default). Commented Jan 21 at 23:07
  • Thank You! There is no color difference now! It was due to the tone mapping. Commented Jan 23 at 0:08

1 Answer 1

1

When rendering without post-processing, three.js applies output transforms for display at the end of the fragment shader. These transforms include conversion from the rendering color space (Linear-sRGB, also called the working color space) to the output color space for the canvas (typically sRGB), as well as tone mapping if it's enabled. For a custom ShaderMaterial, you'll probably want to include those shader chunks at the end of your fragment shader:

gl_FragColor = vec4(..., 1.0);

#include <colorspace_fragment>
#include <tonemapping_fragment>

Aside — If you're using post-processing then these transforms are applied as a separate screen-space pass, and wouldn't need to be included in each material's fragment shader. Multiple post-processing implementations exist, but with the default implementation for WebGL these transforms are part of OutputPass.

Updated as of three.js r172.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.