Пиксели и фреймбуфер
Любая картинка на экране — это сетка цветных точек, которую программа заполняет числами.
Фреймбуфер — область памяти, где для каждого пикселя экрана хранится его цвет; видеосистема читает её и выводит на дисплей.
Зачем это знать
Когда вы пишете шейдер, вы в конечном счёте управляете тем, какое число попадёт в каждую ячейку фреймбуфера. Не понимая, что экран — это просто прямоугольный массив чисел, легко запутаться в том, «откуда берётся цвет». Всё, что делает видеокарта при рисовании 3D-сцены, заканчивается одним: записью цвета в пиксели буфера кадра.
Что такое пиксель
Пиксель (от picture element) — наименьшая адресуемая точка растрового изображения. Экран 1920×1080 содержит 1920 столбцов и 1080 строк, итого 2 073 600 пикселей. Цвет одного пикселя чаще всего хранят четырьмя байтами: красный, зелёный, синий и альфа (прозрачность) — формат RGBA8. Каждый канал — число от 0 до 255.
Сетка пикселей 4x3 (каждая клетка — один пиксель): x=0 x=1 x=2 x=3 +-----+-----+-----+-----+ | RGBA| RGBA| RGBA| RGBA| y=0 +-----+-----+-----+-----+ | RGBA| RGBA| RGBA| RGBA| y=1 +-----+-----+-----+-----+ | RGBA| RGBA| RGBA| RGBA| y=2 +-----+-----+-----+-----+
Цвет белого пикселя в RGBA8 — это (255, 255, 255, 255); чёрного непрозрачного — (0, 0, 0, 255). Чистый красный — (255, 0, 0, 255).
Сколько памяти занимает кадр
Память под фреймбуфер легко посчитать: ширина × высота × байт_на_пиксель. Проверим на Python для разрешения Full HD при 4 байтах на пиксель.
width, height = 1920, 1080
bytes_per_pixel = 4 # RGBA8
total_bytes = width * height * bytes_per_pixel
print("Пикселей:", width * height)
print("Байт:", total_bytes)
print("Мегабайт:", round(total_bytes / (1024 * 1024), 2))
Вывод:
Пикселей: 2073600 Байт: 8294400 Мегабайт: 7.91
Почти 8 МБ на один кадр. Видеокарта хранит и переписывает такие буферы десятки раз в секунду, и это ещё без буфера глубины и промежуточных целей рендеринга.
Адресация: от (x, y) к индексу
В памяти буфер — это одномерный массив байтов. Чтобы добраться до пикселя (x, y), его координаты переводят в линейный индекс. Это та же арифметика, что и при работе с двумерными массивами.
def pixel_offset(x, y, width, bpp=4):
return (y * width + x) * bpp
w = 1920
print("Пиксель (0,0) -> байт", pixel_offset(0, 0, w))
print("Пиксель (1,0) -> байт", pixel_offset(1, 0, w))
print("Пиксель (0,1) -> байт", pixel_offset(0, 1, w))
print("Пиксель (100,50)-> байт", pixel_offset(100, 50, w))
Вывод:
Пиксель (0,0) -> байт 0 Пиксель (1,0) -> байт 4 Пиксель (0,1) -> байт 7680 Пиксель (100,50)-> байт 384400
Соседний по горизонтали пиксель отстоит на 4 байта, а спуск на строку вниз прибавляет целую ширину строки (1920×4 = 7680 байт).
Как работает под капотом
Обычно используют двойную буферизацию: пока монитор показывает один буфер (front buffer), видеокарта рисует следующий кадр в другой (back buffer). Когда кадр готов, буферы меняют местами — «swap». Если менять их строго в момент, когда монитор закончил отрисовку строки (вертикальная синхронизация, VSync), картинка не «рвётся». Без синхронизации вы увидите tearing — горизонтальный разрыв, где верх кадра принадлежит старому изображению, а низ уже новому.
Частые ошибки
- Путать пиксель и «точку» цвета в файле PNG: в сжатых форматах цвет хранится иначе, фреймбуфер же всегда несжатый массив.
- Забывать про альфа-канал и считать пиксель тремя байтами — выравнивание поедет, индексы сместятся.
- Считать, что (0,0) всегда в левом верхнем углу: в графических API ось Y может идти вверх (OpenGL) или вниз (многие 2D-системы).
Итоги
- Изображение — прямоугольный массив пикселей; цвет пикселя обычно 4 байта RGBA.
- Фреймбуфер — память, куда пишется цвет каждого пикселя перед выводом на экран.
- Линейный индекс пикселя:
(y*width + x) * bpp. - Двойная буферизация и VSync избавляют от мерцания и разрывов кадра.