General shifts
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import * as THREE from 'three'
|
||||
import { cardSceneDimensions } from './cardGeometry'
|
||||
|
||||
export type FinishName = 'Printed ink' | 'Foil' | 'Holographic'
|
||||
export type SubstrateName = 'Paper' | 'Linen' | 'Plastic' | 'Metal' | 'Wood'
|
||||
export const finishNames = ['Printed ink', 'Foil', 'Holographic', 'Gold leaf', 'Frosted glass'] as const
|
||||
export const substrateNames = ['Paper', 'Linen', 'Metal', 'Wood', 'Leather'] as const
|
||||
export type FinishName = typeof finishNames[number]
|
||||
export type SubstrateName = typeof substrateNames[number] | 'Plastic'
|
||||
|
||||
export interface MaterialControls {
|
||||
finish: FinishName
|
||||
@@ -31,6 +33,7 @@ export function applyEdgeMaterialControls(
|
||||
Plastic: { color: '#d4cab0', wornColor: '#c7ccd2', roughness: 0.24, metalness: 0, clearcoat: 0.72 },
|
||||
Metal: { color: '#a8adb7', wornColor: '#d0d5dc', roughness: 0.2, metalness: 0.86, clearcoat: 0.18 },
|
||||
Wood: { color: '#6b3f21', wornColor: '#a87847', roughness: 0.56, metalness: 0, clearcoat: 0.08 },
|
||||
Leather: { color: '#503023', wornColor: '#976c49', roughness: 0.72, metalness: 0, clearcoat: 0.04 },
|
||||
}
|
||||
const look = edgeLooks[controls.substrate]
|
||||
material.color.set(look.color)
|
||||
@@ -134,6 +137,13 @@ const fragmentShader = `
|
||||
return sin(phase) * visibility;
|
||||
}
|
||||
|
||||
float leatherGrain(vec2 uv) {
|
||||
vec2 p = uv * vec2(42.0, 58.8);
|
||||
float visibility = 1.0 - smoothstep(0.2, 0.65, max(fwidth(p.x), fwidth(p.y)));
|
||||
float pebbles = smoothstep(0.15, 0.85, fiberNoise(p));
|
||||
return mix(0.5, pebbles, visibility);
|
||||
}
|
||||
|
||||
// Height and analytic UV slopes keep thread ridges lit even at oblique angles.
|
||||
vec3 linenRelief(vec2 uv, vec4 visibility) {
|
||||
vec2 frequency = vec2(52.0, 73.0) * 6.2831853;
|
||||
@@ -205,6 +215,7 @@ const fragmentShader = `
|
||||
if (substrateMode == 2) wearColor = vec3(0.78, 0.80, 0.82);
|
||||
if (substrateMode == 3) wearColor = vec3(0.72, 0.75, 0.80);
|
||||
if (substrateMode == 4) wearColor = vec3(0.62, 0.39, 0.20);
|
||||
if (substrateMode == 6) wearColor = vec3(0.59, 0.40, 0.25);
|
||||
float abrasion = clamp(
|
||||
edgeWear * 0.55 + scratchWear * 0.30 + scuffWear * 0.22,
|
||||
0.0,
|
||||
@@ -280,6 +291,8 @@ const fragmentShader = `
|
||||
float woodSurfaceGrain = 0.5;
|
||||
float metalEtchDepth = 0.0;
|
||||
float metalEtchExposure = 1.0;
|
||||
float leatherPebble = 0.5;
|
||||
float leatherCoverage = smoothstep(0.08, 0.65, mask);
|
||||
if (substrateMode == 0) {
|
||||
vec2 fiberUv = artworkUv * vec2(230.0, 322.0);
|
||||
float visible = 1.0 - smoothstep(0.4, 1.2, max(fwidth(fiberUv.x), fwidth(fiberUv.y)));
|
||||
@@ -328,8 +341,33 @@ const fragmentShader = `
|
||||
surfaceHeight = woodSurfaceGrain * relief;
|
||||
surfaceShade = 0.88 + woodSurfaceGrain * 0.12;
|
||||
}
|
||||
if (substrateMode == 6) {
|
||||
vec3 view = normalize(cameraPosition - vWorldPosition);
|
||||
vec2 alongSurface = vec2(dot(view, normalize(vWorldTangent)),
|
||||
dot(view, normalize(vWorldBitangent)));
|
||||
float relief = 0.0032 * min(detail, 1.5);
|
||||
float grain = leatherGrain(artworkUv);
|
||||
vec2 parallax = alongSurface / max(abs(dot(view, normalize(vWorldNormal))), 0.35)
|
||||
* (grain - 0.5) * relief / cardFaceSize;
|
||||
leatherPebble = leatherGrain(artworkUv + parallax);
|
||||
// Treat protected ink as a smooth printed layer, not recessed leather grain.
|
||||
surfaceHeight = (leatherPebble - 0.5) * relief * leatherCoverage;
|
||||
surfaceShade = 1.0 - (1.0 - leatherPebble) * 0.10 * min(detail, 1.5) * leatherCoverage;
|
||||
}
|
||||
// Only coating microrelief follows the finish mask; protected text stays flat.
|
||||
if (finishMode == 3) {
|
||||
float leafFold = filteredWave(artworkUv.x * 270.0
|
||||
+ fiberNoise(artworkUv * vec2(28.0, 39.2)) * 16.0);
|
||||
surfaceHeight += leafFold * 0.0012 * mask * finishStrength;
|
||||
}
|
||||
if (finishMode == 5) {
|
||||
vec2 frostUv = artworkUv * vec2(190.0, 266.0);
|
||||
float visible = 1.0 - smoothstep(0.4, 1.2, max(fwidth(frostUv.x), fwidth(frostUv.y)));
|
||||
surfaceHeight += (fiberNoise(frostUv) - 0.5) * visible * 0.0025 * mask * finishStrength;
|
||||
}
|
||||
surfaceHeight -= scratchWear * 0.0018 + scuffWear * 0.0007;
|
||||
float substrateNormalScale = substrateMode == 3 ? 0.06 : 0.12;
|
||||
if (substrateMode == 6) substrateNormalScale = 0.0;
|
||||
vec3 normal = normalize(
|
||||
vWorldNormal +
|
||||
vWorldTangent * normalSample.x * normalStrength * substrateNormalScale +
|
||||
@@ -369,6 +407,7 @@ const fragmentShader = `
|
||||
if (substrateMode == 2) substrateRoughness = min(roughness, 0.25);
|
||||
if (substrateMode == 3) substrateRoughness = min(roughness, 0.20);
|
||||
if (substrateMode == 4) substrateRoughness = max(roughness, 0.48);
|
||||
if (substrateMode == 6) substrateRoughness = max(roughness, 0.62);
|
||||
|
||||
float nDotV = max(dot(normal, viewDirection), 0.0);
|
||||
float dielectricFresnel = 0.04 + 0.96 * pow(1.0 - nDotV, 5.0);
|
||||
@@ -420,6 +459,10 @@ const fragmentShader = `
|
||||
vec3 woodTint = mix(vec3(0.72, 0.40, 0.18), vec3(1.0, 0.86, 0.65), woodSurfaceGrain);
|
||||
printedInk *= mix(vec3(1.0), woodTint, 0.25);
|
||||
}
|
||||
if (substrateMode == 6) {
|
||||
printedInk *= mix(vec3(1.0), vec3(0.98, 0.94, 0.88), leatherCoverage);
|
||||
printedInk += lightColor * substrateSpecular * leatherPebble * 0.12 * leatherCoverage;
|
||||
}
|
||||
if (finishMode == 0) {
|
||||
vec3 wornInk = applyWearColor(printedInk, edgeWear, scratchWear, scuffWear);
|
||||
gl_FragColor = vec4(wornInk, artworkSample.a);
|
||||
@@ -434,6 +477,32 @@ const fragmentShader = `
|
||||
float specularPower = mix(96.0, 10.0, substrateRoughness);
|
||||
float directSpecular = pow(max(dot(normal, halfVector), 0.0), specularPower);
|
||||
float environmentBrightness = dot(environment, vec3(0.2126, 0.7152, 0.0722));
|
||||
if (finishMode >= 3) {
|
||||
vec3 treatment = printedInk;
|
||||
if (finishMode == 3) {
|
||||
float leafMottle = fiberNoise(artworkUv * vec2(38.0, 53.2));
|
||||
vec3 gold = mix(vec3(0.76, 0.51, 0.19), vec3(0.92, 0.71, 0.34), leafMottle);
|
||||
float inkLuma = dot(artworkSample.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
vec3 gilding = gold * (0.42 + direct * 0.30 + inkLuma * 0.28);
|
||||
gilding += gold * environment * environmentIntensity * (0.30 + fresnel * 0.35);
|
||||
gilding += lightColor * vec3(1.0, 0.85, 0.46)
|
||||
* min(directSpecular * lightIntensity * attenuation * 0.40, 0.28);
|
||||
treatment = mix(printedInk, gilding, 0.48);
|
||||
}
|
||||
if (finishMode == 5) {
|
||||
float satin = pow(max(dot(normal, halfVector), 0.0), 8.0)
|
||||
* diffuse * lightIntensity * attenuation;
|
||||
vec3 coolFrost = vec3(0.79, 0.90, 0.95);
|
||||
treatment = mix(printedInk, coolFrost * (0.38 + direct * 0.32), 0.32);
|
||||
treatment += coolFrost * (satin * 0.22 + fresnel * environmentIntensity * 0.32);
|
||||
}
|
||||
vec3 coated = mix(printedInk, treatment, clamp(mask * finishStrength, 0.0, 1.0));
|
||||
coated = applyWearColor(coated, edgeWear, scratchWear, scuffWear);
|
||||
gl_FragColor = vec4(coated, artworkSample.a);
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
return;
|
||||
}
|
||||
if (substrateMode == 3 && finishMode == 1) {
|
||||
float plateSpecular = pow(max(dot(normal, halfVector), 0.0), 150.0);
|
||||
float plateEdge = smoothstep(0.003, 0.055, fwidth(mask));
|
||||
@@ -555,6 +624,8 @@ export function applyMaterialControls(
|
||||
'Printed ink': 0,
|
||||
Foil: 1,
|
||||
Holographic: 2,
|
||||
'Gold leaf': 3,
|
||||
'Frosted glass': 5,
|
||||
}
|
||||
const substrateModes: Record<SubstrateName, number> = {
|
||||
Paper: 0,
|
||||
@@ -562,6 +633,7 @@ export function applyMaterialControls(
|
||||
Plastic: 2,
|
||||
Metal: 3,
|
||||
Wood: 4,
|
||||
Leather: 6,
|
||||
}
|
||||
material.uniforms.finishMode.value = finishModes[controls.finish]
|
||||
material.uniforms.substrateMode.value = substrateModes[controls.substrate]
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
applyMaterialControls,
|
||||
createCardMaterial,
|
||||
createStudioCubeTexture,
|
||||
finishNames,
|
||||
substrateNames,
|
||||
type FinishName,
|
||||
type MaterialControls,
|
||||
type SubstrateName,
|
||||
@@ -101,17 +103,12 @@ document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
|
||||
</label>
|
||||
<label>Finish
|
||||
<select id="finish">
|
||||
<option>Holographic</option>
|
||||
<option>Foil</option>
|
||||
<option>Printed ink</option>
|
||||
${finishNames.map(name => `<option${name === 'Holographic' ? ' selected' : ''}>${name}</option>`).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label>Material
|
||||
<select id="substrate">
|
||||
<option>Paper</option>
|
||||
<option>Linen</option>
|
||||
<option>Metal</option>
|
||||
<option>Wood</option>
|
||||
${substrateNames.map(name => `<option>${name}</option>`).join('')}
|
||||
</select>
|
||||
</label>
|
||||
<label>Pose
|
||||
@@ -426,13 +423,13 @@ let applyingLightingPreset = false
|
||||
|
||||
const gui = new GUI({ title: 'Material proof' })
|
||||
const surfaceFolder = gui.addFolder('Surface')
|
||||
surfaceFolder.add(controls, 'finish', ['Printed ink', 'Foil', 'Holographic']).name('Finish').onChange((value: FinishName) => {
|
||||
surfaceFolder.add(controls, 'finish', [...finishNames]).name('Finish').onChange((value: FinishName) => {
|
||||
pauseScriptedMotion()
|
||||
finishSelect.value = value
|
||||
updateMaterial()
|
||||
updateStatus()
|
||||
})
|
||||
surfaceFolder.add(controls, 'substrate', ['Paper', 'Linen', 'Metal', 'Wood']).name('Material').onChange((value: SubstrateName) => {
|
||||
surfaceFolder.add(controls, 'substrate', [...substrateNames]).name('Material').onChange((value: SubstrateName) => {
|
||||
pauseScriptedMotion()
|
||||
substrateSelect.value = value
|
||||
updateMaterial()
|
||||
@@ -832,16 +829,9 @@ function isExperimentSnapshot(value: unknown): value is ExperimentSnapshot {
|
||||
if (typeof value !== 'object' || value === null) return false
|
||||
const snapshot = value as Record<string, unknown>
|
||||
const fixtureValid = typeof snapshot.fixture === 'string' && snapshot.fixture.length > 0
|
||||
const finishValid =
|
||||
snapshot.finish === 'Printed ink' ||
|
||||
snapshot.finish === 'Foil' ||
|
||||
snapshot.finish === 'Holographic'
|
||||
const substrateValid =
|
||||
snapshot.substrate === 'Paper' ||
|
||||
snapshot.substrate === 'Linen' ||
|
||||
snapshot.substrate === 'Plastic' ||
|
||||
snapshot.substrate === 'Metal' ||
|
||||
snapshot.substrate === 'Wood'
|
||||
const finishValid = finishNames.some(name => name === snapshot.finish)
|
||||
const substrateValid = snapshot.substrate === 'Plastic' ||
|
||||
substrateNames.some(name => name === snapshot.substrate)
|
||||
const lightingPresetValid =
|
||||
snapshot.lightingPreset === 'Studio' ||
|
||||
snapshot.lightingPreset === 'Warm gallery' ||
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
applyEdgeMaterialControls,
|
||||
applyMaterialControls,
|
||||
createCardMaterial,
|
||||
finishNames,
|
||||
substrateNames,
|
||||
type FinishName,
|
||||
type SubstrateName,
|
||||
} from './cardMaterial'
|
||||
@@ -20,8 +22,8 @@ export interface PackCardSpec {
|
||||
}
|
||||
|
||||
export const packSize = 10
|
||||
export const packFinishes: readonly FinishName[] = ['Printed ink', 'Foil', 'Holographic']
|
||||
export const packSubstrates: readonly SubstrateName[] = ['Paper', 'Linen', 'Metal', 'Wood']
|
||||
export const packFinishes = finishNames
|
||||
export const packSubstrates = substrateNames
|
||||
|
||||
function randomChoice<T>(values: readonly T[], random: () => number) {
|
||||
const roll = random()
|
||||
|
||||
Reference in New Issue
Block a user