4.1 KiB
Executable File
4.1 KiB
Executable File
onBeforeCompile 注入 GLSL 的陷阱
场景
使用 MeshPhysicalMaterial 的 transmission 功能但需要增强效果时(如 drei 的 MeshTransmissionMaterial),
通过 material.onBeforeCompile 注入自定义 GLSL 代码。
常见陷阱
1. 函数签名版本差异
Three.js 不同版本的内置函数签名不同:
// r166 及之前
vec4 getIBLVolumeRefraction(n, v, roughness, diffuseColor, specularColor, specularF90,
pos, modelMatrix, viewMatrix, projectionMatrix, ior, thickness,
attenuationColor, attenuationDistance)
// r167+ 新增 dispersion 参数
vec4 getIBLVolumeRefraction(n, v, roughness, diffuseColor, specularColor, specularF90,
pos, modelMatrix, viewMatrix, projectionMatrix, dispersion, ior, thickness,
attenuationColor, attenuationDistance)
必须检查目标版本的实际签名:
curl -s "https://cdn.jsdelivr.net/npm/three@0.167.0/src/renderers/shaders/ShaderChunk/transmission_pars_fragment.glsl.js" \
| tr '\n' ' ' | grep -oE 'vec4 getIBLVolumeRefraction\([^)]+\)'
2. GLSL 不允许嵌套函数定义
// 错误!GLSL 不支持函数内定义函数
void main() {
float myRand(vec2 co) { return fract(sin(...)); } // 编译失败
}
// 正确:函数必须在全局作用域
float myRand(vec2 co) { return fract(sin(...)); }
void main() {
float r = myRand(uv);
}
3. 条件编译宏保护
某些变量只在特定宏下可用:
vWorldPosition→ 需要USE_TRANSMISSION启用vTransmissionMapUv→ 需要USE_TRANSMISSIONMAP启用roughnessFactor→ 在lights_physical_fragment之后可用
// 在替换 #include <transmission_fragment> 时
// 原始代码自带 #ifdef USE_TRANSMISSION,替换代码也必须包含
#ifdef USE_TRANSMISSION
// ... 你的代码
#endif
4. 变量名冲突
注入的全局函数/变量可能与 Three.js 内部冲突:
- 避免使用
hash,random,noise等通用名 - 自定义函数加前缀:
snoise→ OK,random→ 可能冲突 - uniform 名称加前缀
u:uDistortion,uNoiseTime
推荐模式
安全注入:修改 normal 而不替换整个 chunk
material.onBeforeCompile = (shader) => {
shader.uniforms.uDistortion = { value: 0 };
shader.uniforms.uNoiseTime = { value: 0 };
// 在 fragment shader 最前面加 uniform 声明 + 工具函数
shader.fragmentShader = `
uniform float uDistortion;
uniform float uNoiseTime;
${noiseGLSL}
` + shader.fragmentShader;
// 在 transmission_fragment 之前插入法线扰动
shader.fragmentShader = shader.fragmentShader.replace(
'#include <transmission_fragment>',
`
#ifdef USE_TRANSMISSION
{
// 扰动 normal 影响折射方向
if (uDistortion > 0.0) {
normal = normalize(normal + uDistortion * vec3(
snoiseFractal(vWorldPosition * 0.08 + vec3(uNoiseTime)),
snoiseFractal(vWorldPosition.zxy * 0.08 - vec3(uNoiseTime)),
snoiseFractal(vWorldPosition.yxz * 0.08)
));
}
}
#endif
#include <transmission_fragment>
`
);
};
完整替换:需要随机多采样 + 色差时
当需要 MeshTransmissionMaterial 的颗粒感(随机采样噪声)和色差效果时,
必须完整替换 #include <transmission_fragment>。关键点:
- 保留
#ifdef USE_TRANSMISSION/#endif包裹 - 保留 transmissionMap 和 thicknessMap 的
#ifdef块 - 使用正确版本的
getIBLVolumeRefraction签名 - 自己处理色差时,传
dispersion = 0.0,用不同 IOR 采样 R/G/B - 低采样数(6)+ 每像素随机偏移 → 产生可见的胶片颗粒感
视觉效果来源速查
| 效果 | 来源 | 实现方式 |
|---|---|---|
| 玻璃折射 | MeshPhysicalMaterial transmission |
Three.js 内置 |
| 色差 (chromatic aberration) | 不同 IOR 采样 R/G/B | 替换 transmission_fragment |
| 胶片颗粒感 | 低采样数 + 每像素随机方向 | 替换 transmission_fragment |
| 有机扭曲 | simplex noise 扰动法线/折射方向 | onBeforeCompile 注入 |
| 颜色偏移 | dispersion 属性 (r167+) |
MeshPhysicalMaterial 内置 |