Встроенные uniform Three.js в шейдерах

В Three.js uniform — это глобальная переменная шейдера, значение которой задаётся на стороне JavaScript и может быть использовано в вершинных и фрагментных шейдерах GLSL. В отличие от атрибутов (attribute), uniform не изменяется для каждой вершины, а остаётся постоянным на всю отрисовку примитива. Это делает uniform идеальным инструментом для передачи данных, таких как матрицы трансформации, цвета, текстуры и параметры освещения.

Пример базового объявления uniform в GLSL:

uniform float time;
uniform vec3 lightPosition;
uniform sampler2D textureMap;

В JavaScript эти uniform устанавливаются через объект uniforms материала:

const material = new THREE.ShaderMaterial({
    uniforms: {
        time: { value: 0.0 },
        lightPosition: { value: new THREE.Vector3(10, 10, 10) },
        textureMap: { value: myTexture }
    },
    vertexShader: vertexShaderSource,
    fragmentShader: fragmentShaderSource
});

Встроенные uniform Three.js

Three.js предоставляет набор встроенных uniform, которые автоматически передаются в шейдер и позволяют ускорить разработку и унифицировать работу с объектами. Основные категории:

1. Матрицы трансформации

  • modelViewMatrix — матрица модели, умноженная на матрицу вида. Преобразует вершины из локальных координат объекта в координаты камеры.
  • projectionMatrix — матрица проекции, необходимая для перевода координат из камеры в экранные.
  • normalMatrix — обратная транспонированная матрица верхнего уровня из modelViewMatrix, используется для корректного преобразования нормалей при освещении.
  • modelMatrix — только матрица модели, без учёта камеры.
  • viewMatrix — матрица вида камеры.
  • modelViewMatrixInverse и projectionMatrixInverse — обратные матрицы для различных вычислений в шейдерах.

Использование в вершинном шейдере:

vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * mvPosition;

2. Камера и положение глаз

  • cameraPosition — позиция камеры в мировой системе координат. Полезно для вычислений вектора взгляда или эффектов отражения.
  • viewMatrix — как указано выше, трансформация мировой системы в систему камеры.

Пример для вычисления вектора от фрагмента к камере:

vec3 viewDir = normalize(cameraPosition - worldPosition);

3. Освещение

Three.js автоматически поддерживает uniform для всех стандартных источников света, если используется MeshStandardMaterial или MeshPhongMaterial с включёнными светильниками:

  • ambientLightColor — цвет глобального фонового освещения.
  • directionalLights — массив направленных источников света с их цветом и направлением.
  • pointLights — массив точечных источников с позицией, цветом и дальностью.
  • spotLights — параметры прожекторов: положение, направление, угол конуса и цвет.
  • hemisphereLights — верхний и нижний цвета для полусферного освещения.

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


4. Текстуры и карты

Three.js включает встроенные uniform для стандартных карт материала:

  • map — основная цветовая текстура (diffuseMap).
  • normalMap — карта нормалей.
  • roughnessMap и metalnessMap — карты шероховатости и металличности для PBR материалов.
  • envMap — карта окружения для отражений.

Пример использования в фрагментном шейдере:

uniform sampler2D map;
varying vec2 vUv;

void main() {
    vec4 texColor = texture2D(map, vUv);
    gl_FragColor = texColor;
}

5. Временные и динамические uniform

Для создания анимаций часто используются:

  • time — пользовательский uniform, обновляемый каждый кадр.
  • deltaTime — разница времени между кадрами (не встроенный, но часто создаётся вручную).

Пример динамической анимации:

material.uniforms.time.value = clock.getElapsedTime();

В шейдере можно использовать для смещения вершин:

position.y += sin(position.x + time) * 0.5;

Передача и обновление uniform

Uniform можно обновлять в реальном времени без пересоздания материала. Доступ к ним через объект uniforms:

material.uniforms.lightPosition.value.set(5, 10, 5);
material.uniforms.time.value += 0.01;

Особенность Three.js — каждый uniform должен быть объектом с ключом value, даже если передаётся число или вектор. Это связано с тем, что Three.js отслеживает ссылки на объекты и автоматически обновляет GPU.


Использование uniform в разных типах материалов

  • ShaderMaterial — полный контроль, требует ручного объявления всех uniform.
  • RawShaderMaterial — полный контроль без автоматической вставки встроенных uniform.
  • MeshStandardMaterial / MeshPhongMaterial / MeshBasicMaterial — встроенные uniform управляются Three.js, можно расширять через onBeforeCompile.

Пример расширения стандартного материала:

material.onBeforeComp ile = (shader) => {
    shader.uniforms.time = { value: 0 };
    shader.fragmentShader = 'uniform float time;\n' + shader.fragmentShader;
};

Это позволяет интегрировать собственные эффекты в готовые материалы без переписывания шейдера полностью.


Оптимизация работы с uniform

  1. Минимизировать количество uniform — каждый новый uniform увеличивает нагрузку на GPU.
  2. Использовать массивы uniform — для точечных источников света или костей скелета.
  3. Обновлять только изменившиеся значения — постоянное присвоение идентичного значения создаёт лишние операции.
  4. Кэшировать векторные объекты (THREE.Vector3, THREE.Color) вместо создания новых при каждом кадре.

Заключение по встроенным uniform

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