Цветовая интерполяция OpenGL

В настоящее время я работаю над небольшим проектом на C ++ и OpenGL и пытаюсь реализовать инструмент выбора цвета, аналогичный тому, что используется в Photoshop, как показано ниже.

введите описание изображения здесь

Однако у меня возникли проблемы с интерполяцией большого квадрата. При работе на моем настольном компьютере с 8800 GTS результат был аналогичным, но смешивание не было таким гладким.

Это код, который я использую:

GLfloat swatch[] = { 0,0,0, 1,1,1, mR,mG,mB, 0,0,0 };
GLint swatchVert[] = { 400,700, 400,500, 600,500, 600,700 };

glVertexPointer(2, GL_INT, 0, swatchVert);
glColorPointer(3, GL_FLOAT, 0, swatch);
glDrawArrays(GL_QUADS, 0, 4);

При переходе на мой ноутбук с Intel Graphics HD 3000 результат был еще хуже, без изменения кода.

http://i.imgur.com/wSJI2.png

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

введите описание изображения здесь


person Will-of-fortune    schedule 03.11.2012    source источник


Ответы (3)


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

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

Проблема в том, что интерполяция происходит в пространстве RGB, но для вашего желаемого результата ее необходимо разместить в пространстве HSV или HSL, где H (оттенок) будет оставаться постоянным.

Однако дело не только в интерполяции. Что вам мешает, так это то, что цвета не интерполируются линейно; к большинству дисплеев применена нелинейная функция, также известная как «гамма» (фактически гамма - это показатель степени мощности, приложенной к входным значениям).

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

person datenwolf    schedule 03.11.2012

Я быстро сделал вершинный / фрагментный шейдер, который успешно интерполирует цвета:

#ifdef GL_ES
precision highp float;
#endif

uniform vec2 resolution;

void main(void)
{
    vec2 p = gl_FragCoord.xy / resolution.xy;
    float gray = 1.0 - p.x;
    float red = p.y;
    gl_FragColor = vec4(red, gray*red, gray*red, 1.0);
}

Вот результат: введите описание изображения здесь

Применение его к квадрату теперь дает правильный результат, потому что интерполяция действительно выполняется по всей поверхности с использованием координат x и y. См. Подробное объяснение @ datenwolf, почему это работает.

ИЗМЕНИТЬ 1 Чтобы получить полный диапазон цветов в функциональном палитре цветов, можно изменить оттенок в интерактивном режиме (см. https://stackoverflow.com/a/9234854/570738).

Онлайн-демонстрация: http://goo.gl/Ivirl

#ifdef GL_ES
precision highp float;
#endif

uniform float time;
uniform vec2 resolution;

const vec4  kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
const vec4  kRGBToI     = vec4 (0.596, -0.275, -0.321, 0.0);
const vec4  kRGBToQ     = vec4 (0.212, -0.523, 0.311, 0.0);

const vec4  kYIQToR   = vec4 (1.0, 0.956, 0.621, 0.0);
const vec4  kYIQToG   = vec4 (1.0, -0.272, -0.647, 0.0);
const vec4  kYIQToB   = vec4 (1.0, -1.107, 1.704, 0.0);

const float PI = 3.14159265358979323846264;

void adjustHue(inout vec4 color, float hueAdjust) {
    // Convert to YIQ
    float   YPrime  = dot (color, kRGBToYPrime);
    float   I      = dot (color, kRGBToI);
    float   Q      = dot (color, kRGBToQ);

    // Calculate the hue and chroma
    float   hue     = atan (Q, I);
    float   chroma  = sqrt (I * I + Q * Q);

    // Make the user's adjustments
    hue += hueAdjust;

    // Convert back to YIQ
    Q = chroma * sin (hue);
    I = chroma * cos (hue);

    // Convert back to RGB
    vec4 yIQ   = vec4 (YPrime, I, Q, 0.0);
    color.r = dot (yIQ, kYIQToR);
    color.g = dot (yIQ, kYIQToG);
    color.b = dot (yIQ, kYIQToB);
}

void main(void)
{
    vec2 p = gl_FragCoord.xy / resolution.xy;
    float gray = 1.0 - p.x;
    float red = p.y;
    vec4 color = vec4(red, gray*red, gray*red, 1.0);
    adjustHue(color, mod(time, 2.0*PI));
    gl_FragColor = color;
}

РЕДАКТИРОВАТЬ 2: при необходимости шейдеры для использования с координатами текстуры (для применения к квадратам с координатами текстуры от 0 до 1) должны выглядеть примерно так. Не проверено.

Фрагментный шейдер:

void main(void)
{
    vec2 p = gl_TexCoord[0].st;
    float gray = 1.0 - p.x;
    float red = p.y;
    gl_FragColor = vec4(red, gray*red, gray*red, 1.0);
}

Сквозной вершинный шейдер:

void main()
{
    gl_TexCoord[0]=gl_MultiTexCoord0; 
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
person num3ric    schedule 03.11.2012
comment
Спасибо за ваш ответ, у меня очень мало опыта работы с GLSL, хотя я, кажется, помню, что читал, если вы хотите использовать шейдер, вам нужно реализовать все, потому что он полностью заменяет конвейер OpenGL? Есть ли простой способ использовать предоставленный шейдер и применить его только к одному вызову glDrawArrays ()? - person Will-of-fortune; 04.11.2012
comment
@ Will-of-fortune: пока вы придерживаетесь OpenGL-2.1 и ниже, вы можете использовать шейдеры выборочно. Т.е. использовать фиксированный конвейер функций для большинства вещей, но шейдер только для некоторых. Только с ядром OpenGL-3 и более поздними версиями необходимо использовать шейдеры. Но честно говоря: использование шейдеров значительно упрощает жизнь, поэтому я настоятельно рекомендую использовать их везде. - person datenwolf; 04.11.2012
comment
@ Will-of-fortune Я обычно использую фреймворки или графический движок, которые значительно упрощают управление шейдерами. Однако я отсылаю вас к следующему: nehe.gamedev.net/article/glsl_an_introduction/25007 Перейдите к разделу GLSL API Как использовать GLSL в вашем приложении OpenGL. Этот раздел покажет вам, как использовать шейдеры glsl лучше, чем я мог бы в этих комментариях. - person num3ric; 04.11.2012

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

#version 330 core

smooth in vec4 color;
smooth in vec2 uv;

out vec4 colorResult;

void main(){
    float uvX = 1.0 - uv.st.x;
    float uvY = uv.st.y;

    vec4 white = vec4(1, 1, 1, 1);
    vec4 black = vec4(0, 0, 0, 1);

    colorResult = mix(mix(color, white, uvX), black, uvY);
}

Это просто объяснить и рассуждать. mix - это линейная интерполяция или lerp в glsl. Итак, я смешиваю цвет вершины и белый по обратной оси x, это дает мне белый цвет в верхнем левом углу и чистый цвет в верхнем правом углу. В результате я смешиваю с черным по оси Y, получая черный цвет внизу.

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

В основном, чтобы это работало, вы хотите установить все цвета вершин на желаемый цвет и убедиться, что ваши UV-координаты установлены:

TL: UV(0, 0), COLOR(YOUR DESIRED COLOR)
BL: UV(0, 1), COLOR(YOUR DESIRED COLOR)
BR: UV(1, 1), COLOR(YOUR DESIRED COLOR)
TR: UV(1, 0), COLOR(YOUR DESIRED COLOR)
person M2tM    schedule 18.04.2015