Add table shader diagnostics and candle shadow model
This commit is contained in:
+101
-34
@@ -2,6 +2,23 @@ import * as THREE from 'https://esm.sh/three@0.165.0';
|
|||||||
|
|
||||||
const canvas = document.getElementById('scene');
|
const canvas = document.getElementById('scene');
|
||||||
canvas.style.cursor = 'grab';
|
canvas.style.cursor = 'grab';
|
||||||
|
const tableDebugModes = {
|
||||||
|
none: 0,
|
||||||
|
shadow: 1,
|
||||||
|
dust: 2,
|
||||||
|
normal: 3,
|
||||||
|
room: 4,
|
||||||
|
scene: 5,
|
||||||
|
mask: 6,
|
||||||
|
ao: 7
|
||||||
|
};
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const tableDebugName = urlParams.get('tableDebug') || 'none';
|
||||||
|
const tableDebugMode = tableDebugModes[tableDebugName] ?? tableDebugModes.none;
|
||||||
|
const labStatus = document.getElementById('lab_status');
|
||||||
|
if (labStatus && tableDebugMode !== tableDebugModes.none) {
|
||||||
|
labStatus.textContent = `table debug: ${tableDebugName}`;
|
||||||
|
}
|
||||||
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: false });
|
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: false });
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
@@ -59,6 +76,11 @@ const cameraRig = {
|
|||||||
pointerY: 0,
|
pointerY: 0,
|
||||||
keys: new Set()
|
keys: new Set()
|
||||||
};
|
};
|
||||||
|
if (urlParams.get('view') === 'wide') {
|
||||||
|
cameraRig.target.set(0, 0.05, 0);
|
||||||
|
cameraRig.pitch = 0.96;
|
||||||
|
cameraRig.radius = 7.8;
|
||||||
|
}
|
||||||
updateCameraRig(0);
|
updateCameraRig(0);
|
||||||
|
|
||||||
const clock = new THREE.Clock();
|
const clock = new THREE.Clock();
|
||||||
@@ -162,11 +184,11 @@ function buildTable() {
|
|||||||
color: 0x8a4c22,
|
color: 0x8a4c22,
|
||||||
map: tableTexture,
|
map: tableTexture,
|
||||||
normalMap: tableNormal,
|
normalMap: tableNormal,
|
||||||
normalScale: new THREE.Vector2(0.07, 0.07),
|
normalScale: new THREE.Vector2(0.22, 0.18),
|
||||||
roughness: 0.31,
|
roughness: 0.38,
|
||||||
metalness: 0,
|
metalness: 0,
|
||||||
clearcoat: 0.54,
|
clearcoat: 0.54,
|
||||||
clearcoatRoughness: 0.42,
|
clearcoatRoughness: 0.48,
|
||||||
reflectivity: 0.34,
|
reflectivity: 0.34,
|
||||||
envMapIntensity: 0
|
envMapIntensity: 0
|
||||||
});
|
});
|
||||||
@@ -238,7 +260,14 @@ function addCandle(x, y, z, intensity, height) {
|
|||||||
const baseLightIntensity = intensity * 7.4;
|
const baseLightIntensity = intensity * 7.4;
|
||||||
const light = new THREE.PointLight(0xff9f45, baseLightIntensity, 4.35, 1.86);
|
const light = new THREE.PointLight(0xff9f45, baseLightIntensity, 4.35, 1.86);
|
||||||
light.position.copy(flame.position);
|
light.position.copy(flame.position);
|
||||||
light.castShadow = false;
|
light.castShadow = true;
|
||||||
|
light.shadow.mapSize.set(1024, 1024);
|
||||||
|
light.shadow.bias = -0.00004;
|
||||||
|
light.shadow.normalBias = 0.018;
|
||||||
|
light.shadow.radius = 5;
|
||||||
|
light.shadow.blurSamples = 12;
|
||||||
|
light.shadow.camera.near = 0.04;
|
||||||
|
light.shadow.camera.far = 5.0;
|
||||||
candle.add(light);
|
candle.add(light);
|
||||||
|
|
||||||
candle.userData = {
|
candle.userData = {
|
||||||
@@ -486,6 +515,7 @@ function configureTableShader(material) {
|
|||||||
shader.uniforms.candleBodyData = {
|
shader.uniforms.candleBodyData = {
|
||||||
value: [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()]
|
value: [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()]
|
||||||
};
|
};
|
||||||
|
shader.uniforms.tableDebugMode = { value: tableDebugMode };
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader
|
shader.vertexShader = shader.vertexShader
|
||||||
.replace(
|
.replace(
|
||||||
@@ -508,6 +538,7 @@ function configureTableShader(material) {
|
|||||||
uniform vec3 candleBodyPositions[3];
|
uniform vec3 candleBodyPositions[3];
|
||||||
uniform vec3 candleFlamePositions[3];
|
uniform vec3 candleFlamePositions[3];
|
||||||
uniform vec2 candleBodyData[3];
|
uniform vec2 candleBodyData[3];
|
||||||
|
uniform int tableDebugMode;
|
||||||
varying vec3 vTableWorldPosition;
|
varying vec3 vTableWorldPosition;
|
||||||
varying vec4 vSceneReflectionCoord;
|
varying vec4 vSceneReflectionCoord;
|
||||||
|
|
||||||
@@ -536,41 +567,68 @@ function configureTableShader(material) {
|
|||||||
sampleRoomReflection(dir - bitangent * 0.026) * 0.11;
|
sampleRoomReflection(dir - bitangent * 0.026) * 0.11;
|
||||||
}
|
}
|
||||||
|
|
||||||
float candleBodyOcclusion(vec3 point, vec3 flame, vec3 body, vec2 bodyData) {
|
float candleBodyOcclusion(vec3 point, vec3 flame, vec3 body, vec2 bodyData, float selfLight) {
|
||||||
vec3 ray = point - flame;
|
vec3 segment = point - flame;
|
||||||
float rayLen = max(length(ray), 0.0001);
|
vec2 segmentXZ = segment.xz;
|
||||||
vec3 dir = ray / rayLen;
|
vec2 flameToBody = flame.xz - body.xz;
|
||||||
float t = clamp(dot(body - flame, dir), 0.0, rayLen);
|
float radius = bodyData.x;
|
||||||
vec3 closest = flame + dir * t;
|
float a = max(dot(segmentXZ, segmentXZ), 0.000001);
|
||||||
float lateral = length((body - closest).xz);
|
float b = 2.0 * dot(flameToBody, segmentXZ);
|
||||||
|
float c = dot(flameToBody, flameToBody) - radius * radius;
|
||||||
|
float discriminant = b * b - 4.0 * a * c;
|
||||||
|
float nearestT = clamp(-dot(flameToBody, segmentXZ) / a, 0.0, 1.0);
|
||||||
|
vec2 nearestXZ = flameToBody + segmentXZ * nearestT;
|
||||||
|
float nearestDistance = length(nearestXZ);
|
||||||
|
float hitT = nearestT;
|
||||||
|
float cylinderHit = 0.0;
|
||||||
|
if (discriminant > 0.0) {
|
||||||
|
float sqrtDisc = sqrt(discriminant);
|
||||||
|
float t0 = (-b - sqrtDisc) / (2.0 * a);
|
||||||
|
float t1 = (-b + sqrtDisc) / (2.0 * a);
|
||||||
|
float validT0 = step(0.0, t0) * step(t0, 1.0);
|
||||||
|
float validT1 = step(0.0, t1) * step(t1, 1.0);
|
||||||
|
hitT = mix(hitT, t0, validT0);
|
||||||
|
hitT = mix(hitT, t1, (1.0 - validT0) * validT1);
|
||||||
|
cylinderHit = max(validT0, validT1);
|
||||||
|
}
|
||||||
|
float closestY = flame.y + segment.y * hitT;
|
||||||
float bodyTop = body.y + bodyData.y;
|
float bodyTop = body.y + bodyData.y;
|
||||||
float vertical = smoothstep(body.y - 0.05, body.y + 0.14, closest.y) *
|
float vertical = smoothstep(body.y - 0.045, body.y + 0.08, closestY) *
|
||||||
(1.0 - smoothstep(bodyTop - 0.12, bodyTop + 0.08, closest.y));
|
(1.0 - smoothstep(bodyTop - 0.08, bodyTop + 0.045, closestY));
|
||||||
float sourceDistance = length(flame - body);
|
float segmentLength = length(segment);
|
||||||
float penumbra = bodyData.x * (2.2 + sourceDistance * 1.65 + rayLen * 0.34);
|
float penumbraWidth = radius * (0.45 + segmentLength * 0.12);
|
||||||
float umbra = 1.0 - smoothstep(bodyData.x * 0.45, bodyData.x * 1.16, lateral);
|
float exactHit = cylinderHit;
|
||||||
float softEdge = 1.0 - smoothstep(bodyData.x * 1.05, penumbra, lateral);
|
float softHit = 1.0 - smoothstep(radius, radius + penumbraWidth, nearestDistance);
|
||||||
float travelFade = 1.0 - smoothstep(1.65, 4.2, rayLen);
|
float selfShadowLimiter = mix(1.0, 0.06, selfLight);
|
||||||
float waxTransmission = 0.38 + 0.28 * smoothstep(bodyTop - 0.32, bodyTop + 0.05, closest.y);
|
float waxExitHeight = smoothstep(body.y, bodyTop, closestY);
|
||||||
return clamp((umbra * 0.34 + softEdge * 0.2) * vertical * travelFade * waxTransmission, 0.0, 0.38);
|
float waxTransmission = 0.48 + 0.34 * waxExitHeight;
|
||||||
|
float bodyOpacity = 1.0 - waxTransmission * mix(0.62, 0.86, selfLight);
|
||||||
|
return clamp(max(exactHit, softHit * 0.72) * vertical * selfShadowLimiter * bodyOpacity, 0.0, 0.42);
|
||||||
}
|
}
|
||||||
|
|
||||||
float candleContactOcclusion(vec3 point, vec3 body, vec2 bodyData) {
|
float candleContactOcclusion(vec3 point, vec3 body, vec2 bodyData) {
|
||||||
vec2 delta = point.xz - body.xz;
|
vec2 delta = point.xz - body.xz;
|
||||||
float base = 1.0 - smoothstep(bodyData.x * 0.72, bodyData.x * 2.55, length(delta));
|
float base = 1.0 - smoothstep(bodyData.x * 0.72, bodyData.x * 2.55, length(delta));
|
||||||
return base * 0.18;
|
return base * 0.32;
|
||||||
}
|
}
|
||||||
|
|
||||||
float candleOcclusionField(vec3 point) {
|
float candleContactField(vec3 point) {
|
||||||
float contact = 0.0;
|
float contact = 0.0;
|
||||||
float projectedShadow = 0.0;
|
|
||||||
for (int bodyIndex = 0; bodyIndex < 3; bodyIndex++) {
|
for (int bodyIndex = 0; bodyIndex < 3; bodyIndex++) {
|
||||||
contact = max(contact, candleContactOcclusion(point, candleBodyPositions[bodyIndex], candleBodyData[bodyIndex]));
|
contact = max(contact, candleContactOcclusion(point, candleBodyPositions[bodyIndex], candleBodyData[bodyIndex]));
|
||||||
|
}
|
||||||
|
return clamp(contact, 0.0, 0.36);
|
||||||
|
}
|
||||||
|
|
||||||
|
float candleProjectedShadowField(vec3 point) {
|
||||||
|
float projectedShadow = 0.0;
|
||||||
|
for (int bodyIndex = 0; bodyIndex < 3; bodyIndex++) {
|
||||||
for (int flameIndex = 0; flameIndex < 3; flameIndex++) {
|
for (int flameIndex = 0; flameIndex < 3; flameIndex++) {
|
||||||
projectedShadow = max(projectedShadow, candleBodyOcclusion(point, candleFlamePositions[flameIndex], candleBodyPositions[bodyIndex], candleBodyData[bodyIndex]));
|
float selfLight = bodyIndex == flameIndex ? 1.0 : 0.0;
|
||||||
|
projectedShadow = max(projectedShadow, candleBodyOcclusion(point, candleFlamePositions[flameIndex], candleBodyPositions[bodyIndex], candleBodyData[bodyIndex], selfLight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return clamp(contact + projectedShadow, 0.0, 0.46);
|
return clamp(projectedShadow, 0.0, 0.46);
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
@@ -588,25 +646,34 @@ function configureTableShader(material) {
|
|||||||
smoothstep(0.0, 0.08, sceneReflectionUv.y) *
|
smoothstep(0.0, 0.08, sceneReflectionUv.y) *
|
||||||
smoothstep(0.0, 0.08, 1.0 - sceneReflectionUv.x) *
|
smoothstep(0.0, 0.08, 1.0 - sceneReflectionUv.x) *
|
||||||
smoothstep(0.0, 0.08, 1.0 - sceneReflectionUv.y);
|
smoothstep(0.0, 0.08, 1.0 - sceneReflectionUv.y);
|
||||||
vec2 roughReflectionUv = sceneReflectionUv + normal.xz * 0.012;
|
vec2 roughReflectionUv = sceneReflectionUv + normal.xz * 0.024;
|
||||||
vec3 sceneReflection = texture2D(sceneReflectionMap, roughReflectionUv).rgb;
|
vec3 sceneReflection = texture2D(sceneReflectionMap, roughReflectionUv).rgb;
|
||||||
sceneReflection = pow(max(sceneReflection, vec3(0.0)), vec3(0.88)) * sceneReflectionInBounds * sceneReflectionEdge;
|
sceneReflection = pow(max(sceneReflection, vec3(0.0)), vec3(0.88)) * sceneReflectionInBounds * sceneReflectionEdge;
|
||||||
vec2 tableDustUv = clamp(vec2(vTableWorldPosition.x / 9.8 + 0.5, 0.5 - vTableWorldPosition.z / 6.6), vec2(0.0), vec2(1.0));
|
vec2 tableDustUv = clamp(vec2(vTableWorldPosition.x / 9.8 + 0.5, 0.5 - vTableWorldPosition.z / 6.6), vec2(0.0), vec2(1.0));
|
||||||
float dust = texture2D(tableDustMap, tableDustUv).r;
|
float dust = texture2D(tableDustMap, tableDustUv).r;
|
||||||
float reflectionCleanliness = 1.0 - dust * 0.52;
|
float reflectionCleanliness = 1.0 - dust * 0.62;
|
||||||
vec3 combinedReflection = (roomReflection * 0.22 + sceneReflection * 0.32) * reflectionCleanliness;
|
vec3 combinedReflection = (roomReflection * 0.18 + sceneReflection * 0.5) * reflectionCleanliness;
|
||||||
float fresnel = pow(1.0 - max(dot(viewDirWorld, tableNormalWorld), 0.0), 1.85);
|
float fresnel = pow(1.0 - max(dot(viewDirWorld, tableNormalWorld), 0.0), 1.85);
|
||||||
float tableReflectionMask = smoothstep(-0.095, -0.025, vTableWorldPosition.y);
|
float tableReflectionMask = smoothstep(-0.095, -0.025, vTableWorldPosition.y);
|
||||||
vec3 reflectedSurface = combinedReflection * (0.56 + fresnel * 0.44);
|
vec3 reflectedSurface = combinedReflection * (0.56 + fresnel * 0.44);
|
||||||
vec3 reflectionLift = max(reflectedSurface - outgoingLight * 0.18, vec3(0.0));
|
|
||||||
float contactAo = 1.0 - smoothstep(0.0, 0.9, length(vTableWorldPosition.xz * vec2(0.34, 0.58))) * 0.16;
|
float contactAo = 1.0 - smoothstep(0.0, 0.9, length(vTableWorldPosition.xz * vec2(0.34, 0.58))) * 0.16;
|
||||||
float dustDulling = dust * tableReflectionMask;
|
float dustDulling = dust * tableReflectionMask;
|
||||||
float candleOcclusion = candleOcclusionField(vTableWorldPosition) * tableReflectionMask;
|
float candleContact = candleContactField(vTableWorldPosition) * tableReflectionMask;
|
||||||
|
float candleProjectedShadow = candleProjectedShadowField(vTableWorldPosition) * tableReflectionMask;
|
||||||
|
float candleOcclusion = clamp(candleContact + candleProjectedShadow * 0.42, 0.0, 0.48);
|
||||||
|
vec3 normalDebug = normalize(normal) * 0.5 + 0.5;
|
||||||
outgoingLight *= mix(1.0, contactAo, tableReflectionMask * 0.55);
|
outgoingLight *= mix(1.0, contactAo, tableReflectionMask * 0.55);
|
||||||
outgoingLight += tableReflectionMask * reflectionLift * (0.12 + fresnel * 0.2) * reflectionCleanliness;
|
outgoingLight = mix(outgoingLight, reflectedSurface, tableReflectionMask * (0.07 + fresnel * 0.14) * reflectionCleanliness);
|
||||||
outgoingLight += tableReflectionMask * combinedReflection * 0.012;
|
outgoingLight += tableReflectionMask * roomReflection * 0.008 * reflectionCleanliness;
|
||||||
outgoingLight = mix(outgoingLight, outgoingLight * vec3(0.78, 0.74, 0.66) + vec3(0.035, 0.028, 0.02), dustDulling * 0.18);
|
outgoingLight = mix(outgoingLight, outgoingLight * vec3(0.72, 0.68, 0.6) + vec3(0.045, 0.036, 0.025), dustDulling * 0.32);
|
||||||
outgoingLight *= mix(vec3(1.0), vec3(0.62, 0.52, 0.42), candleOcclusion);
|
outgoingLight *= mix(vec3(1.0), vec3(0.5, 0.4, 0.31), candleOcclusion);
|
||||||
|
if (tableDebugMode == 1) outgoingLight = vec3(candleProjectedShadow);
|
||||||
|
if (tableDebugMode == 2) outgoingLight = vec3(dust);
|
||||||
|
if (tableDebugMode == 3) outgoingLight = normalDebug;
|
||||||
|
if (tableDebugMode == 4) outgoingLight = roomReflection;
|
||||||
|
if (tableDebugMode == 5) outgoingLight = sceneReflection;
|
||||||
|
if (tableDebugMode == 6) outgoingLight = vec3(tableReflectionMask);
|
||||||
|
if (tableDebugMode == 7) outgoingLight = vec3(candleContact);
|
||||||
#include <opaque_fragment>`
|
#include <opaque_fragment>`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user