diff --git a/public/index.html b/public/index.html index 0aec3c1..d83f957 100644 --- a/public/index.html +++ b/public/index.html @@ -280,6 +280,6 @@ console.log(message); }; - + diff --git a/public/js/book-page-format-module.js b/public/js/book-page-format-module.js index 8f12ab2..b4948e8 100644 --- a/public/js/book-page-format-module.js +++ b/public/js/book-page-format-module.js @@ -3,7 +3,7 @@ * Defines the canonical page geometry used by the WebGL book renderer. */ import { BaseModule } from './base-module.js'; -import { calculateProceduralBookThickness, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-loader-quality-fix'; +import { calculateProceduralBookThickness, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-paper-loader-fix'; export const BOOK_TEXTURE_WIDTH = 3072; diff --git a/public/js/book-texture-renderer-module.js b/public/js/book-texture-renderer-module.js index 3941b69..9961b36 100644 --- a/public/js/book-texture-renderer-module.js +++ b/public/js/book-texture-renderer-module.js @@ -62,6 +62,8 @@ class BookTextureRendererModule extends BaseModule { this.pageFormat = this.getModule('book-page-format'); this.pagination = this.getModule('book-pagination'); this.localization = this.getModule('localization'); + this.reportProgress(10, 'Waiting for book fonts'); + if (document.fonts?.ready) await document.fonts.ready; this.reportProgress(20, 'Preparing page texture canvases'); this.createPageCanvases(); this.drawEmptySpread(); @@ -120,18 +122,18 @@ class BookTextureRendererModule extends BaseModule { if (!canvas || !ctx) return; ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = '#fff7dc'; + ctx.fillStyle = '#f2ead0'; ctx.fillRect(0, 0, canvas.width, canvas.height); const shade = ctx.createLinearGradient(0, 0, canvas.width, 0); if (side === 'left') { - shade.addColorStop(0, 'rgba(255, 255, 255, 0.10)'); + shade.addColorStop(0, 'rgba(255, 255, 255, 0.06)'); shade.addColorStop(0.78, 'rgba(255, 255, 255, 0)'); - shade.addColorStop(1, 'rgba(82, 42, 14, 0.16)'); + shade.addColorStop(1, 'rgba(70, 48, 28, 0.08)'); } else { - shade.addColorStop(0, 'rgba(82, 42, 14, 0.16)'); + shade.addColorStop(0, 'rgba(70, 48, 28, 0.08)'); shade.addColorStop(0.22, 'rgba(255, 255, 255, 0)'); - shade.addColorStop(1, 'rgba(255, 255, 255, 0.10)'); + shade.addColorStop(1, 'rgba(255, 255, 255, 0.06)'); } ctx.fillStyle = shade; ctx.fillRect(0, 0, canvas.width, canvas.height); diff --git a/public/js/loader.js b/public/js/loader.js index 9c03a69..b164408 100644 --- a/public/js/loader.js +++ b/public/js/loader.js @@ -24,7 +24,7 @@ const ModuleState = { ERROR: 'ERROR' }; -const MODULE_CACHE_BUSTER = '20260607-webgl-loader-quality-fix'; +const MODULE_CACHE_BUSTER = '20260607-webgl-paper-loader-fix'; window.MODULE_CACHE_BUSTER = MODULE_CACHE_BUSTER; /** diff --git a/public/js/webgl-book-lab.js b/public/js/webgl-book-lab.js index 1be53cc..722a16e 100644 --- a/public/js/webgl-book-lab.js +++ b/public/js/webgl-book-lab.js @@ -4,7 +4,7 @@ import { RenderPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postproces import { SSAOPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SSAOPass.js'; import { SMAAPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/SMAAPass.js'; import { OutputPass } from 'https://esm.sh/three@0.165.0/examples/jsm/postprocessing/OutputPass.js'; -import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-loader-quality-fix'; +import { PROCEDURAL_BOOK, createProceduralBookModel, snapProceduralPageCount } from './procedural-book-model.js?v=20260607-webgl-paper-loader-fix'; const canvas = document.getElementById('scene'); canvas.style.cursor = 'grab'; @@ -182,7 +182,7 @@ const fastFlipOverlap = 5; let activeFlips = []; let pendingPageFlips = 0; -const paperColor = new THREE.Color(0xf1ead2); +const paperColor = new THREE.Color(0xece4ca); const inkColor = '#1a1009'; await reportLabStep(48, 'Preparing high-resolution page textures'); @@ -253,69 +253,69 @@ const materials = { side: THREE.DoubleSide }), pageBlock: new THREE.MeshStandardMaterial({ - color: 0xf4eed8, + color: 0xeee6cc, map: paperTextures.color, normalMap: paperTextures.normal, - normalScale: new THREE.Vector2(0.014, 0.014), + normalScale: new THREE.Vector2(0.008, 0.008), roughnessMap: paperTextures.roughness, roughness: 0.88, metalness: 0, - envMapIntensity: 0.06 + envMapIntensity: 0.025 }), pageEdge: new THREE.MeshStandardMaterial({ - color: 0xf0e5c7, + color: 0xe8ddbe, map: paperTextures.edge, normalMap: paperTextures.normal, - normalScale: new THREE.Vector2(0.012, 0.012), + normalScale: new THREE.Vector2(0.008, 0.008), roughnessMap: paperTextures.roughness, roughness: 0.94, metalness: 0, - envMapIntensity: 0.05 + envMapIntensity: 0.02 }), pageSurface: new THREE.MeshStandardMaterial({ - color: 0xf5efd9, + color: 0xeee6cc, map: paperTextures.color, normalMap: paperTextures.normal, - normalScale: new THREE.Vector2(0.012, 0.012), + normalScale: new THREE.Vector2(0.006, 0.006), roughnessMap: paperTextures.roughness, roughness: 0.9, metalness: 0, emissive: 0x14110b, - emissiveIntensity: 0.012, - envMapIntensity: 0.035, + emissiveIntensity: 0.004, + envMapIntensity: 0.012, side: THREE.DoubleSide }), flipPageSurface: new THREE.MeshStandardMaterial({ - color: 0xf5efd9, + color: 0xeee6cc, roughness: 0.92, metalness: 0, emissive: 0x100d08, - emissiveIntensity: 0.01, - envMapIntensity: 0.02, + emissiveIntensity: 0.004, + envMapIntensity: 0.01, side: THREE.DoubleSide }), leftPage: new THREE.MeshStandardMaterial({ color: 0xffffff, map: leftTexture, normalMap: paperTextures.normal, - normalScale: new THREE.Vector2(0.01, 0.01), + normalScale: new THREE.Vector2(0.004, 0.004), roughnessMap: paperTextures.roughness, roughness: 0.86, metalness: 0, emissive: 0x11100c, - emissiveIntensity: 0.012, + emissiveIntensity: 0.004, side: THREE.DoubleSide }), rightPage: new THREE.MeshStandardMaterial({ color: 0xffffff, map: rightTexture, normalMap: paperTextures.normal, - normalScale: new THREE.Vector2(0.01, 0.01), + normalScale: new THREE.Vector2(0.004, 0.004), roughnessMap: paperTextures.roughness, roughness: 0.86, metalness: 0, emissive: 0x11100c, - emissiveIntensity: 0.012, + emissiveIntensity: 0.004, side: THREE.DoubleSide }), spineCloth: new THREE.MeshStandardMaterial({ @@ -352,12 +352,12 @@ configureBookShadowReceiver(materials.leather, 0.52); configureBookShadowReceiver(materials.hingeLeather, 0.36); configureBookShadowReceiver(materials.spineBaseLeather, 0.34); configureBookShadowReceiver(materials.coverEdge, 0.28); -configureBookShadowReceiver(materials.pageBlock, 0.3); -configureBookShadowReceiver(materials.pageEdge, 0.24); -configureBookShadowReceiver(materials.pageSurface, 0.2); -configureBookShadowReceiver(materials.flipPageSurface, 0.2); -configureBookShadowReceiver(materials.leftPage, 0.18); -configureBookShadowReceiver(materials.rightPage, 0.18); +configureBookShadowReceiver(materials.pageBlock, 0.18); +configureBookShadowReceiver(materials.pageEdge, 0.16); +configureBookShadowReceiver(materials.pageSurface, 0.11); +configureBookShadowReceiver(materials.flipPageSurface, 0.11); +configureBookShadowReceiver(materials.leftPage, 0.08); +configureBookShadowReceiver(materials.rightPage, 0.08); configureBookShadowReceiver(materials.spineCloth, 0.48); configureBookShadowReceiver(materials.headband, 0.62); @@ -369,7 +369,7 @@ await reportLabStep(78, 'Building physical book stack'); buildBook(); notifyBookPageCountChanged(); await reportLabStep(82, 'Loading room reflection texture'); -loadAiRoomReflection(); +await loadAiRoomReflection(); await reportLabStep(86, 'Preparing static shadow and mirror maps'); primeSceneForLoader(); await reportLabStep(90, 'Compiled WebGL scene passes'); @@ -656,11 +656,11 @@ function configureBookShadowReceiver(material, strength) { float sideFill = grazingSide * sideReach; float tableFill = tableReach * (0.16 + underside * 0.22) * (1.0 - upFacing * 0.58); float pageFill = smoothstep(0.02, 0.2, tableDistance) * (1.0 - smoothstep(0.24, 0.72, tableDistance)); - vec3 tableWarmth = vec3(0.042, 0.034, 0.028) * tableFill; - vec3 roomWarmth = vec3(0.032, 0.032, 0.03) * sideFill; - vec3 pageWarmth = vec3(0.032, 0.032, 0.029) * pageFill * grazingSide * (1.0 - upFacing * 0.42); + vec3 tableWarmth = vec3(0.026, 0.024, 0.021) * tableFill; + vec3 roomWarmth = vec3(0.024, 0.024, 0.023) * sideFill; + vec3 pageWarmth = vec3(0.022, 0.022, 0.02) * pageFill * grazingSide * (1.0 - upFacing * 0.42); vec3 indirect = tableWarmth + roomWarmth + pageWarmth; - return albedo * indirect * mix(1.0, 0.86, shadow); + return albedo * indirect * mix(1.0, 0.92, shadow); } float spineClothThread(float coordinate, float frequency, float sharpness) { @@ -690,8 +690,8 @@ function configureBookShadowReceiver(material, strength) { sin((uv.y * 211.0 - uv.x * 53.0) * 6.28318530718); float cloud = sin((uv.x * 17.0 + uv.y * 11.0) * 6.28318530718) * sin((uv.x * 29.0 - uv.y * 23.0) * 6.28318530718); - float fiber = clamp(fleck * 0.008 + cloud * 0.012, -0.02, 0.026); - vec3 paperTint = mix(vec3(0.94, 0.925, 0.875), vec3(1.025, 1.015, 0.97), clamp(0.56 + fiber, 0.0, 1.0)); + float fiber = clamp(fleck * 0.005 + cloud * 0.007, -0.012, 0.014); + vec3 paperTint = mix(vec3(0.965, 0.955, 0.915), vec3(1.01, 1.0, 0.96), clamp(0.5 + fiber, 0.0, 1.0)); return baseLight * paperTint; } @@ -708,7 +708,7 @@ function configureBookShadowReceiver(material, strength) { ${isHardcoverPaper ? 'outgoingLight = hardcoverPaperLight(vBookSurfaceUv, outgoingLight);' : ''} ${isHeadband ? 'outgoingLight = headbandCreviceLight(vBookSurfaceUv, outgoingLight);' : ''} float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength; - outgoingLight *= mix(vec3(1.0), ${isHeadband ? 'vec3(0.16, 0.095, 0.055)' : isHardcoverPaper ? 'vec3(0.68, 0.62, 0.52)' : 'vec3(0.38, 0.29, 0.2)'}, bookReceiverShadow); + outgoingLight *= mix(vec3(1.0), ${isHeadband ? 'vec3(0.16, 0.095, 0.055)' : isHardcoverPaper ? 'vec3(0.82, 0.78, 0.68)' : 'vec3(0.38, 0.29, 0.2)'}, bookReceiverShadow); outgoingLight += bookLocalBounce(vBookReceiverWorldPosition, normalize(vBookReceiverWorldNormal), bookReceiverShadow, diffuseColor.rgb); #include ` ); @@ -1479,11 +1479,11 @@ function configureHardcoverPaperMaterial(material, { useEdgeMap = false } = {}) material.userData.isHardcoverPaper = true; if (!material.map) material.map = useEdgeMap ? paperTextures.edge : paperTextures.color; material.normalMap = paperTextures.normal; - material.normalScale = material.normalScale ?? new THREE.Vector2(useEdgeMap ? 0.012 : 0.01, useEdgeMap ? 0.012 : 0.01); + material.normalScale = material.normalScale ?? new THREE.Vector2(useEdgeMap ? 0.008 : 0.006, useEdgeMap ? 0.008 : 0.006); material.roughnessMap = paperTextures.roughness; - material.roughness = Math.max(material.roughness ?? 0.9, useEdgeMap ? 0.94 : 0.9); + material.roughness = Math.max(material.roughness ?? 0.94, useEdgeMap ? 0.96 : 0.94); material.metalness = 0; - material.envMapIntensity = Math.min(material.envMapIntensity ?? 0.025, 0.035); + material.envMapIntensity = Math.min(material.envMapIntensity ?? 0.012, 0.02); material.needsUpdate = true; } @@ -1568,13 +1568,13 @@ function handlePageCanvases(event) { function drawCanvasPageTexture(canvas, sourceCanvas, side) { const ctx = canvas.getContext('2d'); - ctx.fillStyle = '#fffaf0'; + ctx.fillStyle = '#f2ead0'; ctx.fillRect(0, 0, canvas.width, canvas.height); const shade = ctx.createLinearGradient(0, 0, canvas.width, 0); - shade.addColorStop(0, 'rgba(93, 55, 24, 0.10)'); + shade.addColorStop(0, 'rgba(70, 48, 28, 0.04)'); shade.addColorStop(side === 'left' ? 0.85 : 0.15, 'rgba(255, 255, 255, 0)'); - shade.addColorStop(1, 'rgba(85, 49, 21, 0.08)'); + shade.addColorStop(1, 'rgba(70, 48, 28, 0.04)'); ctx.fillStyle = shade; ctx.fillRect(0, 0, canvas.width, canvas.height); @@ -2599,33 +2599,38 @@ function createRoomReflectionTexture() { } function loadAiRoomReflection() { - new THREE.TextureLoader().load('/assets/webgl/room_reflection_candlelit_study_equirect_4k.png', (texture) => { - texture.colorSpace = THREE.SRGBColorSpace; - texture.mapping = THREE.EquirectangularReflectionMapping; - texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); - texture.minFilter = THREE.LinearMipmapLinearFilter; - texture.magFilter = THREE.LinearFilter; - texture.generateMipmaps = true; - texture.needsUpdate = true; - tableRoomReflectionTexture = texture; - if (tableShader) { - tableShader.uniforms.roomReflectionMap.value = texture; - } - markStaticSceneBuffersDirty(); + return new Promise((resolve) => { + new THREE.TextureLoader().load('/assets/webgl/room_reflection_candlelit_study_equirect_4k.png', (texture) => { + texture.colorSpace = THREE.SRGBColorSpace; + texture.mapping = THREE.EquirectangularReflectionMapping; + texture.anisotropy = renderer.capabilities.getMaxAnisotropy(); + texture.minFilter = THREE.LinearMipmapLinearFilter; + texture.magFilter = THREE.LinearFilter; + texture.generateMipmaps = true; + texture.needsUpdate = true; + tableRoomReflectionTexture = texture; + if (tableShader) { + tableShader.uniforms.roomReflectionMap.value = texture; + } + markStaticSceneBuffersDirty(); - const image = texture.image; - if (!image) return; - const canvas = document.createElement('canvas'); - canvas.width = image.naturalWidth || image.width; - canvas.height = image.naturalHeight || image.height; - const ctx = canvas.getContext('2d'); - ctx.drawImage(image, 0, 0, canvas.width, canvas.height); - generatedTextureCanvases.aiRoomReflection = canvas; - tintAmbientFromCanvas(canvas); - markStaticSceneBuffersDirty(); - }, undefined, () => { - tintAmbientFromCanvas(generatedTextureCanvases.roomReflection); - markStaticSceneBuffersDirty(); + const image = texture.image; + if (image) { + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth || image.width; + canvas.height = image.naturalHeight || image.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0, canvas.width, canvas.height); + generatedTextureCanvases.aiRoomReflection = canvas; + tintAmbientFromCanvas(canvas); + markStaticSceneBuffersDirty(); + } + resolve(texture); + }, undefined, () => { + tintAmbientFromCanvas(generatedTextureCanvases.roomReflection); + markStaticSceneBuffersDirty(); + resolve(tableRoomReflectionTexture); + }); }); }