Передача времени и пользовательских данных в шейдер

В Three.js взаимодействие с шейдерами осуществляется через механизм uniform-переменных и атрибутов. Uniform-переменные позволяют передавать в шейдер данные, которые остаются постоянными на протяжении одного рендера, но могут меняться между кадрами, а атрибуты определяют значения для отдельных вершин или фрагментов.

Uniform-переменные для времени

Для создания анимаций в шейдерах часто используется время. В Three.js время обычно передается в виде float через uniform-переменную. Пример создания материала с пользовательским шейдером, учитывающим время:

const uniforms = {
    uTime: { value: 0.0 }
};

const material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: /* glsl */`
        uniform float uTime;
        varying vec2 vUv;
        void main() {
            vUv = uv;
            vec3 pos = position;
            pos.y += sin(uTime + position.x) * 0.5;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
        }
    `,
    fragmentShader: /* glsl */`
        varying vec2 vUv;
        void main() {
            gl_FragColor = vec4(vUv, 0.5 + 0.5*sin(uTime), 1.0);
        }
    `
});

Обновление значения времени осуществляется в цикле анимации:

function animate(time) {
    requestAnimationFrame(animate);
    uniforms.uTime.value = time * 0.001; // перевод миллисекунд в секунды
    renderer.render(scene, camera);
}
animate();

Ключевой момент — uTime.value изменяется каждый кадр, создавая динамическое поведение объектов.

Передача пользовательских данных

Помимо времени, в шейдер можно передавать любые данные: числа, векторы, текстуры. Примеры типов uniform-переменных:

  • float — одно число
  • vec2, vec3, vec4 — двумерные, трёхмерные и четырёхмерные векторы
  • mat3, mat4 — матрицы
  • sampler2D — текстура

Пример передачи пользовательских данных:

const uniforms = {
    uColor: { value: new THREE.Color(0xff0000) },
    uIntensity: { value: 1.0 }
};

const material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: /* glsl */`
        varying vec3 vColor;
        uniform float uIntensity;
        void main() {
            vColor = position * uIntensity;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: /* glsl */`
        varying vec3 vColor;
        uniform vec3 uColor;
        void main() {
            gl_FragColor = vec4(vColor * uColor, 1.0);
        }
    `
});

Изменение данных в рантайме:

uniforms.uIntensity.value = Math.sin(Date.now() * 0.001);
uniforms.uColor.value.setHSL(Math.random(), 0.5, 0.5);

Атрибуты и кастомные данные на вершинах

Для передачи информации на уровне вершин используются BufferGeometry и кастомные атрибуты:

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
    -1, -1, 0,
     1, -1, 0,
     0,  1, 0
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

const customValues = new Float32Array([
    0.1, 0.5, 0.9
]);
geometry.setAttribute('aValue', new THREE.BufferAttribute(customValues, 1));

const material = new THREE.ShaderMaterial({
    vertexShader: /* glsl */`
        attribute float aValue;
        varying float vValue;
        void main() {
            vValue = aValue;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: /* glsl */`
        varying float vValue;
        void main() {
            gl_FragColor = vec4(vValue, vValue, vValue, 1.0);
        }
    `
});

Каждой вершине можно присвоить собственные значения, которые затем используются в вершинном и фрагментном шейдерах.

Динамическая связь с GUI и внешними данными

Uniform-переменные позволяют напрямую интегрировать шейдер с пользовательскими интерфейсами или данными из приложения. Например, при использовании dat.GUI можно менять uniform-переменные в реальном времени:

const params = { speed: 1.0 };
const gui = new dat.GUI();
gui.add(params, 'speed', 0, 5).onChange(value => {
    uniforms.uSpeed.value = value;
});

Такое управление позволяет создавать интерактивные эффекты: деформации, цветовые анимации, реагирование на пользовательский ввод.

Важные практики

  • Все uniform-переменные объявляются в шейдере и передаются через объект uniforms.
  • Для динамических данных, таких как время или пользовательские параметры, обновление должно происходить каждый кадр.
  • Анимации на основе uniform-переменных легче оптимизировать, чем перезаписывать геометрию полностью.
  • Кастомные атрибуты эффективны для передачи большого количества уникальных данных на вершины без использования нескольких uniform-переменных.

Использование этих методов позволяет создавать сложные динамические визуальные эффекты в Three.js, делая шейдеры полностью управляемыми как через внутренние переменные сцены, так и через пользовательские данные.