Framebuffer в WebGL представляет собой объект рендеринга, который позволяет направлять вывод графического конвейера не на экран, а в текстуру. В deck.gl этот механизм используется как фундамент для множества подсистем: от постобработки и оффскрин-рендеринга до выбора объектов (picking) и сложных визуальных эффектов.
В WebGL базовая работа с framebuffer опирается на создание объекта, к которому привязываются буферы цвета, глубины и трафарета. В более высокоуровневой библиотеке deck.gl эта работа инкапсулируется через luma.gl, обеспечивая удобные абстракции над WebGL2 API.
Внутри deck.gl framebuffer используется как промежуточный слой между слоями (layers) и итоговым canvas. Каждый кадр проходит несколько стадий:
Ключевая особенность заключается в том, что framebuffer позволяет разделить вычисление геометрии и финальное отображение, что критично для производительных визуализаций больших данных.
deck.gl не управляет framebuffer напрямую. Вместо этого используется
слой абстракции luma.gl, где framebuffer представлен
классом Framebuffer.
Основные компоненты:
Texture — хранит цветовой буферRenderbuffer — используется для depth/stencilFramebuffer — связывает текстуры и renderbufferТипичная инициализация выглядит следующим образом:
import {Framebuffer, Texture2D, Renderbuffer} from '@luma.gl/webgl';
const colorTexture = new Texture2D(gl, {
width: 1024,
height: 1024,
format: gl.RGBA
});
const depthBuffer = new Renderbuffer(gl, {
width: 1024,
height: 1024,
format: gl.DEPTH_COMPONENT16
});
const framebuffer = new Framebuffer(gl, {
color: colorTexture,
depth: depthBuffer
});
Framebuffer становится активным контекстом рендеринга через bind:
framebuffer.bind();
// draw calls
framebuffer.unbind();
Одним из ключевых сценариев использования framebuffer является offscreen rendering. В deck.gl он применяется для:
Внутренне deck.gl управляет lifecycle framebuffer через
LayerManager и Deck instance.
Упрощённый цикл выглядит так:
Каждый слой (Layer) в deck.gl не знает, куда именно будет отрисован результат. Он лишь генерирует команды рендеринга. Framebuffer выбирается на уровне контекста выполнения.
Слой может быть рендерен:
Это позволяет отделить логическую модель данных от физического вывода.
Picking — один из наиболее важных сценариев использования framebuffer в deck.gl. Он позволяет определять, какой объект находится под курсором.
Принцип работы:
Пример логики:
const pickingFramebuffer = new Framebuffer(gl, {
width: canvas.width,
height: canvas.height,
color: new Texture2D(gl, {
format: gl.RGBA,
type: gl.UNSIGNED_BYTE
}),
depth: new Renderbuffer(gl, {
format: gl.DEPTH_COMPONENT16
})
});
Рендер в picking pass:
pickingFramebuffer.bind();
layers.forEach(layer => {
layer.draw({picking: true});
});
pickingFramebuffer.unbind();
Чтение пикселя:
const pixel = new Uint8Array(4);
gl.readPixels(
x,
y,
1,
1,
gl.RGBA,
gl.UNSIGNED_BYTE,
pixel
);
Декодирование ID:
const objectId =
pixel[0] +
pixel[1] * 256 +
pixel[2] * 256 * 256;
Post-processing эффекты в deck.gl реализуются через цепочку framebuffer passes. Каждый эффект создаёт собственный framebuffer, в который рендерится сцена или её часть.
Типичная схема:
Пример эффекта размытия:
class BlurEffect {
constructor(gl, width, height) {
this.fbo = new Framebuffer(gl, {
width,
height,
color: new Texture2D(gl, {width, height})
});
}
apply(inputTexture) {
this.fbo.bind();
// draw full-screen quad with blur shader
drawQuad(inputTexture);
this.fbo.unbind();
return this.fbo.color;
}
}
При сложных эффектах используется техника ping-pong buffering — два framebuffer, которые поочерёдно принимают результат рендера.
Схема:
let readFbo = fboA;
let writeFbo = fboB;
for (let i = 0; i < passes; i++) {
writeFbo.bind();
shader.setUniform('uTexture', readFbo.color);
drawFullScreen();
writeFbo.unbind();
[readFbo, writeFbo] = [writeFbo, readFbo];
}
Эта техника позволяет реализовывать:
WebGL2 значительно расширяет возможности framebuffer:
В deck.gl это используется для ускорения вычислений и уменьшения числа проходов.
Пример MRT:
const framebuffer = new Framebuffer(gl, {
color: [
new Texture2D(gl, {format: gl.RGBA32F}),
new Texture2D(gl, {format: gl.RGBA32F})
]
});
Это позволяет записывать несколько значений за один draw call.
Внутренний render loop deck.gl можно представить как последовательность проходов:
Каждый проход использует собственный framebuffer, управляемый
Deck instance.
Ключевой момент заключается в том, что framebuffer пересоздаются только при изменении размеров viewport или конфигурации эффектов, что снижает накладные расходы.
Работа с framebuffer напрямую влияет на производительность:
readPixels) блокирует
pipelineОптимизации в deck.gl включают:
Часто встречающиеся проблемы:
Framebuffer в deck.gl выступает как фундамент для построения кастомных rendering pipeline. С его помощью можно:
Гибкость достигается за счёт того, что framebuffer является универсальным контейнером для промежуточных данных рендеринга, независимо от типа слоя или источника геометрии.