Итак, у меня есть некоторый код, который предназначен для генерации линейного градиента между двумя входными цветами:
struct color {
float r, g, b, a;
}
color produce_gradient(const color & c1, const color & c2, float ratio) {
color output_color;
output_color.r = c1.r + (c2.r - c1.r) * ratio;
output_color.g = c1.g + (c2.g - c1.g) * ratio;
output_color.b = c1.b + (c2.b - c1.b) * ratio;
output_color.a = c1.a + (c2.a - c1.a) * ratio;
return output_color;
}
Я также написал (семантически идентичный) код в мои шейдеры.
Проблема заключается в том, что использование такого рода кода создает «темные полосы» в середине, где сходятся цвета, из-за причуд того, как яркость переводится между экраном компьютера и необработанными данными, используемыми для представления этих пикселей.
Итак, у меня есть следующие вопросы:
Код:
color produce_gradient(const color & c1, const color & c2, float ratio) {
color output_color;
output_color.r = pow(pow(c1.r,2.2) + (pow(c2.r,2.2) - pow(c1.r,2.2)) * ratio, 1/2.2);
output_color.g = pow(pow(c1.g,2.2) + (pow(c2.g,2.2) - pow(c1.g,2.2)) * ratio, 1/2.2);
output_color.b = pow(pow(c1.b,2.2) + (pow(c2.b,2.2) - pow(c1.b,2.2)) * ratio, 1/2.2);
output_color.a = pow(pow(c1.a,2.2) + (pow(c2.a,2.2) - pow(c1.a,2.2)) * ratio, 1/2.2);
return output_color;
}
РЕДАКТИРОВАТЬ: Для справки, вот пост, который связан с этой проблемой, чтобы объяснить, как «ошибка» выглядит на практике: https://graphicdesign.stackexchange.com/questions/64890/in-gimp-how-do-i-get-the-smudge-blur-tools-to-work-properly
Я думаю, что в вашем коде есть изъян.
сначала я бы удостоверился, что 0 <= ratio <=1
во-вторых, я бы использовал формулу c1.x * (1-ratio) + c2.x *ratio
способ, которым вы настроили свои вычисления в данный момент, позволяет получить отрицательные результаты, которые объясняют темные пятна.
Там нет никакого ответа, когда вам нужно беспокоиться о гамме.
Обычно вы хотите работать в линейном цветовом пространстве при смешивании, смешивании, вычислительном освещении и т. Д.
Если ваши входные данные не находятся в линейном пространстве (например, с гамма-коррекцией или в некотором цветовом пространстве, таком как sRGB), тогда вы обычно хотите преобразовать их сразу в линейный. Вы не сказали нам, находятся ли ваши входы в линейном RGB.
Когда вы закончите, вы хотите убедиться, что ваши линейные значения скорректированы с учетом цветового пространства устройства вывода, будь то простая гамма или другое преобразование цветового пространства. Опять же, здесь нет простого ответа, потому что вы должны знать, выполняется ли это преобразование для вас неявно на более низком уровне в стеке или это ваша ответственность.
Тем не менее, много кода сходит с рук с читерством. Они будут принимать свои входные данные в sRGB и применять альфа-смешение или затухание, как если бы они были в линейном RGB, а затем выводить результаты как есть (возможно, с ограничением). Иногда это разумный компромисс.
Ваша проблема полностью лежит в области реализации восприятия цвета.
чтобы позаботиться о восприятии аберраций легкости, вы можете использовать один из многих алгоритмов, найденных в Интернете
один такой алгоритм Luma
float luma(color c){
return 0.30 * c.r + 0.59 * c.g + 0.11 * c.b;
}
на этом этапе я хотел бы указать, что стандартным методом будет применение всех алгоритмов в цветовом пространстве восприятия, а затем преобразование в цветовое пространство rgb для отображения.
colorRGB —(Конвертировать) -> colorPerceptual —(вход) -> f (colorPerceptual) — (вывод) -> colorPerceptual» —(Конвертировать) -> colorRGB
но если вы хотите настроить только на легкость (воспринимаемые хроматические аберрации не будут исправлены), вы можете сделать это эффективно следующим образом
//define color of unit lightness. based on Luma algorithm
color unit_l(1/0.3/3, 1/0.59/3, 1/0.11/3);
color produce_gradient(const color & c1, const color & c2, float ratio) {
color output_color;
output_color.r = c1.r + (c2.r - c1.r) * ratio;
output_color.g = c1.g + (c2.g - c1.g) * ratio;
output_color.b = c1.b + (c2.b - c1.b) * ratio;
output_color.a = c1.a + (c2.a - c1.a) * ratio;
float target_lightness = luma(c1) + (luma(c2) - luma(c1)) * ratio; //linearly interpolate perceptual lightness
float delta_lightness = target_lightness - luma(output_color); //calculate required lightness change magnitude
//adjust lightness
output_color.g += unit_l.r * delta_lightness;
output_color.b += unit_l.g * delta_lightness;
output_color.a += unit_l.b * delta_lightness;
//at this point luma(output_color) approximately equals target_lightness which takes care of the perceptual lightness aberrations
return output_color;
}
Ваш второй пример кода совершенно правильный, за исключением того, что альфа-канал обычно не гамма-коррекция, поэтому вы не должны использовать pow
в теме. Ради эффективности было бы лучше сделать гамма-коррекцию один раз для каждого канала, вместо удвоения.
Общее правило заключается в том, что вы должны выполнять гамму в обоих направлениях всякий раз, когда добавляете или вычитаете значения. Если вы только умножаете или делите, это не имеет значения: pow(pow(x, 2.2) * pow(y, 2.2), 1/2.2)
математически эквивалентно x * y
,
Иногда вы можете обнаружить, что вы получите лучшие результаты, работая в неисправленном пространстве. Например, если вы изменяете размер изображения, вам следует выполнить гамма-коррекцию, если вы уменьшаете размер, но не увеличиваете его. Я забыл, где я это читал, но я сам это проверил — артефакты от увеличения были гораздо менее спорными, если вы использовали значения пикселей с гамма-коррекцией по сравнению с линейными.