Soften WebGL paper rendering
This commit is contained in:
+70
-65
@@ -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 <opaque_fragment>`
|
||||
);
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user