Uniform, attribute, varying: передача данных

В WebGL и, соответственно, в Three.js, для передачи данных между JavaScript и шейдерами используются три ключевых концепта: uniform, attribute и varying. Каждый из них имеет свою область применения и особенности взаимодействия с графическим конвейером. Понимание этих терминов критически важно для работы с кастомными материалами, шейдерами и эффектами.


Uniform

Uniform — это глобальная переменная, доступная и в вершинном, и в фрагментном шейдере. Она не изменяется для отдельных вершин или фрагментов в рамках одного рендера, а задаётся извне через JavaScript. Основное назначение uniform — передача данных, которые одинаковы для всей геометрии объекта в конкретный момент времени.

Примеры использования:

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

Синтаксис в GLSL:

uniform mat4 modelViewMatrix;
uniform vec3 lightDirection;
uniform sampler2D diffuseMap;

В Three.js передача данных осуществляется через объект uniforms в ShaderMaterial:

const material = new THREE.ShaderMaterial({
    uniforms: {
        uTime: { value: 0.0 },
        uColor: { value: new THREE.Color(0xff0000) }
    },
    vertexShader: vertexShaderCode,
    fragmentShader: fragmentShaderCode
});

Изменение uniform в процессе анимации:

material.uniforms.uTime.value += 0.01;

Attribute

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

GLSL синтаксис:

attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;

Three.js автоматически связывает стандартные атрибуты (position, normal, uv) с данными геометрии. Для кастомных атрибутов используется BufferAttribute:

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

const colors = new Float32Array([
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

В шейдере эти атрибуты можно использовать для расчёта позиции или передачи данных во фрагментный шейдер через varying.


Varying

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

Примеры применения:

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

Синтаксис в GLSL:

// В вершинном шейдере
varying vec3 vColor;

void main() {
    vColor = color; // attribute color
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

// Во фрагментном шейдере
varying vec3 vColor;

void main() {
    gl_FragColor = vec4(vColor, 1.0);
}

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


Связь uniform, attribute и varying

  1. Attribute → Varying → Fragment Shader Атрибуты определяют индивидуальные свойства вершин. Эти свойства можно интерполировать с помощью varying для корректного отображения на поверхности примитива.

  2. Uniform → Vertex/Fragment Shader Глобальные константы задаются через uniform и могут использоваться на любом этапе шейдера, например, для управления временем, цветом освещения, коэффициентами деформации.

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

// Вершинный шейдер
uniform float uTime;
attribute vec3 position;
attribute vec3 color;
varying vec3 vColor;

void main() {
    vec3 pos = position + sin(uTime) * 0.5;
    vColor = color;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

// Фрагментный шейдер
varying vec3 vColor;
void main() {
    gl_FragColor = vec4(vColor, 1.0);
}

Практические советы

  • Uniform следует использовать для данных, которые не меняются между вершинами и фрагментами в одном кадре.
  • Attribute необходим для передачи индивидуальных данных вершин (позиции, цвета, кастомные параметры).
  • Varying обеспечивает интерполяцию атрибутов во фрагментный шейдер, что делает возможным плавное отображение градиентов, освещения и текстурных эффектов.
  • Для оптимизации производительности минимизировать количество uniform и атрибутов, особенно больших массивов, чтобы снизить нагрузку на GPU.

Эффективное понимание и сочетание uniform, attribute и varying позволяет создавать сложные визуальные эффекты в Three.js, управлять геометрией и материалами на уровне шейдеров, а также реализовывать динамические и интерактивные сцены с точным контролем над визуальными параметрами.