2012-11-03 22 views
8

Obecnie pracuję nad małym projektem w C++ i OpenGL i próbuję zaimplementować narzędzie do wyboru kolorów podobne do tego w programie photoshop, jak poniżej.Interpolacja kolorów OpenGL

enter image description here

Jednak mam problemy z interpolacją dużego placu. Praca na moim komputerze stacjonarnym z 8800 GTS była podobna, ale mieszanie nie było tak płynne.

Jest to kod używam:

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); 

Przeniesienie na moim laptopie z Intel HD Graphics 3000, wynik ten był nawet gorszy bez zmian w kodzie.

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

myślałem, że to OpenGL podział quad na dwa trójkąty, więc próbowałem renderowanie za pomocą trójkątów i interpolacji koloru w środku placu siebie, ale to nadal nie robi zupełnie dopasować wynik miałem nadzieję .

enter image description here

Odpowiedz

11

ja szybko się wierzchołek/fragment shader, który z powodzeniem interpoluje kolory:

#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); 
} 

oto wynik: enter image description here

Stosowanie go na quad teraz otrzymuje się poprawny wynik, ponieważ interpolacja jest naprawdę wykonywana na całej powierzchni przy użyciu współrzędnych x i y. Zobacz szczegółowe wyjaśnienie @ datenwolf, dlaczego tak działa.

Edycja 1 W celu uzyskania pełnej gamy kolorów w funkcjonalnym colorpicker, możliwe jest zmodyfikowanie odcień interaktywnie (patrz https://stackoverflow.com/a/9234854/570738).

demo na żywo w Internecie: 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; 
} 

EDIT 2: W razie potrzeby, shadery do użytku ze współrzędnych tekstur (zastosowanie na quadach z współrzędne tekstury od 0 do 1) powinien wyglądać mniej więcej tak. Bez ograniczeń.

cieniującego fragmenty:

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); 
} 

Przejściówka werteksach:

void main() 
{ 
    gl_TexCoord[0]=gl_MultiTexCoord0; 
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
} 
+0

Dzięki za odpowiedź, mam bardzo małe doświadczenie z GLSL, chociaż wydaje mi się, że pamiętam, że jeśli chcesz użyć shadera, musisz wszystko zaimplementować, ponieważ całkowicie zastępuje on potok OpenGL? Czy istnieje prosty sposób użycia dostarczonego modułu cieniującego i zastosowanie go tylko do jednego wywołania funkcji glDrawArrays()? –

+0

@ Wola fortuny: Dopóki używasz OpenGL-2.1 i poniżej, możesz używać wybiórczo. To znaczy. do większości rzeczy używaj potokowej funkcji, ale tylko shadery. Tylko z rdzeniem OpenGL-3 i późniejszym * musi * używać shaderów. Ale szczerze: korzystanie z shaderów sprawia, że ​​życie jest o wiele prostsze, więc zdecydowanie polecam je wszędzie. – datenwolf

+0

@ Will-of-fortune Zwykle używam frameworków lub silnika graficznego, co znacznie ułatwia zarządzanie shaderów. Jednak odsyłam do tego: http://nehe.gamedev.net/article/glsl_an_introduction/25007/ Przejdź do "Interfejsu API GLSL Jak używać GLSL w twojej aplikacji OpenGL". W tej sekcji pokażemy, jak włączyć glsl shadery lepiej niż ja w tych komentarzach. – num3ric

9

OpenGL wykorzystuje barycentrycznej interpolację wartości emitowanych z cieniującego wierzchołka do wartości na wejściu-fragment cieniującego fragmentu . To, co widzisz, to efekt podzielenia kwadratu na trójkąty, jak już wiesz.

Teraz spójrz na to: znajduje się trójkąt w prawym górnym rogu, który ma czerwony kolor, a więc ładnie interpoluje w dół z czerwoną zawartością. A tam jest lewy dolny trójkąt z szarym tylko.Oczywiście czerwony trójkąta w prawym górnym rogu nie przyczyni się do szarego lewego dolnego.

Problem polega na tym, że interpolacja odbywa się w przestrzeni RGB, ale aby pożądany wynik musiał być umieszczony w przestrzeni HSV lub HSL, gdzie H (Barwa) byłaby stała.

Jednak nie jest to tylko kwestia interpolacji. To, co wchodzi ci w drogę, jest takie, że kolory nie interpolują liniowo; większość wyświetlaczy ma zastosowaną funkcję nieliniową, znaną również jako "gamma" (w rzeczywistości współczynnik gamma jest wykładnikiem mocy stosowanej do wartości wejściowych).

Kontekst OpenGL może być w przestrzeni kolorów z korektą kolorów. Musisz to sprawdzić. Następnie, wiedząc, w której przestrzeni kolorów znajduje się bufor ramki, musisz zastosować transformację fragmentów współrzędnych barycentrycznych (ocenianych za pomocą modułu cieniującego wierzchołków) na wartości fragmentów za pomocą modułu cieniującego fragment.

3

mi się nie podoba szczyt głosowało odpowiedź, ponieważ jest tak naprawdę tylko dosłowne i skupia się na czerwono w prostej odpowiedzi tak chciałem przyjść z prostą alternatywę na podstawie koloru wierzchołków:

#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); 
} 

Jest to łatwe do wyjaśnienia i uzasadnienia. mix to interpolacja liniowa lub lerp in glsl. Tak więc mieszam kolor wierzchołka i bieli wzdłuż odwrotnej osi X, to daje mi białe w lewym górnym rogu i czysty kolor w prawym górnym rogu. W wyniku tego miksuję się z czernią wzdłuż osi Y, dając mi czarny na dole.

Mam nadzieję, że to pomogło, natknąłem się na tę odpowiedź i okazało się, że nie było to zbyt pomocne w uzyskiwaniu niczego poza trudnymi czerwonymi, zielonymi lub niebieskimi paletami, a bit dopasowywania po prostu nie był tym, czego chciałem.

Zasadniczo, aby uzyskać to do pracy chcesz ustawić wszystkie kolory wierzchołków do pożądanego koloru, i zapewnić sobie współrzędne UV są:

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)