Refine WebGL paper and spine materials
This commit is contained in:
@@ -370,18 +370,23 @@ function addSimulatedStackBodies(group, context, model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createStackBodyMaterials(context, model, side, isSinglePage = false) {
|
function createStackBodyMaterials(context, model, side, isSinglePage = false) {
|
||||||
const baseColor = side < 0 ? '#d8c7a4' : '#e7d6b4';
|
const baseColor = side < 0 ? '#fff1c8' : '#fff7d7';
|
||||||
const lineColor = '#9a8058';
|
const lineColor = '#c39a4b';
|
||||||
const layerTexture = createStackLayerTexture(context, model.bundleCount, baseColor, lineColor);
|
const layerTextures = createStackLayerTextures(context, model.bundleCount, baseColor, lineColor);
|
||||||
const surface = new THREE.MeshStandardMaterial({
|
const surface = new THREE.MeshStandardMaterial({
|
||||||
map: layerTexture,
|
map: layerTextures.color,
|
||||||
roughness: 0.84,
|
normalMap: layerTextures.normal,
|
||||||
|
normalScale: new THREE.Vector2(0.034, 0.034),
|
||||||
|
roughnessMap: layerTextures.roughness,
|
||||||
|
roughness: 0.88,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
envMapIntensity: 0.08,
|
envMapIntensity: 0.06,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
});
|
});
|
||||||
const edge = surface.clone();
|
const edge = surface.clone();
|
||||||
edge.map = layerTexture;
|
edge.map = layerTextures.color;
|
||||||
|
edge.normalMap = layerTextures.normal;
|
||||||
|
edge.roughnessMap = layerTextures.roughness;
|
||||||
const bottom = context.materials.pageTop.clone();
|
const bottom = context.materials.pageTop.clone();
|
||||||
const top = side < 0 && context.materials.leftPage
|
const top = side < 0 && context.materials.leftPage
|
||||||
? context.materials.leftPage
|
? context.materials.leftPage
|
||||||
@@ -401,33 +406,97 @@ function createStackBodyMaterials(context, model, side, isSinglePage = false) {
|
|||||||
return [surface, edge, bottom, top, context.materials.spine];
|
return [surface, edge, bottom, top, context.materials.spine];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStackLayerTexture(context, bundleCount, baseColor, lineColor) {
|
function createStackLayerTextures(context, bundleCount, baseColor, lineColor) {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
|
const normalCanvas = document.createElement('canvas');
|
||||||
|
const roughnessCanvas = document.createElement('canvas');
|
||||||
canvas.width = 2048;
|
canvas.width = 2048;
|
||||||
canvas.height = 1024;
|
canvas.height = 1024;
|
||||||
|
normalCanvas.width = canvas.width;
|
||||||
|
normalCanvas.height = canvas.height;
|
||||||
|
roughnessCanvas.width = canvas.width;
|
||||||
|
roughnessCanvas.height = canvas.height;
|
||||||
const context2d = canvas.getContext('2d');
|
const context2d = canvas.getContext('2d');
|
||||||
|
const normalContext = normalCanvas.getContext('2d');
|
||||||
|
const roughnessContext = roughnessCanvas.getContext('2d');
|
||||||
context2d.fillStyle = baseColor;
|
context2d.fillStyle = baseColor;
|
||||||
context2d.fillRect(0, 0, canvas.width, canvas.height);
|
context2d.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
context2d.strokeStyle = lineColor;
|
normalContext.fillStyle = 'rgb(128, 128, 255)';
|
||||||
context2d.globalAlpha = 0.95;
|
normalContext.fillRect(0, 0, normalCanvas.width, normalCanvas.height);
|
||||||
context2d.lineWidth = 4.2;
|
roughnessContext.fillStyle = 'rgb(224, 224, 224)';
|
||||||
context2d.lineCap = 'square';
|
roughnessContext.fillRect(0, 0, roughnessCanvas.width, roughnessCanvas.height);
|
||||||
for (let row = 0; row < bundleCount; row += 1) {
|
|
||||||
const v = bundleCount <= 1 ? 0.5 : row / (bundleCount - 1);
|
const irregular = (seed) => {
|
||||||
|
const value = Math.sin(seed * 12.9898 + 78.233) * 43758.5453;
|
||||||
|
return value - Math.floor(value);
|
||||||
|
};
|
||||||
|
const drawLayerLine = (v, alpha, width, normalStrength) => {
|
||||||
const y = (1 - v) * canvas.height;
|
const y = (1 - v) * canvas.height;
|
||||||
context2d.beginPath();
|
context2d.beginPath();
|
||||||
context2d.moveTo(-8, y);
|
context2d.moveTo(-8, y);
|
||||||
context2d.lineTo(canvas.width + 8, y);
|
context2d.lineTo(canvas.width + 8, y);
|
||||||
|
context2d.strokeStyle = lineColor;
|
||||||
|
context2d.globalAlpha = alpha;
|
||||||
|
context2d.lineWidth = width;
|
||||||
|
context2d.lineCap = 'square';
|
||||||
context2d.stroke();
|
context2d.stroke();
|
||||||
|
|
||||||
|
normalContext.beginPath();
|
||||||
|
normalContext.moveTo(-8, y);
|
||||||
|
normalContext.lineTo(normalCanvas.width + 8, y);
|
||||||
|
const normalByte = Math.round(128 + normalStrength * 68);
|
||||||
|
normalContext.strokeStyle = `rgb(128, ${normalByte}, 255)`;
|
||||||
|
normalContext.globalAlpha = Math.min(1, alpha * 0.85);
|
||||||
|
normalContext.lineWidth = Math.max(1, width * 0.72);
|
||||||
|
normalContext.stroke();
|
||||||
|
|
||||||
|
roughnessContext.beginPath();
|
||||||
|
roughnessContext.moveTo(-8, y);
|
||||||
|
roughnessContext.lineTo(roughnessCanvas.width + 8, y);
|
||||||
|
const roughnessByte = Math.round(218 + alpha * 28);
|
||||||
|
roughnessContext.strokeStyle = `rgb(${roughnessByte}, ${roughnessByte}, ${roughnessByte})`;
|
||||||
|
roughnessContext.globalAlpha = Math.min(1, alpha * 0.72);
|
||||||
|
roughnessContext.lineWidth = Math.max(1, width * 0.8);
|
||||||
|
roughnessContext.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let row = 0; row < bundleCount; row += 1) {
|
||||||
|
const rowV = bundleCount <= 1 ? 0.5 : row / (bundleCount - 1);
|
||||||
|
const rowAccent = 0.5 + irregular(row + 0.37) * 0.28;
|
||||||
|
drawLayerLine(rowV, rowAccent, 2.6 + irregular(row + 2.13) * 0.8, 0.58);
|
||||||
|
if (row >= bundleCount - 1) continue;
|
||||||
|
const nextV = (row + 1) / Math.max(1, bundleCount - 1);
|
||||||
|
const interval = nextV - rowV;
|
||||||
|
const microLines = 12;
|
||||||
|
for (let sub = 1; sub < microLines; sub += 1) {
|
||||||
|
const seed = row * 17.0 + sub * 3.0;
|
||||||
|
const t = THREE.MathUtils.clamp((sub + (irregular(seed) - 0.5) * 0.22) / microLines, 0.04, 0.96);
|
||||||
|
const v = rowV + interval * t;
|
||||||
|
const alpha = 0.3 + irregular(seed + 1.91) * 0.24;
|
||||||
|
const width = 1.05 + irregular(seed + 5.47) * 1.05;
|
||||||
|
drawLayerLine(v, alpha, width, 0.34 + irregular(seed + 9.17) * 0.26);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const texture = new THREE.CanvasTexture(canvas);
|
context2d.globalAlpha = 1;
|
||||||
texture.colorSpace = THREE.SRGBColorSpace;
|
normalContext.globalAlpha = 1;
|
||||||
texture.anisotropy = context.maxAnisotropy;
|
roughnessContext.globalAlpha = 1;
|
||||||
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
||||||
texture.magFilter = THREE.LinearFilter;
|
const colorTexture = new THREE.CanvasTexture(canvas);
|
||||||
texture.generateMipmaps = true;
|
const normalTexture = new THREE.CanvasTexture(normalCanvas);
|
||||||
texture.needsUpdate = true;
|
const roughnessTexture = new THREE.CanvasTexture(roughnessCanvas);
|
||||||
return texture;
|
[colorTexture, normalTexture, roughnessTexture].forEach((texture) => {
|
||||||
|
texture.wrapS = THREE.RepeatWrapping;
|
||||||
|
texture.wrapT = THREE.RepeatWrapping;
|
||||||
|
texture.anisotropy = context.maxAnisotropy;
|
||||||
|
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||||
|
texture.magFilter = THREE.LinearFilter;
|
||||||
|
texture.generateMipmaps = true;
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
});
|
||||||
|
colorTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
normalTexture.colorSpace = THREE.NoColorSpace;
|
||||||
|
roughnessTexture.colorSpace = THREE.NoColorSpace;
|
||||||
|
return { color: colorTexture, normal: normalTexture, roughness: roughnessTexture };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLoftedLineBody(model, lines, depth) {
|
function createLoftedLineBody(model, lines, depth) {
|
||||||
|
|||||||
+18
-17
@@ -275,14 +275,14 @@ const materials = {
|
|||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
}),
|
}),
|
||||||
spineCloth: new THREE.MeshStandardMaterial({
|
spineCloth: new THREE.MeshStandardMaterial({
|
||||||
color: 0x6f0808,
|
color: 0xa51d1d,
|
||||||
map: spineClothTextures.color,
|
map: spineClothTextures.color,
|
||||||
normalMap: spineClothTextures.normal,
|
normalMap: spineClothTextures.normal,
|
||||||
normalScale: new THREE.Vector2(0.075, 0.075),
|
normalScale: new THREE.Vector2(0.07, 0.07),
|
||||||
roughnessMap: spineClothTextures.roughness,
|
roughnessMap: spineClothTextures.roughness,
|
||||||
roughness: 0.9,
|
roughness: 0.86,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
envMapIntensity: 0.045,
|
envMapIntensity: 0.075,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -536,8 +536,8 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
sin((uv.y * 380.0 - uv.x * 33.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 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);
|
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);
|
vec3 threadTint = mix(vec3(0.72, 0.24, 0.2), vec3(1.34, 0.86, 0.68), raisedThread);
|
||||||
float fiberShade = 0.9 + fineFiber * 0.035 - valley * 0.18;
|
float fiberShade = 0.96 + fineFiber * 0.03 - valley * 0.11;
|
||||||
return baseLight * threadTint * fiberShade;
|
return baseLight * threadTint * fiberShade;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,12 +547,12 @@ function configureBookShadowReceiver(material, strength) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vec3 hardcoverPaperLight(vec2 uv, vec3 baseLight) {
|
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) *
|
float fleck = sin((uv.x * 241.0 + uv.y * 97.0) * 6.28318530718) *
|
||||||
sin((uv.y * 211.0 - uv.x * 53.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);
|
float cloud = sin((uv.x * 17.0 + uv.y * 11.0) * 6.28318530718) *
|
||||||
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));
|
sin((uv.x * 29.0 - uv.y * 23.0) * 6.28318530718);
|
||||||
|
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));
|
||||||
return baseLight * paperTint;
|
return baseLight * paperTint;
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
@@ -2049,9 +2049,9 @@ function createSpineClothTextures() {
|
|||||||
const height = threadAt(x, y);
|
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 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);
|
const threadGlow = THREE.MathUtils.clamp(0.58 + height * 0.46, 0, 1);
|
||||||
colorImage.data[index] = Math.round(95 * threadGlow * wornFiber);
|
colorImage.data[index] = Math.round(128 * threadGlow * wornFiber);
|
||||||
colorImage.data[index + 1] = Math.round(10 * threadGlow * wornFiber);
|
colorImage.data[index + 1] = Math.round(22 * threadGlow * wornFiber);
|
||||||
colorImage.data[index + 2] = Math.round(9 * (0.84 + height * 0.12));
|
colorImage.data[index + 2] = Math.round(18 * (0.86 + height * 0.12));
|
||||||
colorImage.data[index + 3] = 255;
|
colorImage.data[index + 3] = 255;
|
||||||
|
|
||||||
const hLeft = threadAt((x - 1 + size) % size, y);
|
const hLeft = threadAt((x - 1 + size) % size, y);
|
||||||
@@ -2116,12 +2116,12 @@ function createHardcoverPaperTextures() {
|
|||||||
const fiberAt = (x, y) => {
|
const fiberAt = (x, y) => {
|
||||||
const nx = x / size;
|
const nx = x / size;
|
||||||
const ny = y / 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 pulpA = Math.sin((nx * 173 + ny * 67) * 6.28318530718);
|
||||||
const pulpB = Math.sin((nx * 89 - ny * 131) * 6.28318530718);
|
const pulpB = Math.sin((nx * 89 - ny * 131) * 6.28318530718);
|
||||||
|
const cloudA = Math.sin((nx * 19 + ny * 11) * 6.28318530718);
|
||||||
|
const cloudB = Math.sin((nx * 31 - ny * 27) * 6.28318530718);
|
||||||
const fleck = Math.max(0, 0.5 - Math.abs(pulpA * pulpB));
|
const fleck = Math.max(0, 0.5 - Math.abs(pulpA * pulpB));
|
||||||
return laid * 0.08 + chain * 0.045 - fleck * 0.055;
|
return cloudA * cloudB * 0.026 - fleck * 0.035;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let y = 0; y < size; y += 1) {
|
for (let y = 0; y < size; y += 1) {
|
||||||
@@ -2135,7 +2135,8 @@ function createHardcoverPaperTextures() {
|
|||||||
colorImage.data[index + 2] = Math.round(235 * shade);
|
colorImage.data[index + 2] = Math.round(235 * shade);
|
||||||
colorImage.data[index + 3] = 255;
|
colorImage.data[index + 3] = 255;
|
||||||
|
|
||||||
const line = y % 34 === 0 ? 0.72 : y % 34 === 1 ? 0.82 : 1;
|
const linePhase = (y + Math.sin(x * 0.021) * 4) % 34;
|
||||||
|
const line = linePhase < 1.2 ? 0.72 : linePhase < 2.1 ? 0.82 : 1;
|
||||||
edgeImage.data[index] = Math.round(255 * shade * line);
|
edgeImage.data[index] = Math.round(255 * shade * line);
|
||||||
edgeImage.data[index + 1] = Math.round(244 * shade * line);
|
edgeImage.data[index + 1] = Math.round(244 * shade * line);
|
||||||
edgeImage.data[index + 2] = Math.round(207 * shade * line);
|
edgeImage.data[index + 2] = Math.round(207 * shade * line);
|
||||||
|
|||||||
Reference in New Issue
Block a user