From 13f8b60e20e18ee12ee975c19e2f46a97e96f9e7 Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Sat, 6 Jun 2026 03:00:07 +0200 Subject: [PATCH] Improve WebGL leather material --- public/js/webgl-book-lab.js | 79 +++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/public/js/webgl-book-lab.js b/public/js/webgl-book-lab.js index d1fcc91..e2ca1a8 100644 --- a/public/js/webgl-book-lab.js +++ b/public/js/webgl-book-lab.js @@ -175,40 +175,44 @@ const materials = { color: 0x25130b, map: leatherTextures.color, normalMap: leatherTextures.normal, - normalScale: new THREE.Vector2(0.055, 0.055), - roughness: 0.72, + normalScale: new THREE.Vector2(0.07, 0.07), + roughnessMap: leatherTextures.roughness, + roughness: 0.78, metalness: 0.02, - envMapIntensity: 0.08, + envMapIntensity: 0.1, side: THREE.DoubleSide }), hingeLeather: new THREE.MeshStandardMaterial({ color: 0x32180c, map: leatherTextures.color, normalMap: leatherTextures.normal, - normalScale: new THREE.Vector2(0.048, 0.048), - roughness: 0.78, + normalScale: new THREE.Vector2(0.062, 0.062), + roughnessMap: leatherTextures.roughness, + roughness: 0.82, metalness: 0.015, - envMapIntensity: 0.06, + envMapIntensity: 0.08, side: THREE.DoubleSide }), spineBaseLeather: new THREE.MeshStandardMaterial({ color: 0x2a1209, map: leatherTextures.color, normalMap: leatherTextures.normal, - normalScale: new THREE.Vector2(0.042, 0.042), - roughness: 0.82, + normalScale: new THREE.Vector2(0.055, 0.055), + roughnessMap: leatherTextures.roughness, + roughness: 0.86, metalness: 0.01, - envMapIntensity: 0.04, + envMapIntensity: 0.06, side: THREE.DoubleSide }), coverEdge: new THREE.MeshStandardMaterial({ color: 0x5b351b, map: leatherTextures.color, normalMap: leatherTextures.normal, - normalScale: new THREE.Vector2(0.052, 0.052), - roughness: 0.76, + normalScale: new THREE.Vector2(0.068, 0.068), + roughnessMap: leatherTextures.roughness, + roughness: 0.8, metalness: 0.015, - envMapIntensity: 0.05, + envMapIntensity: 0.07, side: THREE.DoubleSide }), pageBlock: new THREE.MeshStandardMaterial({ @@ -252,6 +256,9 @@ const materials = { }), spineCloth: new THREE.MeshStandardMaterial({ color: 0x8e1d18, + normalMap: leatherTextures.normal, + normalScale: new THREE.Vector2(0.04, 0.04), + roughnessMap: leatherTextures.roughness, roughness: 0.82, metalness: 0, envMapIntensity: 0.08, @@ -1840,22 +1847,31 @@ function createLeatherTextures() { const size = 1024; const colorCanvas = document.createElement('canvas'); const normalCanvas = document.createElement('canvas'); + const roughnessCanvas = document.createElement('canvas'); colorCanvas.width = size; colorCanvas.height = size; normalCanvas.width = size; normalCanvas.height = size; + roughnessCanvas.width = size; + roughnessCanvas.height = size; const colorContext = colorCanvas.getContext('2d'); const normalContext = normalCanvas.getContext('2d'); + const roughnessContext = roughnessCanvas.getContext('2d'); const colorImage = colorContext.createImageData(size, size); const normalImage = normalContext.createImageData(size, size); + const roughnessImage = roughnessContext.createImageData(size, size); const heightAt = (x, y) => { const nx = x / size; const ny = y / size; - const longGrain = Math.sin((nx * 18 + Math.sin(ny * 18.8495559215) * 0.24) * 6.28318530718); - const crossGrain = Math.sin((ny * 42 + Math.sin(nx * 12.5663706144) * 0.16) * 6.28318530718); - const poreA = Math.sin((nx * 91 + ny * 37) * 6.28318530718); - const poreB = Math.sin((nx * 47 - ny * 83) * 6.28318530718); - return longGrain * 0.34 + crossGrain * 0.22 + poreA * poreB * 0.16; + const longGrain = Math.sin((nx * 24 + Math.sin(ny * 31.4159265359) * 0.18) * 6.28318530718); + const secondaryGrain = Math.sin((nx * 63 + ny * 9 + Math.sin(ny * 50.2654824574) * 0.1) * 6.28318530718); + const crossGrain = Math.sin((ny * 39 + Math.sin(nx * 18.8495559215) * 0.12) * 6.28318530718); + const poreA = Math.sin((nx * 137 + ny * 71) * 6.28318530718); + const poreB = Math.sin((nx * 97 - ny * 113) * 6.28318530718); + const pebble = Math.sin((nx * 181 + Math.sin(ny * 25.1327412287) * 0.22) * 6.28318530718) * + Math.sin((ny * 167 + Math.sin(nx * 37.6991118431) * 0.18) * 6.28318530718); + const pit = Math.max(0, 0.58 - Math.abs(poreA * poreB)); + return longGrain * 0.22 + secondaryGrain * 0.16 + crossGrain * 0.1 + pebble * 0.18 - pit * 0.24; }; for (let y = 0; y < size; y += 1) { @@ -1863,34 +1879,44 @@ function createLeatherTextures() { const wrappedX = (x + size) % size; const wrappedY = (y + size) % size; const height = heightAt(wrappedX, wrappedY); - const grain = THREE.MathUtils.clamp(0.54 + height * 0.22, 0, 1); - const warm = 0.82 + 0.18 * Math.sin((x * 0.07 + y * 0.013)); + const grain = THREE.MathUtils.clamp(0.58 + height * 0.24, 0, 1); + const warm = 0.86 + 0.1 * Math.sin((x * 0.045 + y * 0.011)) + 0.04 * Math.sin((x * 0.009 - y * 0.031)); const index = (y * size + x) * 4; - colorImage.data[index] = Math.round(132 * grain * warm); - colorImage.data[index + 1] = Math.round(66 * grain * warm); - colorImage.data[index + 2] = Math.round(29 * grain); + colorImage.data[index] = Math.round(118 * grain * warm); + colorImage.data[index + 1] = Math.round(54 * grain * warm); + colorImage.data[index + 2] = Math.round(22 * grain); colorImage.data[index + 3] = 255; const hLeft = heightAt((x - 1 + size) % size, wrappedY); const hRight = heightAt((x + 1) % size, wrappedY); const hDown = heightAt(wrappedX, (y - 1 + size) % size); const hUp = heightAt(wrappedX, (y + 1) % size); - const normal = new THREE.Vector3((hLeft - hRight) * 2.8, (hDown - hUp) * 2.8, 1).normalize(); + const normal = new THREE.Vector3((hLeft - hRight) * 4.1, (hDown - hUp) * 4.1, 1).normalize(); normalImage.data[index] = Math.round((normal.x * 0.5 + 0.5) * 255); normalImage.data[index + 1] = Math.round((normal.y * 0.5 + 0.5) * 255); normalImage.data[index + 2] = Math.round((normal.z * 0.5 + 0.5) * 255); normalImage.data[index + 3] = 255; + + const fiberContrast = Math.abs(hLeft - hRight) + Math.abs(hDown - hUp); + const roughness = THREE.MathUtils.clamp(0.76 + height * 0.1 + fiberContrast * 1.4, 0.5, 0.96); + const roughnessByte = Math.round(roughness * 255); + roughnessImage.data[index] = roughnessByte; + roughnessImage.data[index + 1] = roughnessByte; + roughnessImage.data[index + 2] = roughnessByte; + roughnessImage.data[index + 3] = 255; } } colorContext.putImageData(colorImage, 0, 0); normalContext.putImageData(normalImage, 0, 0); + roughnessContext.putImageData(roughnessImage, 0, 0); const colorTexture = new THREE.CanvasTexture(colorCanvas); const normalTexture = new THREE.CanvasTexture(normalCanvas); - [colorTexture, normalTexture].forEach((texture) => { + const roughnessTexture = new THREE.CanvasTexture(roughnessCanvas); + [colorTexture, normalTexture, roughnessTexture].forEach((texture) => { texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; - texture.repeat.set(2.5, 1.6); + texture.repeat.set(3.6, 2.2); texture.anisotropy = maxTextureAnisotropy; texture.minFilter = THREE.LinearMipmapLinearFilter; texture.magFilter = THREE.LinearFilter; @@ -1898,7 +1924,8 @@ function createLeatherTextures() { }); colorTexture.colorSpace = THREE.SRGBColorSpace; normalTexture.colorSpace = THREE.NoColorSpace; - return { color: colorTexture, normal: normalTexture }; + roughnessTexture.colorSpace = THREE.NoColorSpace; + return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture }; } function createRoomReflectionTexture() {