30 · 溶解、裁切与发光边缘
溶解由两个不同操作组成:决定哪些片元不存在,以及让尚未消失的边缘发光。前者影响深度和阴影,后者只影响可见材质的颜色输出。混在一个只供主材质使用的补丁里,会留下完整的阴影轮廓。
完整运行代码:dissolve.js。示例包含主材质、depth、distance 三份安装,共享阈值,地面用于观察投影。使用连续正弦场而非昂贵噪声,先看清机制;该场具有周期结构,不是随机噪声。
1. 在模型局部空间定义场
把 begin_vertex 后的 transformed 传给 vDemoLocal,片元计算:
它落在 [0,1],不需要 UV,因此不会出现 UV 接缝,但局部尺度会影响花纹大小。把 Mesh 缩放大,花纹也跟随物体放大。若要世界尺度固定,应一致地换成世界位置。
同一插值坐标和函数需要出现在所有阴影 shader,不能主材质用局部坐标、阴影材质却用光源观察坐标。
2. 覆盖范围使用 discard
在 alphatest_fragment 后追加:
float demoField = 0.5 + 0.5 * sin(vDemoLocal.x * 5.0) *
sin(vDemoLocal.y * 6.0) * sin(vDemoLocal.z * 4.0);
if (demoField < uDemoThreshold) discard;
保留内置 alphaTest,使已有贴图裁切仍有效。即使 alphaTest 宏未启用,这个顶层 include 锚点在当前模板中仍存在,可以在其后注入自定义判断。
threshold 小于 0 时全部保留,大于 1 时全部消失。使用边界外值可避免端点等于阈值时残留极少片元。示例把阈值在 -0.1 到 1.1 之间往返变化。
discard 表示该片元不写颜色和深度,不需要启用 transparent。若只是把 alpha 设为 0 却未正确配置混合和深度,可能出现看不见却遮住其他物体的问题。
3. 边缘只在存活侧发光
经过 discard 后,剩下片元满足 demoField >= uDemoThreshold。将这个差值映射为窄带:
// emissivemap_fragment 后
float demoEdge = 1.0 - smoothstep(0.0, 0.06, demoField - uDemoThreshold);
totalEmissiveRadiance += vec3(1.0, 0.14, 0.01) * demoEdge * 3.0;
0.06 是场值宽度,不是世界空间固定厚度;场梯度变化会让可见边缘厚度不同。如果需要屏幕稳定宽度,可研究 fwidth 参与的阈值过渡,但应在 discard 之前计算必要导数,避免在不一致控制流中求导。
emissive 增强不会自动照亮地面,也没有自动 bloom;这些属于光照或后处理需求。
4. 安装到三份材质
const material = new THREE.MeshStandardMaterial({ color: '#4e8496' });
const depth = new THREE.MeshDepthMaterial();
const distance = new THREE.MeshDistanceMaterial();
installDissolve(material, true);
installDissolve(depth, false);
installDissolve(distance, false);
mesh.customDepthMaterial = depth;
mesh.customDistanceMaterial = distance;
完整 installDissolve 在示例源码中。glow 只控制是否添加发光,必须进入 customProgramCacheKey,因为两种安装产生不同源码。时间变化的阈值则不能进入 cache key,否则产生无穷变体。
在 depth/distance 中保留原输出,让 Three.js 自己记录正确深度或距离。不要将主材质最终颜色当成阴影数据。
5. 透明、边缘和内部几何
discard 只裁掉表面,不会生成切口盖面。一个封闭实心物体可能露出背面空洞;DoubleSide 能显示背面,却不等于填充内部截面。CAD 剖切、实体熔解等需求需要额外几何或专门封口算法。
硬阈值边缘可能闪烁。可考虑适当 MSAA、alphaToCoverage 或其他覆盖策略,但要与渲染目标及阴影表现一起验证。开启 transparent 不会自动解决裁切边缘,还会引入排序问题。
验证与练习
固定 threshold 为 -0.1、0.5、1.1,检查全显、部分显、全隐;观察阴影孔洞是否同步;换成点光验证距离材质;删除发光补丁后剩余区域仍应有正常 Standard 光照。
- 把正弦场换为高度与噪声混合,保持主材质和两个阴影材质一致。
- 实现单次溶解后停止更新,而不是永久往返动画。
- 让发光带只出现在消失过程,恢复过程使用另一种颜色。
- 解释为什么 DoubleSide 无法生成真实切口盖面,并给出需要额外几何的案例。