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;
+}