diff --git a/src/components/GenericShape.tsx b/src/components/GenericShape.tsx index 73e7953c..921564df 100644 --- a/src/components/GenericShape.tsx +++ b/src/components/GenericShape.tsx @@ -1,8 +1,11 @@ import { Suspense } from "react"; import { useGLTF, useTexture } from "@react-three/drei"; import { BASE_URL, shapeTextureToUrl, shapeToUrl } from "../loaders"; -import { setupColor } from "../textureUtils"; import { filterGeometryByVertexGroups, getHullBoneIndices } from "../meshUtils"; +import { + createAlphaAsRoughnessMaterial, + setupAlphaAsRoughnessTexture, +} from "../shaderMaterials"; import { MeshStandardMaterial } from "three"; const FALLBACK_URL = `${BASE_URL}/black.png`; @@ -21,11 +24,15 @@ export function ShapeTexture({ material?: MeshStandardMaterial; }) { const url = shapeTextureToUrl(material.name, FALLBACK_URL); - const texture = useTexture(url, (texture) => setupColor(texture)); - material.map = texture; - material.side = 2; - material.transparent = true; - return ; + const texture = useTexture(url, (texture) => + setupAlphaAsRoughnessTexture(texture) + ); + + // Create or reuse shader material that uses alpha channel as roughness + const shaderMaterial = createAlphaAsRoughnessMaterial(); + shaderMaterial.map = texture; + + return ; } export function ShapeModel({ shapeName }: { shapeName: string }) { diff --git a/src/shaderMaterials.ts b/src/shaderMaterials.ts new file mode 100644 index 00000000..552caf93 --- /dev/null +++ b/src/shaderMaterials.ts @@ -0,0 +1,57 @@ +import { + MeshStandardMaterial, + Texture, + RepeatWrapping, + LinearFilter, + LinearMipmapLinearFilter, + SRGBColorSpace, +} from "three"; + +// Shared shader modification function to avoid duplication +const alphaAsRoughnessShaderModifier = (shader: any) => { + // Modify fragment shader to extract alpha channel as roughness after map is sampled + // We need to intercept after diffuseColor is set from the map + shader.fragmentShader = shader.fragmentShader.replace( + "#include ", + ` + #include + // Override roughness with map alpha channel if map exists + #ifdef USE_MAP + roughnessFactor = texture2D(map, vMapUv).a * 1; + #endif + ` + ); +}; + +/** + * Configures a texture for use with alpha-as-roughness materials + * @param texture - The texture to configure + */ +export function setupAlphaAsRoughnessTexture(texture: Texture) { + texture.wrapS = texture.wrapT = RepeatWrapping; + texture.colorSpace = SRGBColorSpace; + texture.flipY = false; + texture.anisotropy = 16; + texture.generateMipmaps = true; + texture.minFilter = LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.needsUpdate = true; +} + +/** + * Creates a reusable shader-enhanced material that treats alpha as roughness + * The same material instance can be used with different textures by setting the `map` property + * @returns A pre-configured MeshStandardMaterial with the shader modifier attached + */ +export function createAlphaAsRoughnessMaterial() { + const material = new MeshStandardMaterial({ + side: 2, // DoubleSide + metalness: 0.0, + roughness: 1.0, + }); + + // Attach shader modifier (will be applied when shader is compiled) + material.onBeforeCompile = alphaAsRoughnessShaderModifier; + + return material; +}