WebGL (Web Graphics Library) — это низкоуровневый API для отрисовки 2D- и 3D-графики в веб-браузере с использованием возможностей графического процессора (GPU). Он основан на спецификации OpenGL ES 2.0 и обеспечивает доступ к программируемому графическому конвейеру через JavaScript. Библиотека Three.js, в свою очередь, является высокоуровневой обёрткой над WebGL, скрывающей большую часть его сложности. Однако для понимания внутренней работы Three.js важно разобрать, как функционирует WebGL под капотом.
Для начала необходимо получить WebGL-контекст из
HTML-элемента <canvas>. Это объект, через который
происходит взаимодействие JavaScript-кода с графическим API. Вызов
getContext("webgl") или getContext("webgl2")
возвращает объект, содержащий методы для работы с буферами, шейдерами,
текстурами и другими графическими сущностями.
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
Этот объект gl и есть интерфейс взаимодействия с GPU, он
позволяет передавать данные и команды для последующей обработки
графическим конвейером.
В WebGL используется программируемый графический конвейер, что означает необходимость явного задания поведения вершинного и фрагментного этапов с помощью шейдеров. Шейдеры пишутся на языке GLSL (OpenGL Shading Language) и компилируются в исполняемый код для GPU.
Обрабатывает каждую вершину примитива. Задаёт её положение в пространстве и может передавать дополнительные данные на следующий этап.
Пример простейшего вершинного шейдера:
attribute vec3 position;
void main() {
gl_Position = vec4(position, 1.0);
}
Отвечает за определение цвета каждого пикселя. Получает данные, интерполированные из вершин.
Пример фрагментного шейдера:
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Красный цвет
}
Для передачи данных в шейдеры используется механизм
буферов. Наиболее распространённый — вершинный
буфер (ARRAY_BUFFER), содержащий координаты
вершин.
const vertices = new Float32Array([
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0,
0.0, 1.0, 0.0
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
Затем буфер связывается с атрибутом в шейдере через
gl.vertexAttribPointer() и
gl.enableVertexAttribArray().
const positionLocation = gl.getAttribLocation(program, 'position');
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
В отличие от атрибутов, uniform-переменные одинаковы для всех вершин или фрагментов в одном вызове отрисовки. Они задаются через JavaScript до начала рендера.
uniform mat4 modelViewProjectionMatrix;
Передача uniform в WebGL:
const matrixLocation = gl.getUniformLocation(program, 'modelViewProjectionMatrix');
gl.uniformMatrix4fv(matrixLocation, false, matrixData);
WebGL требует жесткого управления состоянием —
привязки нужных буферов, программ, текстур и параметров перед каждым
вызовом gl.drawArrays() или
gl.drawElements().
После подготовки всех ресурсов вызывается один из методов отрисовки. Например:
gl.drawArrays(gl.TRIANGLES, 0, 3);
Это запускает графический конвейер: для каждой вершины вызывается вершинный шейдер, затем происходит сборка примитивов, интерполяция и выполнение фрагментного шейдера.
В более сложных случаях используется индексированный рендеринг:
gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0);
В этом случае данные индексов хранятся в отдельном буфере типа
ELEMENT_ARRAY_BUFFER.
WebGL позволяет использовать текстуры для передачи изображений, данных освещения, теней и других эффектов. Текстура загружается, активируется и привязывается к определённому слоту:
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// параметры и загрузка изображения
gl.texImage2D(...);
В GLSL-шейдерах текстура доступна через sampler2D, а
чтение значений происходит с помощью texture2D() или
texture():
uniform sampler2D uTexture;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(uTexture, vUv);
}
Для адекватной отрисовки трёхмерной сцены важно управление буфером глубины (Z-buffer). Он включает определение, какой объект ближе к камере, чтобы корректно отображать перекрытия.
gl.enable(gl.DEPTH_TEST);
Также используется альфа-смешивание для прозрачности:
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
WebGL — низкоуровневый API, и от разработчика требуется ручное управление:
По этой причине библиотеки, такие как Three.js, создают абстракции, которые автоматически управляют жизненным циклом объектов и оптимизируют отправку данных.
Three.js генерирует WebGL-код на лету, компилирует шейдеры и управляет контекстом. При создании сцены, камеры, материалов и объектов Three.js транслирует эти сущности в WebGL-примитивы: создаёт буферы, компилирует шейдеры, загружает текстуры и запускает отрисовку.
Например, материал MeshStandardMaterial в Three.js под
капотом создаёт набор uniform-переменных, передаёт световые параметры,
генерирует GLSL-код, настраивает blending, depth и другие состояния,
затем вызывает gl.drawElements() через собственный
рендер-менеджер.
Понимание этих внутренних механизмов позволяет точнее управлять
производительностью, корректно использовать материалы и камеры, и, при
необходимости, внедрять собственные шейдеры через
ShaderMaterial или RawShaderMaterial.
WebGL предоставляет мощные, но низкоуровневые средства работы с графикой. Все высокоуровневые инструменты, включая Three.js, являются надстройками, которые управляют этими механизмами за разработчика. Глубокое понимание принципов WebGL необходимо при создании сложных визуальных эффектов, оптимизации производительности и использовании нестандартных рендер-процессов.