Reputation: 1
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;
}),
Upvotes: 0
Views: 36
Reputation: 11980
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.
Upvotes: 1