THREE.js赛车服定制器:叠加图案时底色异常变色问题求助
问题:Three.js赛车服定制器叠加图案后底色视觉偏移(Hex值未变)
我用Three.js开发赛车服定制器,支持选择底色、应用条纹/格纹/心形等图案、调整图案颜色,以及叠加图案后修改底色。但遇到一个问题:叠加图案后,底色视觉上会发生偏移(比如粉色变成深红色),但Hex代码完全没有变化;切换回纯色模式时,底色显示又恢复正常。
技术配置
- 底色:通过HTML颜色选择器获取值
- 图案:使用GIMP制作的透明背景PNG(图案区域不透明、为黑色,背景完全透明)
- 叠加实现:通过ShaderMaterial编写着色器完成图案与底色的混合
已排查内容
- 调整过顶点着色器与片元着色器的逻辑
- 确认PNG图片的背景完全透明、图案区域不透明
核心实现代码如下:
材质更新函数
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; }),
解决方案
问题根源
- 颜色空间不匹配:纯色模式使用的
MeshStandardMaterial会自动处理sRGB到线性空间的转换,但自定义ShaderMaterial中直接使用baseColor的原始值,没有做颜色空间转换,导致底色显示偏暗。 - 冗余的透明属性:ShaderMaterial中开启了
transparent: true,但我们的混合逻辑已经通过alpha手动控制,WebGL的默认透明混合公式会干扰最终颜色显示。 - 材质属性不一致:ShaderMaterial缺少
emissive和emissiveIntensity属性,和纯色模式的材质光照表现不一致,进一步放大颜色差异。
修复代码
更新后的ShaderMaterial实现
child.material = new THREE.ShaderMaterial({ uniforms: { baseColor: { value: baseColor }, patternColor: { value: patternColor }, patternTexture: { value: texture }, emissive: { value: baseColor }, emissiveIntensity: { value: 0.5 } }, 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; uniform vec3 emissive; uniform float emissiveIntensity; varying vec2 vUv; // sRGB转线性空间 vec3 srgbToLinear(vec3 color) { return pow(color, vec3(2.2)); } // 线性空间转sRGB vec3 linearToSrgb(vec3 color) { return pow(color, vec3(1.0/2.2)); } void main() { vec4 texelColor = texture2D(patternTexture, vUv); float alpha = texelColor.a; // 转换为线性空间计算颜色混合 vec3 baseLinear = srgbToLinear(baseColor); vec3 patternLinear = srgbToLinear(patternColor); vec3 outputLinear = mix(baseLinear, patternLinear, alpha); // 添加与纯色模式一致的自发光效果 vec3 emissiveLinear = srgbToLinear(emissive) * emissiveIntensity; outputLinear += emissiveLinear; // 转换回sRGB空间输出 gl_FragColor = vec4(linearToSrgb(outputLinear), 1.0); } `, side: THREE.DoubleSide, name: child.material.name, });
关键修复点说明
- 新增颜色空间转换函数,确保混合计算在线性空间进行,输出时转回sRGB,和Three.js默认材质行为一致。
- 移除
transparent: true,避免默认混合逻辑干扰手动颜色混合。 - 添加
emissive和emissiveIntensityuniforms,让ShaderMaterial的光照表现和MeshStandardMaterial对齐。
内容的提问来源于stack exchange,提问作者Mahi Patel
相关产品推荐
相关产品推荐

