Как лучше всего отображать графику более 30 бит / с на дисплее 24 бит / с?
Каков наилучший способ отображения наиболее достоверной графики, имеющей более 8 бит на канал, на обычном дисплее 24 бит / с?
1 ответ
Лучшее решение, которое я могу придумать, основано на случайном сглаживании, которое меняет каждый кадр. Это объединяет преимущество дизеринга с отсутствием фиксированного шаблона дизеринга, и поскольку данный пиксель изменяет значения много раз в секунду, то, что вы воспринимаете, ближе к среднему значению этих различных значений, что ближе к исходному значению "глубокого цвета", чем любое заданное значение 24 bpp.
Как это выглядит
Градиент зеленого цвета, без привязки, сглаженный (показано 10 кадров), затем оба улучшаются одинаково для видимости:
Дизеринг
Дизеринг достигается путем добавления значения насыщенного гамма-сжатия для каждого канала со случайным значением, а затем округления до ближайшего 8-битного значения. Казалось бы естественным использовать случайные числа с равномерным распределением между -0,5 и 0,5 (я говорю в единицах, которые эквивалентны 1 в 8-битных значениях гамма-сжатия, например, разница между 0 и 1 или 254 и 255) однако это может привести к некоторому артефакту полосатости, при котором значения градиента, близкие к 8-битному значению, будут иметь небольшой шум, тогда как значения, наиболее удаленные от любого 8-битного значения, будут показывать намного больший шум. Гауссовский шум является гораздо более подходящим, поскольку он дает намного более плавный уровень шума. Я выбрал сигму 1,0, но для меньшего шума может подойти сигма 0,8.
Вы можете создать гауссовский PRNG, взяв два случайных числа, n1
а также n2
подбирая их каждый в диапазоне [-1, 1], и если они представляют точку внутри единичного круга (если сумма sum
их квадратов уступает или равен 1, в противном случае начните снова) sqrt(-2. * log(sum) / sum) * n1
,
Практическая реализация
Я решил реализовать это путем преобразования 15-битного линейного кадрового буфера RGB в 8-битный кадровый буфер sRGB. Часть от linear до sRGB - это просто деталь, я использую справочную таблицу для преобразования линейных значений в гамма-сжатые значения (я выбрал, чтобы эти промежуточные значения использовали 13 битов, вы можете видеть это как запись с фиксированной точкой 8.5 для значений sRGB).
Само собой разумеется, что вы не собираетесь генерировать новое случайное гауссово число для каждого пикселя, вам нужно предварительно рассчитать их несколько и поместить их в кольцевой буфер. Я решил сделать 16384 из них, да, только 16384, я избегаю любых повторяющихся паттернов, выбирая случайную точку входа в этом буфере, случайную длину для прохождения (между 100 и 1123, это довольно произвольно), и когда я достигаю конец длины Я выбрал новую случайную начальную точку и новую случайную длину. Таким образом, я получаю довольно случайные неповторяющиеся шаблоны из сравнительно небольшого буфера чисел. Числа в буфере хранятся в формате с фиксированной запятой 2.5, поэтому все они находятся в диапазоне от -4.0 до 4.0, что охватывает диапазон гауссовых случайных чисел, который я хочу получить. Просто убедитесь, что добавили 0,5 к вашим случайным числам, так как это позаботится о округлении до ближайшего целого числа позже.
Вот как это работает для каждого пикселя и каждого канала:
15-разрядное линейное значение --via LUT -> 13-разрядное (с фиксированной запятой 8,5) значение с гамма-сжатием, затем ДОБАВИТЬ 2,5 случайного числа с фиксированной запятой, затем сдвинуть на 5 бит вправо.
Теперь вы получаете целочисленное значение в диапазоне от -4 до 260, вы можете использовать if() для их ограничения, но гораздо быстрее использовать LUT из 264 элементов, который возвращает 0 для отрицательных чисел (вы можете использовать отрицательные числа в качестве индекса, выделяя затем ваш буфер выполняет buffer = &buffer[4], я думаю, что это спасает вас, и это возвращает 255 для чисел выше 255. Также я использую одно и то же случайное число для каждого из трех цветовых каналов, это позволяет избежать хроматического шума, хотя, возможно, результат может выглядеть несколько менее шумным, если эти три используют независимые числа.
Для красного канала одного пикселя мой код выглядит так:sfb[i].r = bytecheck_l.lutb[lsrgb_l.lutint[fb[i].r] + dither_l.lutint[id] >> 5];
sfb - буфер sRGB 24 bpp, fb - линейный буфер RGB 45 bpp, lsrgb_l.lutint[] - LUT со сжатием от линейной до гамма-структуры, dither_l.lutint [] LUT, содержащая случайные гауссовы числа в формате с фиксированной запятой 2.5, и bytecheck_l.lutb[] возвращает значения, обрезанные до [0, 255].
Спектакль
Я получаю более 50 кадров в секунду в окне SDL 1400x820 с моим тестовым градиентом, используя только одно ядро 2,4 ГГц Core 2 Quad Q6600 и двухканальную память 800 МГц DDR2, довольно посредственную машину по нынешним стандартам, поэтому это решение, безусловно, подходит для современных компьютеры.
Пожалуйста, дайте мне знать, если какие-либо из моих объяснений требуют разъяснений.