mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
feat: enhance language selection and boundary falloff features
- Added a language dropdown selector to replace the previous button-based language toggle. - Integrated boundary falloff settings into the preview material, allowing for smoother transitions between masked and unmasked areas. - Updated shaders to utilize boundary falloff attributes for improved visual fidelity in bump-only previews. - Refactored related CSS styles for the new dropdown and adjusted layout for better usability. - Introduced new functions for computing boundary attributes and managing edge data textures.
This commit is contained in:
+84
-7
@@ -202,12 +202,17 @@ const vertexShader = /* glsl */`
|
||||
attribute vec3 smoothNormal;
|
||||
attribute vec3 faceNormal;
|
||||
attribute float faceMask;
|
||||
attribute float boundaryFalloffAttr;
|
||||
attribute float boundaryMaskTypeAttr;
|
||||
|
||||
varying vec3 vModelPos; // ORIGINAL model-space position → UV computation in fragment
|
||||
varying vec3 vModelNormal; // model-space face normal → stable UV blending
|
||||
varying vec3 vViewPos; // view-space position (possibly displaced) → TBN & specular
|
||||
varying vec3 vNormal; // view-space normal → lighting
|
||||
varying float vFaceMask; // combined mask (angle + user exclusion)
|
||||
varying vec3 vSmoothNormal; // view-space smooth normal → smooth shading on masked faces
|
||||
varying float vFaceMask; // combined mask (angle + user exclusion + boundary falloff)
|
||||
varying float vUserMask; // raw user-exclusion mask (0 = user-excluded, 1 = included)
|
||||
varying float vMaskType; // boundary mask type (0 = user mask, 1 = angle mask)
|
||||
|
||||
void main() {
|
||||
vec3 safeN = length(normal) > 1e-6 ? normalize(normal) : vec3(0.0, 0.0, 1.0);
|
||||
@@ -223,8 +228,10 @@ const vertexShader = /* glsl */`
|
||||
angleMask = min(angleMask, surfaceAngle > bottomAngleLimit ? 1.0 : 0.0);
|
||||
if (fN.z >= 0.0 && topAngleLimit >= 1.0)
|
||||
angleMask = min(angleMask, surfaceAngle > topAngleLimit ? 1.0 : 0.0);
|
||||
float totalMask = angleMask * faceMask;
|
||||
float totalMask = angleMask * faceMask * boundaryFalloffAttr;
|
||||
vFaceMask = totalMask;
|
||||
vUserMask = faceMask;
|
||||
vMaskType = boundaryMaskTypeAttr;
|
||||
|
||||
if (useDisplacement == 1) {
|
||||
float h = computeHeightAtPoint(position, safeN, safeN);
|
||||
@@ -243,6 +250,8 @@ const vertexShader = /* glsl */`
|
||||
vec4 mvPos = modelViewMatrix * vec4(pos, 1.0);
|
||||
vViewPos = mvPos.xyz;
|
||||
vNormal = normalize(normalMatrix * fN);
|
||||
vec3 sN = length(smoothNormal) > 1e-6 ? normalize(smoothNormal) : safeN;
|
||||
vSmoothNormal = normalize(normalMatrix * sN);
|
||||
gl_Position = projectionMatrix * mvPos;
|
||||
}
|
||||
`;
|
||||
@@ -251,11 +260,19 @@ const fragmentShader = /* glsl */`
|
||||
precision highp float;
|
||||
${sharedGLSL}
|
||||
|
||||
uniform sampler2D boundaryEdgeTex;
|
||||
uniform int boundaryEdgeCount;
|
||||
uniform float boundaryEdgeTexWidth;
|
||||
uniform float boundaryFalloffDist;
|
||||
|
||||
varying vec3 vModelPos;
|
||||
varying vec3 vModelNormal;
|
||||
varying vec3 vViewPos;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vSmoothNormal;
|
||||
varying float vFaceMask;
|
||||
varying float vUserMask;
|
||||
varying float vMaskType;
|
||||
|
||||
// Fragment-only wrapper: compute face-stable projection normal via dFdx
|
||||
// then delegate to the shared height function.
|
||||
@@ -282,6 +299,27 @@ const fragmentShader = /* glsl */`
|
||||
|
||||
// ── Combined mask (angle + user exclusion) from vertex shader ────────
|
||||
float maskBlend = vFaceMask;
|
||||
|
||||
// Per-fragment boundary falloff for bump-only mode. On coarse meshes the
|
||||
// vertex attribute cannot produce a gradient (too few vertices), so we
|
||||
// compute the distance from each pixel to the nearest boundary edge.
|
||||
if (useDisplacement == 0 && boundaryFalloffDist > 0.001 && boundaryEdgeCount > 0) {
|
||||
float minDist = boundaryFalloffDist;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (i >= boundaryEdgeCount) break;
|
||||
float uA = (float(i * 2) + 0.5) / boundaryEdgeTexWidth;
|
||||
float uB = (float(i * 2 + 1) + 0.5) / boundaryEdgeTexWidth;
|
||||
vec3 ea = texture2D(boundaryEdgeTex, vec2(uA, 0.5)).xyz;
|
||||
vec3 eb = texture2D(boundaryEdgeTex, vec2(uB, 0.5)).xyz;
|
||||
vec3 ab = eb - ea;
|
||||
float abLen2 = dot(ab, ab);
|
||||
float t = clamp(dot(vModelPos - ea, ab) / max(abLen2, 1e-10), 0.0, 1.0);
|
||||
float d = length(vModelPos - (ea + t * ab));
|
||||
if (d < minDist) { minDist = d; if (d < 1e-4) break; }
|
||||
}
|
||||
maskBlend *= clamp(minDist / boundaryFalloffDist, 0.0, 1.0);
|
||||
}
|
||||
|
||||
h *= maskBlend;
|
||||
dhx *= maskBlend;
|
||||
dhy *= maskBlend;
|
||||
@@ -306,8 +344,20 @@ const fragmentShader = /* glsl */`
|
||||
vec3 bumpVec = N - bumpStr * (dhx * T + dhy * B);
|
||||
vec3 bumpN = length(bumpVec) > 1e-6 ? normalize(bumpVec) : N;
|
||||
|
||||
// On fully masked faces the bump derivatives are zero, so bumpN falls
|
||||
// back to the flat face normal → faceted/static look. Blend toward
|
||||
// the smooth interpolated normal so masked areas get smooth shading.
|
||||
vec3 smoothN = normalize(vSmoothNormal) * (gl_FrontFacing ? 1.0 : -1.0);
|
||||
bumpN = mix(smoothN, bumpN, maskBlend);
|
||||
|
||||
// ── Shading ───────────────────────────────────────────────────────────
|
||||
vec3 baseColor = mix(vec3(0.50, 0.50, 0.50), vec3(0.22, 0.68, 0.68), maskBlend);
|
||||
// Compute lighting identically for ALL surfaces using the teal base so
|
||||
// that specular highlights, diffuse response, and view-dependent shading
|
||||
// are perfectly consistent everywhere. Mask tinting is applied AFTER
|
||||
// lighting as a colour blend so masked areas keep the same glossy look.
|
||||
vec3 tealBase = vec3(0.22, 0.68, 0.68);
|
||||
vec3 userMaskColor = vec3(0.85, 0.40, 0.15);
|
||||
vec3 angleMaskColor = vec3(0.45, 0.48, 0.50);
|
||||
|
||||
vec3 L1 = normalize(vec3( 0.5, 0.8, 1.0));
|
||||
vec3 L2 = normalize(vec3(-0.5, -0.2, -0.6));
|
||||
@@ -319,10 +369,23 @@ const fragmentShader = /* glsl */`
|
||||
vec3 H1 = normalize(L1 + V);
|
||||
float spec = pow(max(dot(bumpN, H1), 0.0), 64.0) * 0.60;
|
||||
|
||||
vec3 color = baseColor * 0.55 // ambient
|
||||
+ baseColor * diff1 * vec3(1.00, 0.96, 0.88) * 0.55 // key light
|
||||
+ baseColor * diff2 * vec3(0.80, 0.60, 0.50) * 0.15 // warm fill
|
||||
+ vec3(spec); // specular
|
||||
// Lit teal (identical for textured and masked surfaces)
|
||||
vec3 litTeal = tealBase * 0.55
|
||||
+ tealBase * diff1 * vec3(1.00, 0.96, 0.88) * 0.55
|
||||
+ tealBase * diff2 * vec3(0.80, 0.60, 0.50) * 0.15
|
||||
+ vec3(spec);
|
||||
|
||||
// Mask tint: pick colour by mask type, compute same lighting with that base
|
||||
float maskEffect = 1.0 - maskBlend; // 0 = fully textured, 1 = fully masked
|
||||
float effectiveMaskType = mix(vMaskType, 0.0, step(0.5, 1.0 - vUserMask));
|
||||
vec3 maskBase = mix(userMaskColor, angleMaskColor, effectiveMaskType);
|
||||
vec3 litMask = maskBase * 0.55
|
||||
+ maskBase * diff1 * vec3(1.00, 0.96, 0.88) * 0.55
|
||||
+ maskBase * diff2 * vec3(0.80, 0.60, 0.50) * 0.15
|
||||
+ vec3(spec);
|
||||
|
||||
// Blend: 100% mask colour at the boundary, fading to 0% at falloff distance
|
||||
vec3 color = mix(litTeal, litMask, maskEffect);
|
||||
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
@@ -371,6 +434,7 @@ export function updateMaterial(material, displacementTexture, settings) {
|
||||
u.symmetricDisplacement.value = settings.symmetricDisplacement ? 1 : 0;
|
||||
u.useDisplacement.value = settings.useDisplacement ? 1 : 0;
|
||||
u.textureAspect.value.set(settings.textureAspectU ?? 1, settings.textureAspectV ?? 1);
|
||||
u.boundaryFalloffDist.value = settings.boundaryFalloff ?? 0.0;
|
||||
}
|
||||
|
||||
// ── Internal ──────────────────────────────────────────────────────────────────
|
||||
@@ -399,6 +463,10 @@ function buildUniforms(tex, settings) {
|
||||
symmetricDisplacement: { value: settings.symmetricDisplacement ? 1 : 0 },
|
||||
useDisplacement: { value: settings.useDisplacement ? 1 : 0 },
|
||||
textureAspect: { value: new THREE.Vector2(settings.textureAspectU ?? 1, settings.textureAspectV ?? 1) },
|
||||
boundaryEdgeTex: { value: createFallbackDataTexture() },
|
||||
boundaryEdgeCount: { value: 0 },
|
||||
boundaryEdgeTexWidth: { value: 1.0 },
|
||||
boundaryFalloffDist: { value: settings.boundaryFalloff ?? 0.0 },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -412,3 +480,12 @@ function createFallbackTexture() {
|
||||
t.wrapS = t.wrapT = THREE.RepeatWrapping;
|
||||
return t;
|
||||
}
|
||||
|
||||
function createFallbackDataTexture() {
|
||||
const data = new Float32Array(4);
|
||||
const t = new THREE.DataTexture(data, 1, 1, THREE.RGBAFormat, THREE.FloatType);
|
||||
t.minFilter = THREE.NearestFilter;
|
||||
t.magFilter = THREE.NearestFilter;
|
||||
t.needsUpdate = true;
|
||||
return t;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user