Понимание дизеринга изображений и того, как они помогают смешивать CSM

Поэтому я хочу реализовать дизеринг как режим наложения между разделениями моей каскадной карты теней.

Я понятия не имел, что это такое, поэтому посмотрел это видео, чтобы попытаться понять это.
Насколько я понимаю, это способ сопоставить цвета изображения с ограниченной палитрой, пытаясь сохранить убедительный градиент между разноцветными пикселями.

Теперь из этого видео я понимаю, как рассчитать, какой цвет увидит мой глаз, на основе веса шаблона дизеринга. Я не понимаю, как мы берем изображение с 4-байтовыми пиксельными данными и, например, пытаемся сопоставить его с 1-байтовыми пиксельными данными. Как мы можем сопоставить каждый цвет пикселя в исходном изображении с шаблоном дизеринга, чтобы его средневзвешенное значение выглядело так, как будто это исходный цвет, если мы в основном ограничены? Скажем, мы были ограничены только 5 цветами, я предполагаю, что не каждая возможная средневзвешенная комбинация шаблона дизеринга с использованием этих 5 цветов палитры может привести к исходному цвету пикселя, так как же это может быть достигнуто? Также рассчитывается ли шаблон дизеринга для каждого пикселя, чтобы получить размытое изображение?

Помимо этого общего вопроса о сглаживании изображения, мне все еще трудно понять, как этот метод помогает нам смешивать каскадные разбиения, где, что касается его фактической реализации в коде, я видел пример, где он использует пространственные координаты фрагмента и вычислить дизеринг (не уверен, что он вычисляет на самом деле, потому что он не возвращает матрицу, а возвращает число с плавающей запятой):

float GetDither2(ivec2 p)
{
    float d = 0.0;

    if((p.x & 1) != (p.y & 1))
        d += 2.0;
    if((p.y & 1) == 1)
        d += 1.0;

    d *= 0.25;

    return d;
}

float GetDither4(ivec2 p)
{
    float d = GetDither2(p);
    d = d * 0.25 + GetDither2(p >> 1);
    return d;
}

float threshold = GetDither4(ivec2(gl_FragCoord.xy));

if(factor <= threshold)
{
    // sample current cascade
}
else
{
    // sample next cascade
}

И затем он производит выборку любой каскадной карты на основе этого возвращенного числа с плавающей запятой. Так что мой мозг не может перевести то, что я узнал о том, что у вас может быть шаблон дизеринга для имитации большого цветового рисунка, в этот пример, который использует возвращаемый плавающий элемент в качестве порогового коэффициента и сравнивает его с некоторым коэффициентом смешения, просто для выборки из любой карты теней. Так что это меня еще больше сбило с толку.

Был бы признателен за хорошее объяснение этого ????

РЕДАКТИРОВАТЬ:

Хорошо, я вижу корреляцию между алгоритмом, который мне предоставили, с статьей в Википедии об упорядоченном дизеринге, которая насколько я понимаю, это предпочтительный алгоритм дизеринга, потому что согласно статье:

Кроме того, поскольку расположение шаблонов дизеринга всегда остается неизменным относительно кадра дисплея, он менее подвержен дрожанию, чем методы распространения ошибок, что делает его пригодным для анимации.

Теперь я вижу, что код пытается получить это пороговое значение для данной пространственной координаты, хотя мне кажется, что он ошибся, потому что следующий расчет порога выглядит следующим образом: Mpre(i,j) = (Mint(i,j)+1) / n^2

И он должен установить: float d = 1.0 вместо float d = 0.0, если я не ошибаюсь. Во-вторых, я не уверен, как сдвинуть влево пространственную координату ivec2 (я даже не уверен, каково поведение побитового сдвига вектора в glsl…), но я предполагаю, что это просто компонентная побитовая операция, и я попробовал плагин (head вычисление) для заданной пространственной координаты (2,1) (в соответствии с моими предположениями о поразрядной операции) и получил другой пороговый результат для того, что должно быть пороговым значением этой позиции в матрице Байера 4x4.

Поэтому я скептически отношусь к тому, насколько хорошо этот код реализует алгоритм упорядоченного дизеринга.

Во-вторых, я все еще не уверен, как это пороговое значение имеет какое-либо отношение к выбору между картой теней 1 или 2, а не только к уменьшению цветовой палитры данного пикселя, эта логика еще не обосновалась в моей голове, поскольку я не понимаю использование порогового значения дизеринга для заданной пространственной координаты для выбора правильной карты для выборки.

Напоследок не вызовет ли выбор пространственной координаты дрожь? Данный фрагмент в мировом положении (x,y,z) затенен. Учитывая, что координаты пространства фрагмента для данного кадра равны (i,j). Если камера перемещается, не изменится ли эта пространственная координата фрагмента, в результате чего порог сглаживания, вычисленный для этого фрагмента, будет изменяться с каждым движением, вызывающим дрожание шаблона сглаживания?

РЕДАКТИРОВАТЬ2: Пытались совместить карты, как показано ниже, хотя результат не очень хорош, есть идеи?

const int indexMatrix8x8[64] = int[](
    0, 32, 8, 40, 2, 34, 10, 42,
    48, 16, 56, 24, 50, 18, 58, 26,
    12, 44, 4, 36, 14, 46, 6, 38,
    60, 28, 52, 20, 62, 30, 54, 22,
    3, 35, 11, 43, 1, 33, 9, 41,
    51, 19, 59, 27, 49, 17, 57, 25,
    15, 47, 7, 39, 13, 45, 5, 37,
    63, 31, 55, 23, 61, 29, 53, 21
);

for (int i = 0; i < NR_LIGHT_SPACE; i++) {
        if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i]) {
            shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i) * u_ShadowStrength;
                int x = int(mod(gl_FragCoord.x, 8));
                int y = int(mod(gl_FragCoord.y, 8));
                float threshold = (indexMatrix8x8[(x + y * 8)] + 1) / 64.0;
                if (u_CascadeBlend >= threshold)
                {
                    shadow = isInShadow(fs_in.v_FragPosLightSpace[i + 1], normal, lightDirection, i + 1) * u_ShadowStrength;
                }
            }
            break;
        }
    }

В основном, если я понимаю, что я делаю, это получаю пороговое значение из матрицы для каждой пространственной координаты затененного пикселя, и если оно (с учетом вероятности) выше, чем коэффициент смешения, то вместо этого я выбираю вторую карту.

Вот результаты:  введите здесь описание изображения Большое красное поле - это место разделения между картами.
Меньшее красное поле указывает на то, что есть некоторый узор дизеринга, но изображение не так смешано, как я думаю.


person Jorayen    schedule 15.04.2020    source источник


Ответы (2)


Прежде всего, я ничего не знаю о CSM, поэтому я сосредотачиваюсь на дизеринге и смешивании. Сначала посмотрите на это:

Они в основном отвечают на ваш вопрос о том, как вычислить шаблон / пиксели дизеринга.

Также важно иметь хорошую палитру для дизеринга, которая уменьшит ваши 24/32 бит на пиксель до 8 бит на пиксель (или меньше). Есть 2 основных подхода

  1. уменьшить количество цветов (квантование цвета)

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

  2. палитра дизеринга

    дизеринг использует усреднение пикселей для создания желаемого цвета, поэтому нам нужны такие цвета, которые могут генерировать все возможные цвета, которые мы хотим. Так что хорошо иметь несколько (2..4) оттенков каждого основного цвета (R, G, B, C, M, Y) и несколько (> = 4) оттенков серого. Из них вы можете комбинировать любой цвет и интенсивность (если у вас достаточно пикселей).

№1 - лучший вариант, но он связан с каждым изображением, поэтому вам нужно вычислить палитру для каждого изображения. Это может быть проблемой, так как эти вычисления очень сильно потребляют процессор. Кроме того, в старых 256-цветных режимах вы не могли одновременно отображать 2 разные палитры (что с истинным цветом больше не проблема), поэтому дизеринг обычно является лучшим выбором.

Вы даже можете комбинировать их для получения впечатляющих результатов.

Чем лучше использованная палитра, тем менее зернистым будет результат ...

Стандартные цветовые палитры VGA 16 и 256 были специально разработаны для дизеринга, поэтому их рекомендуется использовать ...

Стандартная цветовая палитра VGA 16: VGA 16 цветов

Стандартная цветовая палитра VGA 256: VGA 256 цветов

Вот также код C ++ для 256 цветов:

//---------------------------------------------------------------------------
//--- EGA VGA pallete -------------------------------------------------------
//---------------------------------------------------------------------------
#ifndef _vgapal_h
#define _vgapal_h
//---------------------------------------------------------------------------
unsigned int vgapal[256]=
    {
    0x00000000,0x00220000,0x00002200,0x00222200,
    0x00000022,0x00220022,0x00001522,0x00222222,
    0x00151515,0x00371515,0x00153715,0x00373715,
    0x00151537,0x00371537,0x00153737,0x00373737,
    0x00000000,0x00050505,0x00000000,0x00030303,
    0x00060606,0x00111111,0x00141414,0x00101010,
    0x00141414,0x00202020,0x00242424,0x00202020,
    0x00252525,0x00323232,0x00303030,0x00373737,
    0x00370000,0x00370010,0x00370017,0x00370027,
    0x00370037,0x00270037,0x00170037,0x00100037,
    0x00000037,0x00001037,0x00001737,0x00002737,
    0x00003737,0x00003727,0x00003717,0x00003710,
    0x00003700,0x00103700,0x00173700,0x00273700,
    0x00373700,0x00372700,0x00371700,0x00371000,
    0x00371717,0x00371727,0x00371727,0x00371737,
    0x00371737,0x00371737,0x00271737,0x00271737,
    0x00171737,0x00172737,0x00172737,0x00173737,
    0x00173737,0x00173737,0x00173727,0x00173727,
    0x00173717,0x00273717,0x00273717,0x00373717,
    0x00373717,0x00373717,0x00372717,0x00372717,
    0x00372525,0x00372531,0x00372536,0x00372532,
    0x00372537,0x00322537,0x00362537,0x00312537,
    0x00252537,0x00253137,0x00253637,0x00253237,
    0x00253737,0x00253732,0x00253736,0x00253731,
    0x00253725,0x00313725,0x00363725,0x00323725,
    0x00373725,0x00373225,0x00373625,0x00373125,
    0x00140000,0x00140007,0x00140006,0x00140015,
    0x00140014,0x00150014,0x00060014,0x00070014,
    0x00000014,0x00000714,0x00000614,0x00001514,
    0x00001414,0x00001415,0x00001406,0x00001407,
    0x00001400,0x00071400,0x00061400,0x00151400,
    0x00141400,0x00141500,0x00140600,0x00140700,
    0x00140606,0x00140611,0x00140615,0x00140610,
    0x00140614,0x00100614,0x00150614,0x00110614,
    0x00060614,0x00061114,0x00061514,0x00061014,
    0x00061414,0x00061410,0x00061415,0x00061411,
    0x00061406,0x00111406,0x00151406,0x00101406,
    0x00141406,0x00141006,0x00141506,0x00141106,
    0x00141414,0x00141416,0x00141410,0x00141412,
    0x00141414,0x00121414,0x00101414,0x00161414,
    0x00141414,0x00141614,0x00141014,0x00141214,
    0x00141414,0x00141412,0x00141410,0x00141416,
    0x00141414,0x00161414,0x00101414,0x00121414,
    0x00141414,0x00141214,0x00141014,0x00141614,
    0x00100000,0x00100004,0x00100000,0x00100004,
    0x00100010,0x00040010,0x00000010,0x00040010,
    0x00000010,0x00000410,0x00000010,0x00000410,
    0x00001010,0x00001004,0x00001000,0x00001004,
    0x00001000,0x00041000,0x00001000,0x00041000,
    0x00101000,0x00100400,0x00100000,0x00100400,
    0x00100000,0x00100002,0x00100004,0x00100006,
    0x00100010,0x00060010,0x00040010,0x00020010,
    0x00000010,0x00000210,0x00000410,0x00000610,
    0x00001010,0x00001006,0x00001004,0x00001002,
    0x00001000,0x00021000,0x00041000,0x00061000,
    0x00101000,0x00100600,0x00100400,0x00100200,
    0x00100303,0x00100304,0x00100305,0x00100307,
    0x00100310,0x00070310,0x00050310,0x00040310,
    0x00030310,0x00030410,0x00030510,0x00030710,
    0x00031010,0x00031007,0x00031005,0x00031004,
    0x00031003,0x00041003,0x00051003,0x00071003,
    0x00101003,0x00100703,0x00100503,0x00100403,
    0x00000000,0x00000000,0x00000000,0x00000000,
    0x00000000,0x00000000,0x00000000,0x00000000,
    };
//---------------------------------------------------------------------------
class _vgapal_init_class
        {
public: _vgapal_init_class();
        } vgapal_init_class;
//---------------------------------------------------------------------------
_vgapal_init_class::_vgapal_init_class()
        {
        int i;
        BYTE a;
        union { unsigned int dd; BYTE db[4]; } c;
        for (i=0;i<256;i++)
            {
            c.dd=vgapal[i];
            c.dd=c.dd<<2;
            a      =c.db[0];
            c.db[0]=c.db[2];
            c.db[2]=      a;
            vgapal[i]=c.dd;
            }
        }
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
//--- end. ------------------------------------------------------------------
//---------------------------------------------------------------------------

Теперь вернемся к вашему вопросу о смешивании с помощью дизеринга.

Блендинг - это объединение двух изображений одного разрешения на определенное количество (вес). Итак, каждый цвет пикселя вычисляется следующим образом:

color = w0*color0 + w1*color1;

где color? - пиксели в исходных изображениях, а w? - веса, где все веса вместе составляют 1:

w0 + w1 = 1;

вот пример:

и предварительный просмотр (точки в моем кодировщике GIF размываются):

Смешивание

Но смешивание с помощью дизеринга выполняется иначе. Вместо смешивания цветов мы используем определенный процент пикселей из одного изображения, а другие - из второго. Так:

if (Random()<w0) color = color0;
 else            color = color1;

Где Random() возвращает псевдослучайное число в диапазоне <0,1>. Как вы можете видеть, комбинирование цветов не выполняется, просто вы просто выбираете, из какого изображения вы копируете пиксель ... Вот предварительный просмотр:

Смешивание дизерингом

Теперь точки вызваны смешиванием с помощью дизеринга, поскольку интенсивности изображений очень далеко друг от друга, поэтому это выглядит не очень хорошо, но если вы сглаживаете относительно похожие изображения (например, слои вашей карты теней), результат должен быть достаточно хорошим ( практически без потери производительности).

Чтобы ускорить это, обычно предварительно вычисляют Random() выходы для некоторого блока (8x8, 16x16, ...) и используют это для всего изображения (это немного блочно, но это вроде как забавный эффект ...). Таким образом, это можно сделать и без ответвлений (если вы храните указатели на исходные изображения вместо случайного значения). Также это может быть сделано полностью для целых чисел (без фиксированной точности), если веса являются целыми числами, например <0..255> ...

Теперь, чтобы сделать каскад / переход от image0 к image1 или что-то еще, просто сделайте что-нибудь вроде этого:

for (w0=1.0;w0>=0.0;w0-=0.05)
 {
 w1=1.0-w0;
 render blended images;
 Sleep(100);
 }
render image1;
person Spektre    schedule 16.04.2020
comment
its a bit blocky but that is sort of used as a fun effect ... Разве этот узор не более чем забавный эффект? Насколько я понимаю, если бы мы преобразовали оттенки серого в черный или белый, тогда, если бы мы заказали матрицу Байера любым другим способом, тогда последовательные числа с наибольшей разницей между ними наш глаз не будет воспринимать исходный цвет издалека без этого уникального рисунка это сумма глаз и среднее значение, чтобы получить исходный цвет, не так ли? Также пробовал смешивать, используя матрицу Байера 8x8, но результат не выглядит многообещающим. Я отредактирую op. - person Jorayen; 16.04.2020
comment
Форма шаблона @Jorayen более или менее не имеет отношения к восприятию цвета человеческими глазами (случайные точки используются в основном, чтобы избежать выравнивания и / или интерференционных узоров, которые будут восприниматься сразу же, когда вы узнаете некоторые зернистые точки, которые вы заметите, только если вы их поищете). Важно то, что если у вас есть коробка 8x8, у вас есть 64 пикселя, что ограничивает ваши альфа-каналы до 64 возможных оттенков / уровней. Если вам нужна более точная альфа, вам понадобится коробка большего размера. Интересный эффект, о котором я писал, больше касался перехода между изображениями в слайд-шоу, веб-сайтах, играх и т. Д. - person Spektre; 16.04.2020
comment
@Jorayen после масштабирования изображения очищает матрицу и / или дизеринг не случайным образом, и возникают интерференционные паттерны, которые мы воспринимаем как свойство текстуры ... не уверен, есть ли у GLSL какие-либо используемые PRNG люди, которые могут подделать его из координат фрагмента и / или текстуры, но Я бы, вероятно, использовал предварительно вычисленную текстуру белого шума на стороне процессора ... если в распоряжении нет ГПСЧ - person Spektre; 16.04.2020
comment
@Jorayen Но почему вы не смешиваете карты вместе стандартным смешиванием? это более дружественно к GLSL, чем метод дизеринга, поскольку у вас нет статической переменной фрагмента, возможной в GLSL (по крайней мере, насколько мне известно) ... не было бы никаких явлений ... - person Spektre; 16.04.2020
comment
@Jorayen из беглого взгляда на ваш код `int (mod (gl_FragCoord.x, 8));` ваш PRNG и он не работает должным образом ... каков диапазон вашего gl_FragCoord? если <-1,+1> или <0,1> он не будет работать должным образом ... в качестве быстрого решения попробуйте int(mod(1234.5678*gl_FragCoord.x, 8)); - person Spektre; 16.04.2020
comment
нет gl_FragCoord находится в диапазоне <0.5,800.5 по оси x и <0.5, 600.5 по оси y, если я определил размер порта просмотра как 800x600 пикселей. Также у меня есть метод смешивания с использованием функции glsl mix, но я хочу попробовать дизеринг, поскольку он позволяет вам сэмплировать только одну карту, если возможно, а не две. (Я знаю, что в моем примере кода я все равно пробую оба, но это можно изменить) - person Jorayen; 16.04.2020
comment
@Jorayen, знаете ли вы коэффициенты смешивания (альфа)? - person Spektre; 16.04.2020
comment
Не уверен, что понимаю, каковы коэффициенты соотношения смешивания? - person Jorayen; 16.04.2020
comment
@Jorayen веса ... то, что вы делаете, это 'image = image0 * alpha + (1-alpha) * image1', альфа - это то, что я спросил. Исходя из этого, вы могли бы переписать дизеринг, чтобы избежать использования матрицы и иметь гораздо лучшие статистические свойства или числовую стабильность ... Я думаю, что это u_CascadeBlend, какой это диапазон? - person Spektre; 16.04.2020
comment
Мне сложно понять, о каких весах вы говорите, как я реализовал это с помощью пороговой матрицы. u_CascadeBlend находится в диапазоне <0.0, 1.0> - person Jorayen; 16.04.2020
comment
@Jorayen Blending всегда объединяет 2 или более изображений вместе, и соотношение (или вероятность) того, какая часть каждого изображения идет на окончательный вывод, называется весом ... или альфа ... не имеет значения, какой метод смешивания вы используете для этой цели. то же самое ... Попробуйте это: threshold=mod(3.5*gl_FragCoord.x+7.1*gl_FragCoord.y, 100)*0.01; вместо матричного материала ... - person Spektre; 16.04.2020
comment
Да, но я не пытаюсь смешивать как таковое, больше похоже на выбор между картами теней, как вы писали, без весов. Желаемый эффект должен выглядеть примерно так: catlikecoding.com/unity/ tutorials / custom-srp / в разделе "Смещенный переход". - person Jorayen; 16.04.2020
comment
@Jorayen, вы используете веса, которых вы просто не видите ... u_CascadeBlend - это вероятность, а также вес (это то же самое) ... threshold - ваше случайное значение, масштабируемое по весу ... сгенерированное из положения фрагмента (как PRNG Seed) и набор математических вычислений (mod), которые создают случайные целые числа, которые вы используете для просмотра предопределенного распределения вероятностей переключения между отдельными исходными изображениями (indexMatrix8x8) ... вот как я это вижу, по крайней мере ... просто обходной путь для отсутствия ГПСЧ в GLSL - person Spektre; 16.04.2020
comment
Понятно, теперь это объяснение меня объединяет :) - person Jorayen; 16.04.2020

Я заставил смесь дизеринга работать в моем коде следующим образом:

for (int i = 0; i < NR_LIGHT_SPACE; i++) {
        if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i])
        {
            float fade = fadedShadowStrength(fs_in.v_FragPosClipSpaceZ, 1.0 / u_CascadeEndClipSpace[i], 1.0 / u_CascadeBlend);
            if (fade < 1.0) {
                int x = int(mod(gl_FragCoord.x, 8));
                int y = int(mod(gl_FragCoord.y, 8));
                float threshold = (indexMatrix8x8[(x + y * 8)] + 1) / 64.0;
                if (fade < threshold)
                {
                    shadow = isInShadow(fs_in.v_FragPosLightSpace[i + 1], normal, lightDirection, i + 1) * u_ShadowStrength;
                }
                else
                {
                    shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i) * u_ShadowStrength;
                }
            }
            else
            {
                shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i) * u_ShadowStrength;
            }
            break;
        }
}

Сначала проверьте, близки ли мы к каскадному разделению по коэффициенту затухания, принимая во внимание пространство клипа позиции фрагмента и конец пространства клипа каскада с fadedShadowStrength (я использую эту функцию для нормального смешивания между каскадом, чтобы знать, когда начинать смешивание, в основном, если коэффициент смешивания u_CascadeBlend установлен на 0.1, то мы выполняем смешивание, когда мы, по крайней мере, на 90% в текущем каскаде (по пространству z-клипа).

Затем, если нам нужно затухание (if (fade <1.0)), я просто сравниваю коэффициент затухания с порогом из матрицы и соответственно выбираю карту теней. Результаты:  введите описание изображения здесь

person Jorayen    schedule 16.04.2020