Add WebGL cloth and paper materials

This commit is contained in:
2026-06-06 08:03:45 +02:00
parent 13f8b60e20
commit 67c0c4e7e3
2 changed files with 300 additions and 33 deletions
+17 -6
View File
@@ -208,6 +208,7 @@ function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth, co
const uAt = (x) => (x - leftX) / (rightX - leftX || 1); const uAt = (x) => (x - leftX) / (rightX - leftX || 1);
const vAt = (z) => (z + halfDepth) / depth; const vAt = (z) => (z + halfDepth) / depth;
const pointAt = (x, y, z) => ({ x, y, z, u: uAt(x), v: vAt(z) }); const pointAt = (x, y, z) => ({ x, y, z, u: uAt(x), v: vAt(z) });
const coverProfileXs = section.map((point) => point.x);
const edgeProfile = Array.from({ length: edgeSteps + 1 }, (_, index) => { const edgeProfile = Array.from({ length: edgeSteps + 1 }, (_, index) => {
const angle = Math.PI * 0.5 - (index / edgeSteps) * Math.PI; const angle = Math.PI * 0.5 - (index / edgeSteps) * Math.PI;
return { return {
@@ -221,14 +222,24 @@ function createCoverAssemblyGeometry(pageWidth, depth, thickness, spineWidth, co
const cornerRadius = Math.max(0.0001, edgeRadius - inset); const cornerRadius = Math.max(0.0001, edgeRadius - inset);
const points = []; const points = [];
const pushLinear = (fromX, fromZ, toX, toZ, steps) => { const pushLinear = (fromX, fromZ, toX, toZ, steps) => {
for (let step = 0; step <= steps; step += 1) { const candidates = [];
if (points.length && step === 0) continue; for (let step = 0; step <= steps; step += 1) candidates.push(step / steps);
const t = step / steps; if (Math.abs(fromZ - toZ) < 0.000001) {
points.push({ coverProfileXs.forEach((x) => {
x: THREE.MathUtils.lerp(fromX, toX, t), const t = (x - fromX) / (toX - fromX || 1);
z: THREE.MathUtils.lerp(fromZ, toZ, t) if (t > 0 && t < 1) candidates.push(t);
}); });
} }
candidates
.sort((a, b) => a - b)
.forEach((t) => {
if (points.length && Math.abs(t) < 0.000001) return;
const x = THREE.MathUtils.lerp(fromX, toX, t);
const z = THREE.MathUtils.lerp(fromZ, toZ, t);
const previous = points[points.length - 1];
if (previous && Math.hypot(previous.x - x, previous.z - z) < 0.000001) return;
points.push({ x, z });
});
}; };
const pushCorner = (centerX, centerZ, fromAngle, toAngle) => { const pushCorner = (centerX, centerZ, fromAngle, toAngle) => {
for (let step = 1; step <= cornerSteps; step += 1) { for (let step = 1; step <= cornerSteps; step += 1) {
+283 -27
View File
@@ -169,6 +169,8 @@ const rightTexture = new THREE.CanvasTexture(rightCanvas);
texture.generateMipmaps = true; texture.generateMipmaps = true;
}); });
const leatherTextures = createLeatherTextures(); const leatherTextures = createLeatherTextures();
const spineClothTextures = createSpineClothTextures();
const paperTextures = createHardcoverPaperTextures();
const materials = { const materials = {
leather: new THREE.MeshStandardMaterial({ leather: new THREE.MeshStandardMaterial({
@@ -216,55 +218,80 @@ const materials = {
side: THREE.DoubleSide side: THREE.DoubleSide
}), }),
pageBlock: new THREE.MeshStandardMaterial({ pageBlock: new THREE.MeshStandardMaterial({
color: 0xe3c98f, color: 0xfffbef,
roughness: 0.82, map: paperTextures.color,
normalMap: paperTextures.normal,
normalScale: new THREE.Vector2(0.032, 0.032),
roughnessMap: paperTextures.roughness,
roughness: 0.88,
metalness: 0, metalness: 0,
envMapIntensity: 0.08 envMapIntensity: 0.06
}), }),
pageEdge: new THREE.MeshStandardMaterial({ pageEdge: new THREE.MeshStandardMaterial({
color: 0xc69f64, color: 0xfff4cf,
roughness: 0.92, map: paperTextures.edge,
normalMap: paperTextures.normal,
normalScale: new THREE.Vector2(0.024, 0.024),
roughnessMap: paperTextures.roughness,
roughness: 0.94,
metalness: 0, metalness: 0,
envMapIntensity: 0.08 envMapIntensity: 0.05
}), }),
pageSurface: new THREE.MeshStandardMaterial({ pageSurface: new THREE.MeshStandardMaterial({
color: 0xf0c17a, color: 0xfffbf0,
roughness: 0.86, map: paperTextures.color,
normalMap: paperTextures.normal,
normalScale: new THREE.Vector2(0.03, 0.03),
roughnessMap: paperTextures.roughness,
roughness: 0.9,
metalness: 0, metalness: 0,
emissive: 0x1e1209, emissive: 0x14110b,
emissiveIntensity: 0.08, emissiveIntensity: 0.025,
envMapIntensity: 0.04, envMapIntensity: 0.035,
side: THREE.DoubleSide side: THREE.DoubleSide
}), }),
leftPage: new THREE.MeshStandardMaterial({ leftPage: new THREE.MeshStandardMaterial({
color: 0xffffff, color: 0xffffff,
map: leftTexture, map: leftTexture,
roughness: 0.74, normalMap: paperTextures.normal,
normalScale: new THREE.Vector2(0.025, 0.025),
roughnessMap: paperTextures.roughness,
roughness: 0.86,
metalness: 0, metalness: 0,
emissive: 0x2d1e12, emissive: 0x11100c,
emissiveIntensity: 0.18, emissiveIntensity: 0.035,
side: THREE.DoubleSide side: THREE.DoubleSide
}), }),
rightPage: new THREE.MeshStandardMaterial({ rightPage: new THREE.MeshStandardMaterial({
color: 0xffffff, color: 0xffffff,
map: rightTexture, map: rightTexture,
roughness: 0.74, normalMap: paperTextures.normal,
normalScale: new THREE.Vector2(0.025, 0.025),
roughnessMap: paperTextures.roughness,
roughness: 0.86,
metalness: 0, metalness: 0,
emissive: 0x2d1e12, emissive: 0x11100c,
emissiveIntensity: 0.18, emissiveIntensity: 0.035,
side: THREE.DoubleSide side: THREE.DoubleSide
}), }),
spineCloth: new THREE.MeshStandardMaterial({ spineCloth: new THREE.MeshStandardMaterial({
color: 0x8e1d18, color: 0x6f0808,
normalMap: leatherTextures.normal, map: spineClothTextures.color,
normalScale: new THREE.Vector2(0.04, 0.04), normalMap: spineClothTextures.normal,
roughnessMap: leatherTextures.roughness, normalScale: new THREE.Vector2(0.075, 0.075),
roughness: 0.82, roughnessMap: spineClothTextures.roughness,
roughness: 0.9,
metalness: 0, metalness: 0,
envMapIntensity: 0.08, envMapIntensity: 0.045,
side: THREE.DoubleSide side: THREE.DoubleSide
}) })
}; };
materials.spineCloth.userData.isSpineCloth = true;
configureHardcoverPaperMaterial(materials.pageBlock);
configureHardcoverPaperMaterial(materials.pageEdge, { useEdgeMap: true });
configureHardcoverPaperMaterial(materials.pageSurface);
configureHardcoverPaperMaterial(materials.leftPage);
configureHardcoverPaperMaterial(materials.rightPage);
configureBookShadowReceiver(materials.leather, 0.52); configureBookShadowReceiver(materials.leather, 0.52);
configureBookShadowReceiver(materials.hingeLeather, 0.36); configureBookShadowReceiver(materials.hingeLeather, 0.36);
@@ -393,7 +420,9 @@ function loadUtilityTexture(url) {
} }
function configureBookShadowReceiver(material, strength) { function configureBookShadowReceiver(material, strength) {
material.customProgramCacheKey = () => `book-shadow-receiver-${strength.toFixed(2)}`; const isSpineCloth = material.userData?.isSpineCloth === true;
const isHardcoverPaper = material.userData?.isHardcoverPaper === true;
material.customProgramCacheKey = () => `book-shadow-receiver-${strength.toFixed(2)}-${isSpineCloth ? 'spine-cloth-v1' : isHardcoverPaper ? 'hardcover-paper-v1' : 'plain'}`;
material.onBeforeCompile = (shader) => { material.onBeforeCompile = (shader) => {
shader.uniforms.bookShadowMaps = { value: bookShadowTargets.map((target) => target.texture) }; shader.uniforms.bookShadowMaps = { value: bookShadowTargets.map((target) => target.texture) };
shader.uniforms.bookShadowMatrices = { value: bookShadowMatrices }; shader.uniforms.bookShadowMatrices = { value: bookShadowMatrices };
@@ -403,7 +432,14 @@ function configureBookShadowReceiver(material, strength) {
shader.vertexShader = shader.vertexShader shader.vertexShader = shader.vertexShader
.replace( .replace(
'#include <common>', '#include <common>',
'#include <common>\nvarying vec3 vBookReceiverWorldPosition;' `#include <common>
varying vec3 vBookReceiverWorldPosition;
${isSpineCloth || isHardcoverPaper ? 'varying vec2 vBookSurfaceUv;' : ''}`
)
.replace(
'#include <begin_vertex>',
`${isSpineCloth || isHardcoverPaper ? 'vBookSurfaceUv = uv;' : ''}
#include <begin_vertex>`
) )
.replace( .replace(
'#include <project_vertex>', '#include <project_vertex>',
@@ -419,6 +455,7 @@ function configureBookShadowReceiver(material, strength) {
uniform vec2 bookShadowMapTexelSize; uniform vec2 bookShadowMapTexelSize;
uniform float bookShadowReceiverStrength; uniform float bookShadowReceiverStrength;
varying vec3 vBookReceiverWorldPosition; varying vec3 vBookReceiverWorldPosition;
${isSpineCloth || isHardcoverPaper ? 'varying vec2 vBookSurfaceUv;' : ''}
float bookReceiverUnpackRGBADepth(vec4 packedDepth) { float bookReceiverUnpackRGBADepth(vec4 packedDepth) {
const vec4 unpackFactors = vec4( const vec4 unpackFactors = vec4(
@@ -485,11 +522,45 @@ function configureBookShadowReceiver(material, strength) {
float shadow1 = bookReceiverSample1(bookShadowMatrices[1] * vec4(worldPosition, 1.0)); float shadow1 = bookReceiverSample1(bookShadowMatrices[1] * vec4(worldPosition, 1.0));
float shadow2 = bookReceiverSample2(bookShadowMatrices[2] * vec4(worldPosition, 1.0)); float shadow2 = bookReceiverSample2(bookShadowMatrices[2] * vec4(worldPosition, 1.0));
return clamp(max(max(shadow0, shadow1), shadow2), 0.0, 1.0); return clamp(max(max(shadow0, shadow1), shadow2), 0.0, 1.0);
}
float spineClothThread(float coordinate, float frequency, float sharpness) {
float wave = abs(fract(coordinate * frequency) - 0.5) * 2.0;
return pow(1.0 - wave, sharpness);
}
vec3 spineClothLight(vec2 uv, vec3 baseLight) {
float warp = spineClothThread(uv.x + sin(uv.y * 18.0) * 0.002, 92.0, 2.4);
float weft = spineClothThread(uv.y + sin(uv.x * 21.0) * 0.0016, 64.0, 2.1);
float fineFiber = sin((uv.x * 420.0 + uv.y * 55.0) * 6.28318530718) *
sin((uv.y * 380.0 - uv.x * 33.0) * 6.28318530718);
float raisedThread = clamp(warp * 0.58 + weft * 0.44, 0.0, 1.0);
float valley = clamp((1.0 - warp) * (1.0 - weft), 0.0, 1.0);
vec3 threadTint = mix(vec3(0.55, 0.19, 0.16), vec3(1.18, 0.78, 0.58), raisedThread);
float fiberShade = 0.9 + fineFiber * 0.035 - valley * 0.18;
return baseLight * threadTint * fiberShade;
}
float paperFiber(float coordinate, float frequency, float sharpness) {
float wave = abs(fract(coordinate * frequency) - 0.5) * 2.0;
return pow(1.0 - wave, sharpness);
}
vec3 hardcoverPaperLight(vec2 uv, vec3 baseLight) {
float laid = paperFiber(uv.y + sin(uv.x * 12.0) * 0.002, 88.0, 2.8);
float chain = paperFiber(uv.x + sin(uv.y * 8.0) * 0.0015, 18.0, 1.6);
float fleck = sin((uv.x * 241.0 + uv.y * 97.0) * 6.28318530718) *
sin((uv.y * 211.0 - uv.x * 53.0) * 6.28318530718);
float fiber = clamp(laid * 0.18 + chain * 0.1 + fleck * 0.025, -0.08, 0.24);
vec3 paperTint = mix(vec3(0.92, 0.9, 0.82), vec3(1.12, 1.08, 0.96), clamp(0.58 + fiber, 0.0, 1.0));
return baseLight * paperTint;
}` }`
) )
.replace( .replace(
'#include <opaque_fragment>', '#include <opaque_fragment>',
`float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength; `${isSpineCloth ? 'outgoingLight = spineClothLight(vBookSurfaceUv, outgoingLight);' : ''}
${isHardcoverPaper ? 'outgoingLight = hardcoverPaperLight(vBookSurfaceUv, outgoingLight);' : ''}
float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength;
outgoingLight *= mix(vec3(1.0), vec3(0.38, 0.29, 0.2), bookReceiverShadow); outgoingLight *= mix(vec3(1.0), vec3(0.38, 0.29, 0.2), bookReceiverShadow);
#include <opaque_fragment>` #include <opaque_fragment>`
); );
@@ -1190,6 +1261,9 @@ function buildBook() {
rightPage: materials.rightPage rightPage: materials.rightPage
}, },
configureMaterial(material, part) { configureMaterial(material, part) {
if (part === 'pages') {
configureHardcoverPaperMaterial(material, { useEdgeMap: material.map !== null });
}
const strength = part === 'spine' const strength = part === 'spine'
? 0.48 ? 0.48
: part === 'coverSpineBase' : part === 'coverSpineBase'
@@ -1208,6 +1282,18 @@ function buildBook() {
book.add(proceduralBook.group); book.add(proceduralBook.group);
} }
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(0.024, 0.024);
material.roughnessMap = paperTextures.roughness;
material.roughness = Math.max(material.roughness ?? 0.86, useEdgeMap ? 0.92 : 0.86);
material.metalness = 0;
material.envMapIntensity = Math.min(material.envMapIntensity ?? 0.05, 0.06);
material.needsUpdate = true;
}
function setReadingProgress(value) { function setReadingProgress(value) {
const nextProgress = THREE.MathUtils.clamp(Number.parseFloat(value), 0, 1); const nextProgress = THREE.MathUtils.clamp(Number.parseFloat(value), 0, 1);
if (!Number.isFinite(nextProgress)) return; if (!Number.isFinite(nextProgress)) return;
@@ -1797,7 +1883,7 @@ function createPageCanvas(side) {
canvas.width = pageTextureWidth; canvas.width = pageTextureWidth;
canvas.height = Math.round(pageTextureWidth * PROCEDURAL_BOOK.PAGE_DEPTH / PROCEDURAL_BOOK.PAGE_WIDTH); canvas.height = Math.round(pageTextureWidth * PROCEDURAL_BOOK.PAGE_DEPTH / PROCEDURAL_BOOK.PAGE_WIDTH);
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.fillStyle = '#f5dfab'; ctx.fillStyle = '#fffaf0';
ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.style.width = `${canvas.width}px`; canvas.style.width = `${canvas.width}px`;
canvas.style.height = `${canvas.height}px`; canvas.style.height = `${canvas.height}px`;
@@ -1928,6 +2014,176 @@ function createLeatherTextures() {
return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture }; return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture };
} }
function createSpineClothTextures() {
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 threadAt = (x, y) => {
const nx = x / size;
const ny = y / size;
const warpPhase = nx * 112 + Math.sin(ny * 31.4159265359) * 0.025;
const weftPhase = ny * 76 + Math.sin(nx * 25.1327412287) * 0.02;
const warp = Math.pow(1 - Math.abs((warpPhase - Math.floor(warpPhase)) - 0.5) * 2, 2.2);
const weft = Math.pow(1 - Math.abs((weftPhase - Math.floor(weftPhase)) - 0.5) * 2, 2.0);
const fiber = Math.sin((nx * 430 + ny * 73) * 6.28318530718) * Math.sin((ny * 390 - nx * 41) * 6.28318530718);
const nap = Math.sin((nx * 19 + ny * 7) * 6.28318530718);
return warp * 0.46 + weft * 0.38 + fiber * 0.045 + nap * 0.055;
};
for (let y = 0; y < size; y += 1) {
for (let x = 0; x < size; x += 1) {
const index = (y * size + x) * 4;
const height = threadAt(x, y);
const wornFiber = 0.86 + 0.1 * Math.sin((x * 0.019 + y * 0.037)) + 0.04 * Math.sin((x * 0.083 - y * 0.011));
const threadGlow = THREE.MathUtils.clamp(0.58 + height * 0.46, 0, 1);
colorImage.data[index] = Math.round(95 * threadGlow * wornFiber);
colorImage.data[index + 1] = Math.round(10 * threadGlow * wornFiber);
colorImage.data[index + 2] = Math.round(9 * (0.84 + height * 0.12));
colorImage.data[index + 3] = 255;
const hLeft = threadAt((x - 1 + size) % size, y);
const hRight = threadAt((x + 1) % size, y);
const hDown = threadAt(x, (y - 1 + size) % size);
const hUp = threadAt(x, (y + 1) % size);
const normal = new THREE.Vector3((hLeft - hRight) * 5.4, (hDown - hUp) * 5.4, 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.84 + height * 0.07 + fiberContrast * 1.25, 0.62, 0.98);
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);
const roughnessTexture = new THREE.CanvasTexture(roughnessCanvas);
[colorTexture, normalTexture, roughnessTexture].forEach((texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2.1, 4.4);
texture.anisotropy = maxTextureAnisotropy;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = true;
});
colorTexture.colorSpace = THREE.SRGBColorSpace;
normalTexture.colorSpace = THREE.NoColorSpace;
roughnessTexture.colorSpace = THREE.NoColorSpace;
return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture };
}
function createHardcoverPaperTextures() {
const size = 1024;
const colorCanvas = document.createElement('canvas');
const edgeCanvas = document.createElement('canvas');
const normalCanvas = document.createElement('canvas');
const roughnessCanvas = document.createElement('canvas');
[colorCanvas, edgeCanvas, normalCanvas, roughnessCanvas].forEach((canvas) => {
canvas.width = size;
canvas.height = size;
});
const colorContext = colorCanvas.getContext('2d');
const edgeContext = edgeCanvas.getContext('2d');
const normalContext = normalCanvas.getContext('2d');
const roughnessContext = roughnessCanvas.getContext('2d');
const colorImage = colorContext.createImageData(size, size);
const edgeImage = edgeContext.createImageData(size, size);
const normalImage = normalContext.createImageData(size, size);
const roughnessImage = roughnessContext.createImageData(size, size);
const fiberAt = (x, y) => {
const nx = x / size;
const ny = y / size;
const laid = Math.sin((ny * 92 + Math.sin(nx * 25.1327412287) * 0.12) * 6.28318530718);
const chain = Math.sin((nx * 18 + Math.sin(ny * 12.5663706144) * 0.06) * 6.28318530718);
const pulpA = Math.sin((nx * 173 + ny * 67) * 6.28318530718);
const pulpB = Math.sin((nx * 89 - ny * 131) * 6.28318530718);
const fleck = Math.max(0, 0.5 - Math.abs(pulpA * pulpB));
return laid * 0.08 + chain * 0.045 - fleck * 0.055;
};
for (let y = 0; y < size; y += 1) {
for (let x = 0; x < size; x += 1) {
const index = (y * size + x) * 4;
const fiber = fiberAt(x, y);
const warmth = 0.97 + 0.018 * Math.sin(x * 0.017 + y * 0.003) + 0.012 * Math.sin(y * 0.041);
const shade = THREE.MathUtils.clamp(0.975 + fiber, 0.88, 1.0);
colorImage.data[index] = Math.round(255 * shade * warmth);
colorImage.data[index + 1] = Math.round(251 * shade * warmth);
colorImage.data[index + 2] = Math.round(235 * shade);
colorImage.data[index + 3] = 255;
const line = y % 34 === 0 ? 0.72 : y % 34 === 1 ? 0.82 : 1;
edgeImage.data[index] = Math.round(255 * shade * line);
edgeImage.data[index + 1] = Math.round(244 * shade * line);
edgeImage.data[index + 2] = Math.round(207 * shade * line);
edgeImage.data[index + 3] = 255;
const hLeft = fiberAt((x - 1 + size) % size, y);
const hRight = fiberAt((x + 1) % size, y);
const hDown = fiberAt(x, (y - 1 + size) % size);
const hUp = fiberAt(x, (y + 1) % size);
const normal = new THREE.Vector3((hLeft - hRight) * 3.2, (hDown - hUp) * 3.2, 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 roughness = THREE.MathUtils.clamp(0.86 + Math.abs(fiber) * 0.5 + Math.abs(hLeft - hRight + hDown - hUp) * 1.2, 0.72, 0.98);
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);
edgeContext.putImageData(edgeImage, 0, 0);
normalContext.putImageData(normalImage, 0, 0);
roughnessContext.putImageData(roughnessImage, 0, 0);
const colorTexture = new THREE.CanvasTexture(colorCanvas);
const edgeTexture = new THREE.CanvasTexture(edgeCanvas);
const normalTexture = new THREE.CanvasTexture(normalCanvas);
const roughnessTexture = new THREE.CanvasTexture(roughnessCanvas);
[colorTexture, edgeTexture, normalTexture, roughnessTexture].forEach((texture) => {
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2.6, 3.4);
texture.anisotropy = maxTextureAnisotropy;
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = true;
});
colorTexture.colorSpace = THREE.SRGBColorSpace;
edgeTexture.colorSpace = THREE.SRGBColorSpace;
normalTexture.colorSpace = THREE.NoColorSpace;
roughnessTexture.colorSpace = THREE.NoColorSpace;
return { color: colorTexture, edge: edgeTexture, normal: normalTexture, roughness: roughnessTexture };
}
function createRoomReflectionTexture() { function createRoomReflectionTexture() {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
generatedTextureCanvases.roomReflection = canvas; generatedTextureCanvases.roomReflection = canvas;