Próbuję zmienić odcień obrazu za pomocą modułu cieniującego fragmenty GLSL. Chcę osiągnąć coś podobnego do warstwy korekty odcienia / nasycenia w Photoshopie.
Na poniższym obrazku możesz zobaczyć, co mam do tej pory. Chcę zmienić odcień zielonego kwadratu, aby wyglądał jak czerwony kwadrat po prawej stronie, ale za pomocą tego modułu cieniującego dostaję pół czerwony pół różowy kwadrat (kwadrat na środku).
To, co robię w module cieniującym fragmenty, polega na konwersji koloru tekstury na HSV, a następnie dodaję do niego kolor HSV, który otrzymuję z modułu cieniującego wierzchołki, i przekształcam kolor z powrotem na RGB.
Co ja robię źle?
Moduł cieniujący fragmenty:
precision mediump float;
varying vec2 vTextureCoord;
varying vec3 vHSV;
uniform sampler2D sTexture;
vec3 convertRGBtoHSV(vec3 rgbColor) {
float r = rgbColor[0];
float g = rgbColor[1];
float b = rgbColor[2];
float colorMax = max(max(r,g), b);
float colorMin = min(min(r,g), b);
float delta = colorMax - colorMin;
float h = 0.0;
float s = 0.0;
float v = colorMax;
vec3 hsv = vec3(0.0);
if (colorMax != 0.0) {
s = (colorMax - colorMin ) / colorMax;
}
if (delta != 0.0) {
if (r == colorMax) {
h = (g - b) / delta;
} else if (g == colorMax) {
h = 2.0 + (b - r) / delta;
} else {
h = 4.0 + (r - g) / delta;
}
h *= 60.0;
if (h < 0.0) {
h += 360.0;
}
}
hsv[0] = h;
hsv[1] = s;
hsv[2] = v;
return hsv;
}
vec3 convertHSVtoRGB(vec3 hsvColor) {
float h = hsvColor.x;
float s = hsvColor.y;
float v = hsvColor.z;
if (s == 0.0) {
return vec3(v, v, v);
}
if (h == 360.0) {
h = 0.0;
}
int hi = int(h);
float f = h - float(hi);
float p = v * (1.0 - s);
float q = v * (1.0 - (s * f));
float t = v * (1.0 - (s * (1.0 - f)));
vec3 rgb;
if (hi == 0) {
rgb = vec3(v, t, p);
} else if (hi == 1) {
rgb = vec3(q, v, p);
} else if (hi == 2) {
rgb = vec3(p, v, t);
} if(hi == 3) {
rgb = vec3(p, q, v);
} else if (hi == 4) {
rgb = vec3(t, p, v);
} else {
rgb = vec3(v, p, q);
}
return rgb;
}
void main() {
vec4 textureColor = texture2D(sTexture, vTextureCoord);
vec3 fragRGB = textureColor.rgb;
vec3 fragHSV = convertRGBtoHSV(fragRGB);
fragHSV += vHSV;
fragHSV.x = mod(fragHSV.x, 360.0);
fragHSV.y = mod(fragHSV.y, 1.0);
fragHSV.z = mod(fragHSV.z, 1.0);
fragRGB = convertHSVtoRGB(fragHSV);
gl_FragColor = vec4(convertHSVtoRGB(fragHSV), textureColor.w);
}
EDYCJA: Korzystając z funkcji Sama Hocevara podanych w jego odpowiedzi, problem z różowymi pasmami został rozwiązany, ale mogę osiągnąć tylko połowę spektrum kolorów. Mogę zmienić odcień z czerwonego na zielony, ale nie mogę go zmienić na niebieski lub różowy.
W shaderze fragmentów robię to teraz:
void main() {
vec4 textureColor = texture2D(sTexture, vTextureCoord);
vec3 fragRGB = textureColor.rgb;
vec3 fragHSV = rgb2hsv(fragRGB);
float h = vHSV.x / 360.0;
fragHSV.x *= h;
fragHSV.yz *= vHSV.yz;
fragHSV.x = mod(fragHSV.x, 1.0);
fragHSV.y = mod(fragHSV.y, 1.0);
fragHSV.z = mod(fragHSV.z, 1.0);
fragRGB = hsv2rgb(fragHSV);
gl_FragColor = vec4(hsv2rgb(fragHSV), textureColor.w);
}
źródło
int hi = int(h/60.0); float f = h/60.0 - float(hi);
zamiastint hi = int(h); float f = h - float(hi);
? Nie wiem jednak, czy to powoduje.Odpowiedzi:
Funkcje te będą działać bardzo źle. Sugeruję używanie funkcji napisanych z myślą o GPU. Oto moje:
Pamiętaj, że dla tych funkcji zakres
H
wynosi [0… 1] zamiast [0… 360], więc będziesz musiał dostosować swoje dane wejściowe.Źródło: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
źródło
if()
konstrukcji, ale są dobre w operacjach wektorowych (operacje równoległe na kilku wartościach skalarnych). Powyższe funkcje nigdy nie używająif()
, próbują zrównoważyć operacje i ogólnie używają mniej instrukcji. Są to zwykle dobre wskaźniki, że będą szybsze.Jak zasugerował Nicol Bolas w komentarzach do oryginalnego postu, rozwiązanie mojego problemu zamieszczam w osobnej odpowiedzi.
Pierwszy problem polegał na renderowaniu obrazu za pomocą różowych pasków, jak pokazano w oryginalnym poście. Naprawiłem to za pomocą funkcji Sama Hocevara podanych w jego odpowiedzi ( /gamedev//a/59808/22302 ).
Drugi problem polegał na tym, że mnożałem odcień piksela tekstury przez wartość, którą wysyłałem do modułu cieniującego, która ma być przesunięciem względem odcienia piksela tekstury, więc musiałem wykonać dodawanie zamiast mnożenia.
Nadal wykonuję mnożenie dla nasycenia i jasności, ponieważ w przeciwnym razie mam dziwne zachowanie i tak naprawdę nie potrzebuję zwiększać ich dalej niż nasycenie lub jasność oryginalnej tekstury.
To jest główna () metoda modułu cieniującego, którego teraz używam. Dzięki temu mogę zmieniać odcień od 0º do 360º, desaturować obraz i zmniejszać jasność.
źródło
mod()
odcień, aleclamp()
zamiast tego możesz chcieć nasycenia i jasności .