build-wireframe-scan-reveal
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuild a Wireframe Scan Reveal
实现线框扫描揭示效果
Make one world-space wavefront conduct both representations: draw topology at the front, hold the solid behind it, then remove the temporary cage. Reach for when wireframe is the page's permanent visual language; reach for when the conductor is DOM scroll rather than a 3D wave.
technical-wireframe-info-layoutanimation-on-scrollExtracted from , where a survey pulse had to grow a procedural moss root through cards and type without reading as a circular mask.
inner-green-3d.html让一个世界空间波前同时呈现两种形态:在前沿绘制拓扑结构,在其后方显示实体,随后移除临时线框笼。如果线框是页面的永久视觉语言,请使用;如果控制器是DOM滚动而非3D波前,请使用。
technical-wireframe-info-layoutanimation-on-scroll该方案提取自,其中需要让探测脉冲生成程序化苔藓根状结构穿过卡片和文字,且不能呈现为圆形遮罩效果。
inner-green-3d.htmlPair two representations
搭配两种表现形式
Create the solid mesh and a temporary cage from the same source geometry. Share , , and time between their materials. Keep both in world space; local-space distance changes when a group is scaled or nested and tears the two fronts apart.
LineSegmentsuScanOriginuScanRadiusDiscard the solid beyond the front, with a lag that leaves the cage visible first:
glsl
uniform vec3 uScanOrigin;
uniform float uScanRadius;
uniform float uScanEnabled;
bool unscanned(vec3 worldPosition, float lag) {
if (uScanEnabled < 0.5) return false;
float wobble =
sin(worldPosition.y * 0.011 + worldPosition.x * 0.007) * 36.0
+ sin(worldPosition.z * 0.021 + worldPosition.y * 0.013) * 17.0;
return distance(worldPosition, uScanOrigin)
> uScanRadius - lag + wobble;
}
// In the solid fragment shader:
if (unscanned(vWorldPosition, 520.0)) discard;Use one origin outside the silhouette, usually low and left. A centered origin reads as a loading ring. Keep the two long sine terms; a perfectly circular front reads as a clip-path, while high-frequency noise makes the edge sparkle.
从同一源几何体创建实体网格和临时线框笼。在它们的材质之间共享、和时间参数。保持两者都处于世界空间;当组被缩放或嵌套时,局部空间距离会发生变化,导致两个前沿分离。
LineSegmentsuScanOriginuScanRadius丢弃前沿之外的实体部分,设置延迟使线框笼先显示:
glsl
uniform vec3 uScanOrigin;
uniform float uScanRadius;
uniform float uScanEnabled;
bool unscanned(vec3 worldPosition, float lag) {
if (uScanEnabled < 0.5) return false;
float wobble =
sin(worldPosition.y * 0.011 + worldPosition.x * 0.007) * 36.0
+ sin(worldPosition.z * 0.021 + worldPosition.y * 0.013) * 17.0;
return distance(worldPosition, uScanOrigin)
> uScanRadius - lag + wobble;
}
// 在实体片元着色器中:
if (unscanned(vWorldPosition, 520.0)) discard;选择一个轮廓之外的原点,通常在左下位置。居中原点会让人误以为是加载环。保留两个长正弦项;完美的圆形前沿看起来像裁剪路径,而高频噪声会让边缘产生闪烁效果。
Make the cage ride the front
让线框笼贴合前沿
Measure each line fragment against the same radius:
glsl
float distanceFromOrigin = distance(vWorldPosition, uScanOrigin);
float rim = exp(-pow((distanceFromOrigin - uScanRadius) / 135.0, 2.0));
float trail = smoothstep(uScanRadius, uScanRadius - 950.0, distanceFromOrigin);
float alpha = (rim * 1.60 + trail * 0.34) * uWireOpacity;Use additive blending, , , and a pale version of the scene accent. Keep depth testing on unless the cage must read through the object. Turning depth test off exposes the back half of dense topology and produces a bright unreadable ball.
transparent: truedepthWrite: false将每个线段片元与同一半径进行比较:
glsl
float distanceFromOrigin = distance(vWorldPosition, uScanOrigin);
float rim = exp(-pow((distanceFromOrigin - uScanRadius) / 135.0, 2.0));
float trail = smoothstep(uScanRadius, uScanRadius - 950.0, distanceFromOrigin);
float alpha = (rim * 1.60 + trail * 0.34) * uWireOpacity;使用加法混合、、,以及场景主色调的浅色版本。除非线框笼需要穿透物体显示,否则保持深度测试开启。关闭深度测试会暴露密集拓扑结构的后半部分,形成一个明亮但难以辨认的球体。
transparent: truedepthWrite: falseAnimate one conductor
为控制器添加动画
Use the landed timing as a starting point:
| parameter | default | failure prevented |
|---|---|---|
| duration | 3.4 s | a fast reveal reads as a flash |
| radius easing | | linear travel stalls visually near the far corner |
| solid lag | 520 world units | zero lag makes wire and fill arrive as one wipe |
| rim width | 135 units | a narrow rim aliases; a wide rim becomes fog |
| wire trail | 950 units | no trail hides topology before it can be read |
| wire fade | | persistent cage competes with the finished surface |
| radius reach | scene diagonal × 1.3 + 900 | the far corner remains clipped after completion |
Snap the cage on over the first 6% of the timeline, then fade it during the final 28%:
js
const e = Math.min(1, elapsed / 3.4);
scanRadius.value = easeOutPow(e) * maxRadius;
wireOpacity.value = Math.min(1, e / 0.06) * (1 - smoothstep(0.72, 1, e));At completion, disable the discard branch, remove the cage, and dispose its geometry and material. Hiding it with opacity leaves duplicate geometry and shader work alive for the rest of the page.
以落地时间为起点:
| 参数 | 默认值 | 避免的问题 |
|---|---|---|
| 时长 | 3.4 秒 | 过快的揭示效果会像闪光 |
| 半径缓动 | | 线性移动在远角附近视觉上会停滞 |
| 实体延迟 | 520 世界单位 | 零延迟会让线框和填充同时出现,如同普通过渡 |
| 边缘宽度 | 135 单位 | 过窄的边缘会出现锯齿;过宽的边缘会变成雾状 |
| 线框拖尾 | 950 单位 | 无拖尾会在拓扑结构被看清前就隐藏它 |
| 线框淡出 | | 持续存在的线框会与最终表面产生视觉冲突 |
| 半径范围 | 场景对角线 × 1.3 + 900 | 远角在完成后仍会被裁剪 |
在前6%的时间轴内快速显示线框笼,然后在最后28%的时间内淡出:
js
const e = Math.min(1, elapsed / 3.4);
scanRadius.value = easeOutPow(e) * maxRadius;
wireOpacity.value = Math.min(1, e / 0.06) * (1 - smoothstep(0.72, 1, e));完成后,禁用丢弃分支,移除线框笼,并销毁其几何体和材质。仅通过透明度隐藏会在页面剩余生命周期内保留重复的几何体和着色器工作。
Stage and size the scan
规划扫描的阶段和尺寸
Resolve after layout and after parent matrices update. Use a , guard zero-sized roots, update the camera projection, then recompute the origin and farthest reach. A one-time measurement before fonts or layout settle leaves the last corner unrevealed.
maxRadiusResizeObserverKeep the demo's scene recognisable: grey-green field, bottom light pool, white editorial type, pale cards, and the root-shaped form. The staging belongs in the demo; the reusable skill is only the paired representations and shared conductor.
在布局完成和父矩阵更新后计算。使用,防止根元素尺寸为零,更新相机投影,然后重新计算原点和最远范围。在字体或布局稳定前进行一次性测量会导致最后一个角落无法被揭示。
maxRadiusResizeObserver保持演示场景的辨识度:灰绿色背景、底部光池、白色编辑文字、浅色卡片,以及根状形态。场景规划属于演示内容;可复用的核心是搭配的两种表现形式和共享的控制器。
Respect motion and lifecycle
考虑动效偏好和生命周期
- Under , render a designed diagnostic still at about 62% radius with both cage and solid visible. Let the replay control recompose that still and announce the state; do not hide the scene.
prefers-reduced-motion: reduce - Clamp to 1/30 s. Reset the time base after a hidden tab or offscreen section resumes.
dt - Pause on and when an
document.hiddensays the section is offscreen.IntersectionObserver - Cap device pixel ratio at 2.
- Expose replay as a real button with visible focus and an status. Keep the canvas
aria-livewhen it is decorative.aria-hidden - On teardown, cancel the frame, disconnect observers, remove listeners, and dispose geometry, materials, textures, and the renderer.
- 当时,在半径约为62%的位置渲染一个预设的诊断静态画面,同时显示线框笼和实体。让重播控件重构该画面并播报状态;不要隐藏场景。
prefers-reduced-motion: reduce - 将限制为1/30秒。在隐藏标签页或离线部分恢复后重置时间基准。
dt - 当或
document.hidden检测到部分内容离线时暂停动画。IntersectionObserver - 将设备像素比上限设为2。
- 提供可见的重播按钮,带有焦点状态和状态更新。当画布仅作为装饰时,设置
aria-live。aria-hidden - 在销毁阶段,取消帧请求、断开观察者、移除监听器,并销毁几何体、材质、纹理和渲染器。
State the cost honestly
如实说明性能成本
The temporary second representation and its line coverage are the real cost. Topology density and overdraw matter more than the radius math. Build the wire cage only for the entrance, use the coarsest edge graph that preserves the silhouette, and dispose it as soon as the scan finishes. Do not claim a performance gain without profiling GPU frame time before and during the cage pass.
临时的第二种表现形式及其线段覆盖是主要性能开销。拓扑密度和过度绘制比半径计算更重要。仅在入场时构建线框笼,使用能保留轮廓的最粗糙边缘图,并在扫描完成后立即销毁。未经GPU帧时间分析,不要宣称性能提升。
Verify
验证要点
- Compare the opening, midpoint, and completed states with the source at 1440×900.
- Confirm the cage leads the solid everywhere, including scaled child groups.
- Fast-forward across completion and verify no clipped islands remain.
- Replay twice and confirm only one frame loop and one cage survive.
- Resize during the scan at 390×844 and 1440×900.
- Tab to replay and confirm visible focus and a live status update.
- Test or system reduced motion: composed still, no advancing radius.
?reduced=1 - Hide/show the tab and leave/re-enter the viewport; confirm no time jump.
- Confirm a clean console and disposed cage resources after completion.
Use demo/index.html as the working proof and demo/PROMPT.md to recreate or remix it.
- 在1440×900分辨率下,将开场、中点和完成状态与源文件对比。
- 确认线框笼在所有位置都领先于实体,包括缩放后的子组。
- 快进到完成状态,确认没有残留的裁剪孤岛。
- 重播两次,确认仅存在一个帧循环和一个线框笼。
- 在扫描过程中调整分辨率至390×844和1440×900。
- 切换到重播按钮,确认可见的焦点状态和实时状态更新。
- 测试或系统减少动效设置:显示合成静态画面,半径不再推进。
?reduced=1 - 隐藏/显示标签页,离开/重新进入视口;确认时间没有跳跃。
- 完成后确认控制台无报错,线框笼资源已被销毁。
请使用demo/index.html作为工作示例,使用demo/PROMPT.md进行重制或 remix。