В библиотеке Three.js большинство визуальных
объектов создаются с использованием встроенных материалов, таких как
MeshStandardMaterial, MeshPhongMaterial,
MeshBasicMaterial и других. Эти материалы обеспечивают
удобные настройки освещения, текстур и отражений, однако их
функциональность может быть ограничена при необходимости внедрения
нестандартных эффектов шейдинга. Для гибкой модификации шейдеров
используется метод onBeforeCompile.
onBeforeCompileКаждый материал в Three.js при компиляции превращается в шейдер на
языке GLSL, состоящий из вершинного и
фрагментного шейдера. Метод
onBeforeCompile позволяет вмешиваться в этот процесс
перед компиляцией, изменяя исходный код шейдера или
добавляя собственные uniform-переменные и функции.
Сигнатура метода:
material.onBeforeComp ile = function(shader) {
// shader.vertexShader
// shader.fragmentShader
// shader.uniforms
};
shader.vertexShader — строка с кодом вершинного
шейдера.shader.fragmentShader — строка с кодом фрагментного
шейдера.shader.uniforms — объект с uniform-переменными,
доступными в шейдерах.После изменения этих свойств шейдер компилируется с учётом внесённых
изменений, что позволяет создавать уникальные визуальные
эффекты, не прибегая к написанию полностью кастомного
ShaderMaterial.
Uniform-переменные — это способ передавать данные из JavaScript в GLSL. Пример добавления анимации с изменяющейся интенсивностью света:
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
material.onBeforeComp ile = (shader) => {
shader.uniforms.time = { value: 0.0 };
shader.fragmentShader = `
uniform float time;
` + shader.fragmentShader;
shader.fragmentShader = shader.fragmentShader.replace(
'#include <dithering_fragment>',
`
float pulse = abs(sin(time));
gl_FragColor.rgb *= pulse;
#include <dithering_fragment>
`
);
};
const mesh = new THREE.Mesh(geometry, material);
function animate(time) {
requestAnimationFrame(animate);
material.userData.shader && (material.userData.shader.uniforms.time.value = time * 0.001);
renderer.render(scene, camera);
}
Здесь происходит:
time.replace.Three.js использует блоки шейдера
(#include <...>), которые содержат стандартные
вычисления: освещение, карты нормалей, тени. Метод
onBeforeCompile позволяет:
Пример замены освещения на кастомный эффект:
material.onBeforeComp ile = (shader) => {
shader.fragmentShader = shader.fragmentShader.replace(
'#include <lights_fragment_begin>',
`
vec3 customLight = vec3(1.0, 0.5, 0.2);
vec3 outgoingLight = diffuseColor.rgb * customLight;
`
);
};
В этом случае стандартная подсветка объекта отключается, и цвет рассчитывается по собственной формуле.
Можно внедрять целые функции в шейдер:
material.onBeforeComp ile = (shader) => {
const myFunc = `
float noise(vec2 uv) {
return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
`;
shader.fragmentShader = myFunc + shader.fragmentShader;
shader.fragmentShader = shader.fragmentShader.replace(
'gl_FragColor = vec4(outgoingLight, diffuseColor.a);',
`
float n = noise(vUv * 10.0);
gl_FragColor = vec4(outgoingLight * n, diffuseColor.a);
`
);
};
Реализация позволяет добавлять перлин-шум, анимацию,
постэффекты без создания отдельного
ShaderMaterial.
После компиляции материала объект shader не сохраняется
автоматически в material. Для доступа к uniform-переменным
из анимации можно сохранять ссылку:
material.onBeforeComp ile = (shader) => {
material.userData.shader = shader;
};
Теперь uniform-переменные можно обновлять в функции анимации:
material.userData.shader.uniforms.time.value = performance.now() * 0.001;
onBeforeCompile повторная компиляция перезапишет изменения,
если не сохранять код.EffectComposer.Метод onBeforeCompile открывает доступ
к глубокой кастомизации встроенных материалов в Three.js, сохраняя при
этом простоту работы с высокоуровневыми объектами. Он позволяет сочетать
удобство стандартных шейдеров с гибкостью собственного GLSL-кода,
создавая уникальные визуальные эффекты без полной замены материала на
ShaderMaterial.