43 · Physical 专用变量:清漆、透射、厚度与虹彩

返回系列目录

完整案例:csm-physical-coat.js。左球保持干燥,右球用移动遮罩逐渐增加清漆并降低清漆粗糙度。它表现的是外层反射响应的变化,不是真实积水模拟。以下替换点均核对 CSM 6.4.0 与 Three.js r185。

1. 为什么 Physical 参数与 roughnessMap 的处理不同

CSM 会先把 lights_physical_fragment 和 transmission_fragment 从 include 展开成源码,再替换其中的赋值语句。大多数 Physical 变量替换的是“贴图采样前的基础值”,与第 42 章的 Roughness/Metalness 覆盖采样结果不同。

CSM 变量 类型 替换位置 后续仍生效的操作
csm_Clearcoat float material.clearcoat = clearcoat clearcoatMap 的 R 通道、saturate
csm_ClearcoatRoughness float material.clearcoatRoughness = clearcoatRoughness clearcoatRoughnessMap 的 G 通道、最小粗糙度与几何粗糙度
csm_Transmission float material.transmission = transmission transmissionMap 的 R 通道与透射混合
csm_Thickness float material.thickness = thickness thicknessMap 的 G 通道、折射传播和吸收计算
csm_Iridescence float material.iridescence = iridescence iridescenceMap 的 R 通道、薄膜厚度与 IOR
csm_ClearcoatNormal vec3 替换 clearcoat_normal_fragment_begin 自定义投影扰动算法;如开启 clearcoat normal map,后续贴图路径会覆盖这个结果

这张表决定你是否需要手动采样。例如写 csm_Clearcoat *= mask 不会丢失 clearcoatMap;再乘同一贴图会重复调制。

2. 清漆强度与清漆粗糙度

清漆是基础材质上方的额外反射层,有自己的粗糙度和法线。它不是把基础 roughness 再减小一点。

csm_Clearcoat = wetMask;
csm_ClearcoatRoughness = mix(0.35, 0.045, wetMask);
csm_DiffuseColor.rgb *= mix(1.0, 0.68, wetMask);

案例以 UV 高度生成湿润前沿,用高频函数形成水滴式权重。清漆强度高的区域获得更明显外层高光,粗糙度低使高光集中;基础色适度变暗是艺术化湿润近似。只改变颜色无法替代外层高光。

CSM 在源码发现 Clearcoat、ClearcoatRoughness 或 ClearcoatNormal 的名字时,若材质 clearcoat 是 falsy,会设为 1,启用对应特性。这是构造/更新阶段的材质属性变化,不是每片元在 GLSL 内动态创建渲染功能。显式设置 clearcoat: 1 更容易理解。若 shader 用变量赋值为零,只会使该片元无清漆贡献,编译中的清漆分支仍然存在。

3. 透射与厚度

csm_Transmission 控制反射表面下方的透射贡献,常用范围 0~1;csm_Thickness 控制近似介质厚度。两者仍使用 Physical 的 ior、attenuationColor、attenuationDistance、roughness 等属性。

const material = new CustomShaderMaterial({
  baseMaterial: THREE.MeshPhysicalMaterial,
  transmission: 1,
  thickness: 0.4,
  ior: 1.45,
  roughness: 0.12,
  attenuationColor: new THREE.Color(0x8abbd0),
  attenuationDistance: 2,
  vertexShader: 'varying vec2 vDemoUv; void main() { vDemoUv = uv; }',
  fragmentShader: `
    varying vec2 vDemoUv;
    void main() {
      float center = 1.0 - smoothstep(0.1, 0.5, abs(vDemoUv.x - 0.5));
      csm_Transmission = 0.9;
      csm_Thickness = mix(0.08, 0.5, center);
    }
  `
});

这个片段需在已有场景中 import THREE 和 CSM,加入 Mesh,并在后方放置能被看到的物体。透射不是简单 alpha 混合;通常保持 opacity 1,不因需要玻璃就随意开启 transparent。Three.js 的实时透射依赖特定渲染流程,不是完整多次折射光线追踪。

CSM 检测 csm_Transmission 时会将 falsy transmission 设为 1;仅使用 csm_Thickness 不会自动开启 transmission。只变厚度而 transmission 仍为零,通常看不到透射变化。启用透射可能增加场景渲染成本,不能把“只是多一个 float”当作其全部成本。

厚度影响着色中的传播距离,不会真的改变几何轮廓。对象缩放也会参与路径计算,应保持模型尺度和吸收距离单位一致;非均匀缩放、薄片和封闭模型应分别验证。

4. 虹彩及没有专用变量的属性

csm_Iridescence 调节薄膜干涉强度;膜的厚度范围、iridescenceIOR 仍使用 Physical 属性。可根据油膜遮罩设置强度,而不必重新实现干涉公式。

csm_Iridescence = clamp(oilMask, 0.0, 1.0);

CSM 自动尝试启用 iridescence。虽然其支持表列出 Standard 和 Physical,但 r185 的实际 iridescence 特性由 MeshPhysicalMaterial 路径启用;教程应使用 Physical,不要把“CSM 接受变量名”理解成 Standard 已拥有对应完整功能。

CSM 6.4.0 没有提供 csm_IOR、csm_Sheen、csm_Anisotropy 等变量。统一参数优先使用基础材质属性,空间变化优先考虑内置贴图。仍需特别阶段的数据再用经过验证的 patchMap/手动补丁;不要凭变量命名规律创造不存在的输出。

5. csm_ClearcoatNormal 的特殊算法与限制

它并不是 clearcoatNormal = csm_ClearcoatNormal。6.4.0 实际执行:

vec3 orthogonal = csm_ClearcoatNormal
  - dot(csm_ClearcoatNormal, nonPerturbedNormal) * nonPerturbedNormal;
vec3 projectedBump = mat3(csm_internal_vModelViewMatrix) * orthogonal;
vec3 clearcoatNormal = normalize(nonPerturbedNormal - projectedBump);

nonPerturbedNormal 来自内置片元法线初始化,是观察空间法线。第一行却先把用户向量相对这个观察空间法线投影,第二行再乘 modelViewMatrix 的线性部分。这不是标准的“物体空间法线乘法线矩阵”,也不是标准的“观察空间法线直接赋值”。不能为这个接口简单标注某个通常坐标空间并承诺任意变换都正确。

其行为更接近一个经过特定投影的扰动输入:当输入为零,扰动为零,清漆保持原法线;当输入平行于 nonPerturbedNormal,第一步也会去掉它。重要的是它在默认初始化段没有像其他参数那样获得有效默认值,所以使用时必须明确赋值,不能对未初始化值 +=。

后面 clearcoat_normal_fragment_maps 仍运行。r185 在开启 clearcoatNormalMap 时执行 clearcoatNormal = normalize(tbn2 * clearcoatMapN);tbn2 已在前面的普通法线初始化阶段构造,并不基于这里的自定义 clearcoatNormal,因此贴图路径会覆盖此输出,不是自动在它上面叠加扰动。该算法用了 modelView 而非法线矩阵,尤其要检查旋转、非均匀缩放、实例化和蒙皮;片元 varying 中的矩阵还会占用 varying 资源。

常规开发更可靠的选择是:用 clearcoatNormalMap 与 clearcoatNormalScale 表达外层凹凸;或者用严格指定观察空间、明确替换 clearcoat_normal_fragment_begin 的自定义补丁,并自行决定是否保留后面的法线贴图。不要把解码的切线空间 normal map 直接塞进此变量。案例有意使用标准清漆法线,不把这个特殊行为包装成通用方案。

6. 练习(不附答案)

  1. 为案例加入 clearcoatMap,验证 CSM 清漆权重与 R 通道是否相乘,并与 csm_Roughness 的行为对比。
  2. 只使用 csm_Thickness、保持 transmission 为零,预测结果,再显式开启透射并放置背景几何验证。
  3. 给清漆添加 normal map,在旋转和非均匀缩放下检查高光;解释它与 csm_ClearcoatNormal 算法的区别。
  4. 设计油膜材质参数:哪些交给 Physical 属性,哪些用贴图,哪些用 CSM,并列出所需颜色空间与单位。