Add WebGL book headbands and bounce lighting
This commit is contained in:
@@ -68,6 +68,12 @@ function createBookContext(options) {
|
|||||||
envMapIntensity: 0.08,
|
envMapIntensity: 0.08,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
|
headband: options.materials?.headband ?? new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xf4dcc0,
|
||||||
|
roughness: 0.82,
|
||||||
|
metalness: 0,
|
||||||
|
envMapIntensity: 0.06
|
||||||
|
}),
|
||||||
pageTop: options.materials?.pageTop ?? new THREE.MeshStandardMaterial({
|
pageTop: options.materials?.pageTop ?? new THREE.MeshStandardMaterial({
|
||||||
color: 0xf1dfba,
|
color: 0xf1dfba,
|
||||||
roughness: 0.82,
|
roughness: 0.82,
|
||||||
@@ -324,26 +330,53 @@ function addClothSpine(group, context, model) {
|
|||||||
mesh.userData.bookPart = 'spine';
|
mesh.userData.bookPart = 'spine';
|
||||||
configurePartMaterial(context, mesh.material, 'spine');
|
configurePartMaterial(context, mesh.material, 'spine');
|
||||||
group.add(mesh);
|
group.add(mesh);
|
||||||
|
|
||||||
|
createHeadbandMeshes(context, model).forEach((headband) => group.add(headband));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHeadbandMeshes(context, model) {
|
||||||
|
const radius = 0.0046;
|
||||||
|
const centerOffset = radius * 0.62;
|
||||||
|
const spineProfile = [];
|
||||||
|
for (let i = 2; i <= 30; i += 1) {
|
||||||
|
const point = spineCurvePoint(i / 32, model.spineWidth);
|
||||||
|
spineProfile.push(new THREE.Vector3(point.x, point.y + 0.0012, 0));
|
||||||
|
}
|
||||||
|
const meshes = [];
|
||||||
|
[-1, 1].forEach((zSide) => {
|
||||||
|
const z = zSide * (model.pageDepth * 0.5 + centerOffset);
|
||||||
|
const curve = new THREE.CatmullRomCurve3(spineProfile.map((point) => point.clone().setZ(z)));
|
||||||
|
const geometry = new THREE.TubeGeometry(curve, 56, radius, 10, false);
|
||||||
|
const mesh = new THREE.Mesh(geometry, context.materials.headband);
|
||||||
|
mesh.userData.bookPart = 'headband';
|
||||||
|
configurePartMaterial(context, mesh.material, 'headband');
|
||||||
|
meshes.push(mesh);
|
||||||
|
});
|
||||||
|
return meshes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createClothSpineGeometry(depth, spineWidth) {
|
function createClothSpineGeometry(depth, spineWidth) {
|
||||||
|
const endOverrun = 0.0012;
|
||||||
const profile = [];
|
const profile = [];
|
||||||
for (let i = 0; i <= 32; i += 1) {
|
for (let i = 0; i <= 32; i += 1) {
|
||||||
profile.push(spineCurvePoint(i / 32, spineWidth));
|
profile.push(spineCurvePoint(i / 32, spineWidth));
|
||||||
}
|
}
|
||||||
const positions = [];
|
const positions = [];
|
||||||
|
const uvs = [];
|
||||||
const indices = [];
|
const indices = [];
|
||||||
const front = [];
|
const front = [];
|
||||||
const back = [];
|
const back = [];
|
||||||
const push = (point, z) => {
|
const push = (point, z, uv) => {
|
||||||
const index = positions.length / 3;
|
const index = positions.length / 3;
|
||||||
positions.push(point.x, point.y, z);
|
positions.push(point.x, point.y, z);
|
||||||
|
uvs.push(uv.u, uv.v);
|
||||||
return index;
|
return index;
|
||||||
};
|
};
|
||||||
|
|
||||||
profile.forEach((point) => {
|
profile.forEach((point, index) => {
|
||||||
front.push(push(point, depth * 0.5 + 0.024));
|
const u = profile.length <= 1 ? 0.5 : index / (profile.length - 1);
|
||||||
back.push(push(point, -depth * 0.5 - 0.024));
|
front.push(push(point, depth * 0.5 + endOverrun, { u, v: 1 }));
|
||||||
|
back.push(push(point, -depth * 0.5 - endOverrun, { u, v: 0 }));
|
||||||
});
|
});
|
||||||
for (let i = 0; i < profile.length - 1; i += 1) {
|
for (let i = 0; i < profile.length - 1; i += 1) {
|
||||||
indices.push(front[i], back[i], front[i + 1]);
|
indices.push(front[i], back[i], front[i + 1]);
|
||||||
@@ -353,6 +386,7 @@ function createClothSpineGeometry(depth, spineWidth) {
|
|||||||
const geometry = new THREE.BufferGeometry();
|
const geometry = new THREE.BufferGeometry();
|
||||||
geometry.setIndex(indices);
|
geometry.setIndex(indices);
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||||
|
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
||||||
geometry.computeVertexNormals();
|
geometry.computeVertexNormals();
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|||||||
+142
-5
@@ -170,6 +170,7 @@ const rightTexture = new THREE.CanvasTexture(rightCanvas);
|
|||||||
});
|
});
|
||||||
const leatherTextures = createLeatherTextures();
|
const leatherTextures = createLeatherTextures();
|
||||||
const spineClothTextures = createSpineClothTextures();
|
const spineClothTextures = createSpineClothTextures();
|
||||||
|
const headbandTextures = createHeadbandTextures();
|
||||||
const paperTextures = createHardcoverPaperTextures();
|
const paperTextures = createHardcoverPaperTextures();
|
||||||
|
|
||||||
const materials = {
|
const materials = {
|
||||||
@@ -284,9 +285,20 @@ const materials = {
|
|||||||
metalness: 0,
|
metalness: 0,
|
||||||
envMapIntensity: 0.075,
|
envMapIntensity: 0.075,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
|
}),
|
||||||
|
headband: new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
map: headbandTextures.color,
|
||||||
|
normalMap: headbandTextures.normal,
|
||||||
|
normalScale: new THREE.Vector2(0.055, 0.055),
|
||||||
|
roughnessMap: headbandTextures.roughness,
|
||||||
|
roughness: 0.96,
|
||||||
|
metalness: 0,
|
||||||
|
envMapIntensity: 0
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
materials.spineCloth.userData.isSpineCloth = true;
|
materials.spineCloth.userData.isSpineCloth = true;
|
||||||
|
materials.headband.userData.isHeadband = true;
|
||||||
configureHardcoverPaperMaterial(materials.pageBlock);
|
configureHardcoverPaperMaterial(materials.pageBlock);
|
||||||
configureHardcoverPaperMaterial(materials.pageEdge, { useEdgeMap: true });
|
configureHardcoverPaperMaterial(materials.pageEdge, { useEdgeMap: true });
|
||||||
configureHardcoverPaperMaterial(materials.pageSurface);
|
configureHardcoverPaperMaterial(materials.pageSurface);
|
||||||
@@ -303,6 +315,7 @@ configureBookShadowReceiver(materials.pageSurface, 0.34);
|
|||||||
configureBookShadowReceiver(materials.leftPage, 0.38);
|
configureBookShadowReceiver(materials.leftPage, 0.38);
|
||||||
configureBookShadowReceiver(materials.rightPage, 0.38);
|
configureBookShadowReceiver(materials.rightPage, 0.38);
|
||||||
configureBookShadowReceiver(materials.spineCloth, 0.48);
|
configureBookShadowReceiver(materials.spineCloth, 0.48);
|
||||||
|
configureBookShadowReceiver(materials.headband, 0.62);
|
||||||
|
|
||||||
buildTable();
|
buildTable();
|
||||||
buildLighting();
|
buildLighting();
|
||||||
@@ -422,23 +435,31 @@ function loadUtilityTexture(url) {
|
|||||||
function configureBookShadowReceiver(material, strength) {
|
function configureBookShadowReceiver(material, strength) {
|
||||||
const isSpineCloth = material.userData?.isSpineCloth === true;
|
const isSpineCloth = material.userData?.isSpineCloth === true;
|
||||||
const isHardcoverPaper = material.userData?.isHardcoverPaper === true;
|
const isHardcoverPaper = material.userData?.isHardcoverPaper === true;
|
||||||
material.customProgramCacheKey = () => `book-shadow-receiver-${strength.toFixed(2)}-${isSpineCloth ? 'spine-cloth-v1' : isHardcoverPaper ? 'hardcover-paper-v1' : 'plain'}`;
|
const isHeadband = material.userData?.isHeadband === true;
|
||||||
|
material.customProgramCacheKey = () => `book-shadow-receiver-${strength.toFixed(2)}-${isHeadband ? 'headband-v1' : isSpineCloth ? 'spine-cloth-v4' : 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 };
|
||||||
shader.uniforms.bookShadowMapTexelSize = { value: new THREE.Vector2(1 / bookShadowMapSize, 1 / bookShadowMapSize) };
|
shader.uniforms.bookShadowMapTexelSize = { value: new THREE.Vector2(1 / bookShadowMapSize, 1 / bookShadowMapSize) };
|
||||||
shader.uniforms.bookShadowReceiverStrength = { value: strength };
|
shader.uniforms.bookShadowReceiverStrength = { value: strength };
|
||||||
|
shader.uniforms.bookTableTopY = { value: tableTopY };
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader
|
shader.vertexShader = shader.vertexShader
|
||||||
.replace(
|
.replace(
|
||||||
'#include <common>',
|
'#include <common>',
|
||||||
`#include <common>
|
`#include <common>
|
||||||
varying vec3 vBookReceiverWorldPosition;
|
varying vec3 vBookReceiverWorldPosition;
|
||||||
${isSpineCloth || isHardcoverPaper ? 'varying vec2 vBookSurfaceUv;' : ''}`
|
varying vec3 vBookReceiverWorldNormal;
|
||||||
|
${isSpineCloth || isHardcoverPaper || isHeadband ? 'varying vec2 vBookSurfaceUv;' : ''}`
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'#include <defaultnormal_vertex>',
|
||||||
|
`#include <defaultnormal_vertex>
|
||||||
|
vBookReceiverWorldNormal = normalize(mat3(modelMatrix) * objectNormal);`
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
'#include <begin_vertex>',
|
'#include <begin_vertex>',
|
||||||
`${isSpineCloth || isHardcoverPaper ? 'vBookSurfaceUv = uv;' : ''}
|
`${isSpineCloth || isHardcoverPaper || isHeadband ? 'vBookSurfaceUv = uv;' : ''}
|
||||||
#include <begin_vertex>`
|
#include <begin_vertex>`
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
@@ -454,8 +475,10 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
uniform mat4 bookShadowMatrices[3];
|
uniform mat4 bookShadowMatrices[3];
|
||||||
uniform vec2 bookShadowMapTexelSize;
|
uniform vec2 bookShadowMapTexelSize;
|
||||||
uniform float bookShadowReceiverStrength;
|
uniform float bookShadowReceiverStrength;
|
||||||
|
uniform float bookTableTopY;
|
||||||
varying vec3 vBookReceiverWorldPosition;
|
varying vec3 vBookReceiverWorldPosition;
|
||||||
${isSpineCloth || isHardcoverPaper ? 'varying vec2 vBookSurfaceUv;' : ''}
|
varying vec3 vBookReceiverWorldNormal;
|
||||||
|
${isSpineCloth || isHardcoverPaper || isHeadband ? 'varying vec2 vBookSurfaceUv;' : ''}
|
||||||
|
|
||||||
float bookReceiverUnpackRGBADepth(vec4 packedDepth) {
|
float bookReceiverUnpackRGBADepth(vec4 packedDepth) {
|
||||||
const vec4 unpackFactors = vec4(
|
const vec4 unpackFactors = vec4(
|
||||||
@@ -524,6 +547,19 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
return clamp(max(max(shadow0, shadow1), shadow2), 0.0, 1.0);
|
return clamp(max(max(shadow0, shadow1), shadow2), 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec3 bookLocalBounce(vec3 worldPosition, vec3 worldNormal, float shadow) {
|
||||||
|
float tableDistance = max(0.0, worldPosition.y - bookTableTopY);
|
||||||
|
float tableReach = 1.0 - smoothstep(0.02, 0.24, tableDistance);
|
||||||
|
float grazingSide = 1.0 - pow(abs(worldNormal.y), 0.65);
|
||||||
|
float underside = smoothstep(0.12, 0.82, -worldNormal.y);
|
||||||
|
float pageGlow = smoothstep(0.02, 0.18, worldPosition.y - bookTableTopY) *
|
||||||
|
(1.0 - smoothstep(0.18, 0.34, worldPosition.y - bookTableTopY));
|
||||||
|
float bounce = tableReach * (0.42 + grazingSide * 0.34 + underside * 0.32) + pageGlow * 0.16;
|
||||||
|
vec3 tableWarmth = vec3(0.055, 0.029, 0.014);
|
||||||
|
vec3 pageWarmth = vec3(0.03, 0.021, 0.012);
|
||||||
|
return (tableWarmth * bounce + pageWarmth * pageGlow) * mix(1.0, 0.62, shadow);
|
||||||
|
}
|
||||||
|
|
||||||
float spineClothThread(float coordinate, float frequency, float sharpness) {
|
float spineClothThread(float coordinate, float frequency, float sharpness) {
|
||||||
float wave = abs(fract(coordinate * frequency) - 0.5) * 2.0;
|
float wave = abs(fract(coordinate * frequency) - 0.5) * 2.0;
|
||||||
return pow(1.0 - wave, sharpness);
|
return pow(1.0 - wave, sharpness);
|
||||||
@@ -554,14 +590,23 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
float fiber = clamp(fleck * 0.018 + cloud * 0.022, -0.04, 0.05);
|
float fiber = clamp(fleck * 0.018 + cloud * 0.022, -0.04, 0.05);
|
||||||
vec3 paperTint = mix(vec3(0.96, 0.945, 0.89), vec3(1.08, 1.055, 0.98), clamp(0.62 + fiber, 0.0, 1.0));
|
vec3 paperTint = mix(vec3(0.96, 0.945, 0.89), vec3(1.08, 1.055, 0.98), clamp(0.62 + fiber, 0.0, 1.0));
|
||||||
return baseLight * paperTint;
|
return baseLight * paperTint;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 headbandCreviceLight(vec2 uv, vec3 baseLight) {
|
||||||
|
float wrapRidge = spineClothThread(uv.x * 0.72 + uv.y * 4.8, 58.0, 0.7);
|
||||||
|
float fiber = spineClothThread(uv.y + uv.x * 0.08, 72.0, 1.35);
|
||||||
|
float relief = 0.82 + wrapRidge * 0.1 + fiber * 0.04;
|
||||||
|
return baseLight * relief;
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
'#include <opaque_fragment>',
|
'#include <opaque_fragment>',
|
||||||
`${isSpineCloth ? 'outgoingLight = spineClothLight(vBookSurfaceUv, outgoingLight);' : ''}
|
`${isSpineCloth ? 'outgoingLight = spineClothLight(vBookSurfaceUv, outgoingLight);' : ''}
|
||||||
${isHardcoverPaper ? 'outgoingLight = hardcoverPaperLight(vBookSurfaceUv, outgoingLight);' : ''}
|
${isHardcoverPaper ? 'outgoingLight = hardcoverPaperLight(vBookSurfaceUv, outgoingLight);' : ''}
|
||||||
|
${isHeadband ? 'outgoingLight = headbandCreviceLight(vBookSurfaceUv, outgoingLight);' : ''}
|
||||||
float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength;
|
float bookReceiverShadow = bookReceiverShadowField(vBookReceiverWorldPosition) * bookShadowReceiverStrength;
|
||||||
outgoingLight *= mix(vec3(1.0), vec3(0.38, 0.29, 0.2), bookReceiverShadow);
|
outgoingLight *= mix(vec3(1.0), ${isHeadband ? 'vec3(0.16, 0.095, 0.055)' : 'vec3(0.38, 0.29, 0.2)'}, bookReceiverShadow);
|
||||||
|
outgoingLight += bookLocalBounce(vBookReceiverWorldPosition, normalize(vBookReceiverWorldNormal), bookReceiverShadow);
|
||||||
#include <opaque_fragment>`
|
#include <opaque_fragment>`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1256,6 +1301,7 @@ function buildBook() {
|
|||||||
coverSpineBase: materials.spineBaseLeather,
|
coverSpineBase: materials.spineBaseLeather,
|
||||||
coverEdge: materials.coverEdge,
|
coverEdge: materials.coverEdge,
|
||||||
spine: materials.spineCloth,
|
spine: materials.spineCloth,
|
||||||
|
headband: materials.headband,
|
||||||
pageTop: materials.pageSurface,
|
pageTop: materials.pageSurface,
|
||||||
leftPage: materials.leftPage,
|
leftPage: materials.leftPage,
|
||||||
rightPage: materials.rightPage
|
rightPage: materials.rightPage
|
||||||
@@ -1266,6 +1312,8 @@ function buildBook() {
|
|||||||
}
|
}
|
||||||
const strength = part === 'spine'
|
const strength = part === 'spine'
|
||||||
? 0.48
|
? 0.48
|
||||||
|
: part === 'headband'
|
||||||
|
? 0.62
|
||||||
: part === 'coverSpineBase'
|
: part === 'coverSpineBase'
|
||||||
? 0.34
|
? 0.34
|
||||||
: part === 'hinge'
|
: part === 'hinge'
|
||||||
@@ -2095,6 +2143,95 @@ function createSpineClothTextures() {
|
|||||||
return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture };
|
return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createHeadbandTextures() {
|
||||||
|
const width = 1024;
|
||||||
|
const height = 256;
|
||||||
|
const colorCanvas = document.createElement('canvas');
|
||||||
|
const normalCanvas = document.createElement('canvas');
|
||||||
|
const roughnessCanvas = document.createElement('canvas');
|
||||||
|
colorCanvas.width = width;
|
||||||
|
colorCanvas.height = height;
|
||||||
|
normalCanvas.width = width;
|
||||||
|
normalCanvas.height = height;
|
||||||
|
roughnessCanvas.width = width;
|
||||||
|
roughnessCanvas.height = height;
|
||||||
|
const colorContext = colorCanvas.getContext('2d');
|
||||||
|
const normalContext = normalCanvas.getContext('2d');
|
||||||
|
const roughnessContext = roughnessCanvas.getContext('2d');
|
||||||
|
const colorImage = colorContext.createImageData(width, height);
|
||||||
|
const normalImage = normalContext.createImageData(width, height);
|
||||||
|
const roughnessImage = roughnessContext.createImageData(width, height);
|
||||||
|
const threadAt = (x, y) => {
|
||||||
|
const u = x / width;
|
||||||
|
const v = y / height;
|
||||||
|
const wrap = u * 44 + v * 7.5;
|
||||||
|
const phase = wrap - Math.floor(wrap);
|
||||||
|
const rib = Math.pow(1 - Math.abs(phase - 0.5) * 2, 0.55);
|
||||||
|
const warp = Math.pow(1 - Math.abs(((u * 190 + v * 9) % 1) - 0.5) * 2, 1.1);
|
||||||
|
const weft = Math.pow(1 - Math.abs(((v * 38 + u * 4.5) % 1) - 0.5) * 2, 1.25);
|
||||||
|
return rib * 0.72 + warp * 0.16 + weft * 0.12;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y += 1) {
|
||||||
|
for (let x = 0; x < width; x += 1) {
|
||||||
|
const index = (y * width + x) * 4;
|
||||||
|
const u = x / width;
|
||||||
|
const v = y / height;
|
||||||
|
const wrap = u * 44 + v * 7.5;
|
||||||
|
const alternate = Math.floor(wrap) % 2;
|
||||||
|
const heightValue = threadAt(x, y);
|
||||||
|
const cotton = Math.sin((u * 410 + v * 79) * 6.28318530718) * 0.025;
|
||||||
|
const shade = THREE.MathUtils.clamp(0.76 + heightValue * 0.18 + cotton, 0.58, 1.0);
|
||||||
|
const red = [166, 30, 24];
|
||||||
|
const ivory = [218, 190, 136];
|
||||||
|
const linen = [152, 116, 82];
|
||||||
|
const base = alternate === 0 ? red : ivory;
|
||||||
|
const blend = THREE.MathUtils.clamp(heightValue * 1.08, 0, 1);
|
||||||
|
colorImage.data[index] = Math.round(THREE.MathUtils.lerp(linen[0], base[0], blend) * shade);
|
||||||
|
colorImage.data[index + 1] = Math.round(THREE.MathUtils.lerp(linen[1], base[1], blend) * shade);
|
||||||
|
colorImage.data[index + 2] = Math.round(THREE.MathUtils.lerp(linen[2], base[2], blend) * shade);
|
||||||
|
colorImage.data[index + 3] = 255;
|
||||||
|
|
||||||
|
const hLeft = threadAt((x - 1 + width) % width, y);
|
||||||
|
const hRight = threadAt((x + 1) % width, y);
|
||||||
|
const hDown = threadAt(x, (y - 1 + height) % height);
|
||||||
|
const hUp = threadAt(x, (y + 1) % height);
|
||||||
|
const normal = new THREE.Vector3((hLeft - hRight) * 3.8, (hDown - hUp) * 3.8, 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.74 + heightValue * 0.16 + Math.abs(hLeft - hRight) * 0.8, 0.58, 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);
|
||||||
|
const roughnessTexture = new THREE.CanvasTexture(roughnessCanvas);
|
||||||
|
[colorTexture, normalTexture, roughnessTexture].forEach((texture) => {
|
||||||
|
texture.wrapS = THREE.RepeatWrapping;
|
||||||
|
texture.wrapT = THREE.RepeatWrapping;
|
||||||
|
texture.repeat.set(1.0, 1.0);
|
||||||
|
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() {
|
function createHardcoverPaperTextures() {
|
||||||
const size = 1024;
|
const size = 1024;
|
||||||
const colorCanvas = document.createElement('canvas');
|
const colorCanvas = document.createElement('canvas');
|
||||||
|
|||||||
Reference in New Issue
Block a user