Всем привет! Меня зовут Григорий Дядиченко, и я разрабатываю разные проекты на заказ. Сегодня хотелось бы поговорить про дитеринг и бандинг — две стороны одной медали в мире компьютерной графики.

Сталкивались ли вы с ситуацией, когда вроде бы качественная графика в Unity портится противными полосами в тенях или на небе? Эти артефакты — и есть тот самый бандинг, который может испортить впечатление даже от самого продуманного проекта. Сейчас, когда индустрия стремится к фотореализму и бесшовному погружению в VR/AR, подобные мелочи становятся особенно критичными.
В этой статье мы не просто разберемся, откуда берутся эти полосы, а на практике посмотрим, как технология дитеринга в Universal Render Pipeline (URP) помогает их эффективно маскировать. Если вы хотите поднять визуальное качество своего проекта на новый уровень — добро пожаловать под кат!
Что такое бандинг?

Бандинг (от англ. banding — образование полос) — это артефакт в компьютерной графике, который проявляется в виде видимых ступенчатых полос или градиентов на участках, которые должны быть гладкими и непрерывными.
Представьте, что вам нужно нарисовать переход от черного цвета к белому, но у вас есть только 5 оттенков серого. Вы не сможете сделать это плавно — переход будет состоять из 5 четких полос. Это и есть бандинг.
Проблема возникает из-за ограниченной глубины цвета (битности). Когда для хранения цвета пикселя выделяется недостаточно бит информации (чаще всего 8 бит на канал, что дает 256 оттенков на цвет), графическому процессу не хватает промежуточных значений, чтобы точно отобразить плавный переход. В результате близкие, но разные оттенки "склеиваются" в один, и мы видим резкий скачок — полосу.
Часто в Unity это происходит в следующих случаях:
Градиентное небо (Skybox): Вместо плавного перехода цвета на небосводе видны четкие разноцветные полосы. Это происходит потому, что финальный рендеринг и тональная компрессия «сжимают» цветовую информацию, обнажая ограниченную глубину цвета.
Тени (Shadows): В области мягких полутеней (penumbra) вместо плавного затухания появляются концентрические круги или ступенчатые полосы. Причина — в ограниченной точности (битности) карт теней, которой недостаточно для плавных расчетов.
Видео и UI (ARGB32): При воспроизведении видео с плавными градиентами или в интерфейсах могут быть видны полосы. Формат в RenderTexture ARGB32 (8 бит на канал) физически не может отобразить достаточно оттенков для абсолютно гладкого перехода.
Артефакты компрессии текстур: Сильное сжатие текстур (например, в форматах ASTC или ETC2) может «сломать» плавные градиенты, заменив их на блочные участки однородного цвета, что визуально усиливает эффект бандинга.
Например Safari IOS в WebGL билдах не поддерживает рендер текстуры в форматах с плавающей точкой и высокой битности, поэтому там часто можно столкнуться с этой проблемой.
Что такое дитеринг?
Дитеринг (или размытие ступеней, от англ. dithering) — это техника, используемая в компьютерной графике для обмана человеческого зрения. Представьте, что у вас есть только черная и белая краски, но нужно нарисовать серый цвет. Если вы смешаете их в одну сплошную грязную массу — получится некрасиво. Но если вы нарисуете множество мелких черно-белых точек в шахматном порядке, то с расстояния наш глаз сам смешает их и увидит серый цвет.

Дитеринг делает то же самое: он добавляет в области плавных переходов случайный шум или упорядоченный узор из пикселей разных цветов. Этот шум "разбивает" четкие границы между полосами при бандинге, заставляя наш мозг воспринимать изображение как более плавное и цельное.
Как это работает технически в контексте бандинга?
Есть проблема: У вас есть градиент с бандингом — 3 крупные полосы (например, темно-серый, серый, светло-серый).
Применяется дитеринг: Графический процессор добавляет к каждому пикселю небольшую случайную погрешность (шум). Пиксели на границе полос начинают "перемешиваться".
Результат для мозга: Вместо четкой линии глаз видит шумную, зернистую границу. Мозг интерпретирует эту область не как резкий скачок, а как более плавный переход, потому что шум маскирует идеальную геометрию полос.
Какие алгоритмы дитеринга существуют?
Алгоритмы дитеринга делятся на две большие группы: упорядоченные (Ordered Dithering) и диффузионные (Error-Diffusion Dithering). В реальном времени (игры, Unity) почти всегда используется первая группа из-за своей производительности.
Упорядоченный дитеринг (Ordered Dithering)
Принцип: Используется заранее заданная матрица (паттерн) для определения порога, с которым сравнивается яркость пикселя. Это быстро, детерминировано и не зависит от соседних пикселей, что идеально для параллельных вычислений в шейдерах.
Основные алгоритмы:
a) Dither Patterns (Байеровский дитеринг / Bayer Matrix)

Самый популярный метод в реальном времени.
Как работает: Используется матрица NxN (чаще всего 2x2, 4x4, 8x8), которая содержит пороговые значения. Эта матрица тайлится (повторяется) по всему экрану. Текущий пиксель сравнивается с значением в матрице, и на основе этого решается, округлять его яркость вверх или вниз.
Паттерны: Чем больше матрица (например, 8x8), тем более качественный и менее заметный узор она создает.
Где используется: Практически во всех современных играх и движках. Именно этот метод встроен в шейдеры для борьбы с бандингом, для alpha-clipping (траву, листья) и для дешевой симуляции прозрачности.
Например: Нода Dither в Shader Graph в Unity по умолчанию использует упорядоченный Bayer Matrix (8x8).
b) Blue Noise Dithering (Синий шум)

Более современный и качественный метод.
Как работает: Используется текстура, содержащая "синий шум" — особый тип шума, где спектральная энергия сосредоточена на высоких частотах, а низкочастотные компоненты минимальны. Это делает паттерн дитеринга менее заметным и более приятным глазу, чем байеровская сетка.
Преимущество: Узор выглядит как однородное, мелкое "зерно", а не как повторяющаяся структура. Он лучше маскирует артефакты.
Недостаток: Требует заранее сгенерированной текстусы (предрасчет), а не простой матрицы.
Где используется: В продвинутых рендерерах и в тех случаях, когда качество визуала критически важно. Становится все популярнее в играх.
Диффузионный дитеринг (Error-Diffusion Dithering)
Принцип: При квантовании пикселя (округлении его цвета) возникает ошибка. Эта ошибка "распределяется" (диффундируется) на соседние, еще не обработанные пиксели. Это дает более высокое качество, но является последовательным алгоритмом.
Основные алгоритмы:
a) Floyd–Steinberg Dithering (Флойда-Стейнберга)
Самый известный алгоритм этой группы.
Как работает: Ошибка от текущего пикселя распределяется на 4 соседних пикселя с определенными весами (7/16, 3/16, 5/16, 1/16).
Преимущество: Дает очень качественный результат, похожий на газетную печать.
Недостаток: Алгоритм последовательный, его нельзя эффективно распараллелить для шейдеров, так как обработка каждого следующего пикселя зависит от предыдущего.
Где используется: В основном в статичной обработке изображений (Photoshop, при печати, конвертации цветов). В реальном времени не используется из-за неприемлемой производительности.
b) Другие алгоритмы Error-Diffusion
Jarvis-Judice-Ninke: Распределяет ошибку на большее количество пикселей, результат еще качественнее, но медленнее.
Sierra, Atkinson: Вариации с разными матрицами распределения ошибки.
Примеры и советы для URP
Я больше предпочитаю сейчас собирать шейдер графы для подобного из-за удобства редактирования, но в текст их оформлять неудобно, поэтому приведу пару примеров шейдеров кодом. Для использования матрицы Байера.
Шейдер с матрицей Байера
Shader "Custom/DitherAntiBanding"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
_DitherIntensity ("Dither Intensity", Range(0, 0.02)) = 0.005
_DitherScale ("Dither Scale", Float) = 1.0
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"RenderPipeline"="UniversalPipeline"
}
Pass
{
Name "DitherPass"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
float4 screenPos : TEXCOORD1;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial)
float _DitherIntensity;
float _DitherScale;
CBUFFER_END
// Та же матрица Байера, что и выше
static const float BayerMatrix8x8[64] = {
0.0/64.0, 32.0/64.0, 8.0/64.0, 40.0/64.0, 2.0/64.0, 34.0/64.0, 10.0/64.0, 42.0/64.0,
48.0/64.0, 16.0/64.0, 56.0/64.0, 24.0/64.0, 50.0/64.0, 18.0/64.0, 58.0/64.0, 26.0/64.0,
12.0/64.0, 44.0/64.0, 4.0/64.0, 36.0/64.0, 14.0/64.0, 46.0/64.0, 6.0/64.0, 38.0/64.0,
60.0/64.0, 28.0/64.0, 52.0/64.0, 20.0/64.0, 62.0/64.0, 30.0/64.0, 54.0/64.0, 22.0/64.0,
3.0/64.0, 35.0/64.0, 11.0/64.0, 43.0/64.0, 1.0/64.0, 33.0/64.0, 9.0/64.0, 41.0/64.0,
51.0/64.0, 19.0/64.0, 59.0/64.0, 27.0/64.0, 49.0/64.0, 17.0/64.0, 57.0/64.0, 25.0/64.0,
15.0/64.0, 47.0/64.0, 7.0/64.0, 39.0/64.0, 13.0/64.0, 45.0/64.0, 5.0/64.0, 37.0/64.0,
63.0/64.0, 31.0/64.0, 55.0/64.0, 23.0/64.0, 61.0/64.0, 29.0/64.0, 53.0/64.0, 21.0/64.0
};
float bayer_dither(float2 position, float scale)
{
int2 coord = int2(position.x * scale) % 8;
int index = coord.x + coord.y * 8;
return BayerMatrix8x8[index];
}
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.uv = IN.uv;
OUT.screenPos = ComputeScreenPos(OUT.positionHCS);
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
// Исходный цвет
float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
// Получаем экранные координаты
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
float2 pixelPos = screenUV * _ScreenParams.xy;
// Вычисляем дитеринг и преобразуем в -0.5...0.5
float dither = bayer_dither(pixelPos, _DitherScale);
float ditherValue = (dither - 0.5) * _DitherIntensity;
// Добавляем дитеринг и ограничиваем диапазон
color.rgb += ditherValue;
color.rgb = saturate(color.rgb);
return color;
}
ENDHLSL
}
}
}
Подходит для постпроцессинговой обработки кадра.
И не забывайте главное. Если пользуетесь шумами для чего либо, включая тот же VFX, выставляйте им FilterMode: Point. Тогда они будут накладываться корректно, а иначе на них будет влиять билинейная фильтрация текстур выставленная в Unity на текстурах по умолчанию.
Заключение
Я постарался рассказать про дитеринг и бандинг в Unity URP — что это за проблемы, почему они возникают и как их эффективно решать. Мы разобрали разные алгоритмы дитеринга, от классического Байера до модного синего шума, и написали готовые шейдеры для реального использования в проектах. Если вам понравилось и было интересно — ставьте плюсы.
Подписывайтесь на мой блог в телеграм, если вам интересна Unity разработка — там я делюсь не только графическими фишками, но и практическим опытом разработки разных проектов. В современном мире чаще всего основное знать терминологию. Я встречал многих разработчиков, которые сталкивались с бандингом, но не знали что он так называется. И с визуальными артефактами часто нужно просто знать как он называется, чтобы без проблем найти решение своей проблемы.
Jijiki
в тенях не только это еще может быть, если не ограничить тень то полоски могут появится