GLSL Shader - Ubah Hue / Saturation / Brightness

14

Saya mencoba mengubah rona gambar menggunakan shader fragmen GLSL. Saya ingin mencapai sesuatu yang mirip dengan layer Hue / Saturation Adjustment Photoshop.

Pada gambar berikut ini Anda bisa melihat apa yang saya dapatkan sejauh ini. Saya ingin mengubah rona kotak hijau sehingga terlihat seperti kotak merah di sebelah kanan, tetapi dengan shader ini saya mendapatkan setengah merah setengah merah muda persegi (kotak di tengah).
masukkan deskripsi gambar di sini

Apa yang saya lakukan di shader fragmen adalah mengubah warna tekstur menjadi HSV, kemudian saya menambahkan warna HSV yang saya dapatkan dari vertex shader ke dalamnya dan saya mengubah warna kembali ke RGB.
Apa yang saya lakukan salah?

Shader fragmen:

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

EDIT: Menggunakan fungsi yang disediakan oleh Sam Hocevar dalam jawabannya, masalah dengan pita merah muda telah terpecahkan, tetapi saya hanya dapat mencapai setengah dari spektrum warna. Saya bisa mengubah rona dari merah ke hijau, tetapi saya tidak bisa mengubahnya menjadi biru atau merah muda. masukkan deskripsi gambar di sini

Di fragmen shader, saya melakukan ini sekarang:

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);
}
miviclin
sumber
Bukankah maksud Anda, int hi = int(h/60.0); float f = h/60.0 - float(hi);bukan int hi = int(h); float f = h - float(hi);? Namun, tidak tahu apakah itu penyebabnya.
kolrabi
@ Kolrabi Saya sudah mencobanya tetapi saya masih mendapatkan band-band pink. Saya akhirnya memecahkan masalah itu dengan fungsi konversi yang diberikan Sam Hocevar dalam jawabannya.
miviclin
@mivic: Kami tidak menjawab pertanyaan. Jika Anda menemukan jawabannya sendiri, kirimkan jawaban untuk pertanyaan Anda.
Nicol Bolas

Jawaban:

22

Fungsi-fungsi ini akan berkinerja sangat buruk. Saya sarankan menggunakan fungsi yang ditulis dengan GPU dalam pikiran. Ini milik saya:

vec3 rgb2hsv(vec3 c)
{
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

Perhatikan bahwa untuk fungsi-fungsi ini kisaran untuk H[0 ... 1] bukan [0 ... 360], jadi Anda harus menyesuaikan input Anda.

Sumber: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl

sam hocevar
sumber
Menggunakan fungsi-fungsi ini memecahkan masalah. Tidak ada lagi band pink. Tapi saya pikir saya masih melakukan kesalahan. Saya telah mengedit posting asli saya dengan informasi lebih lanjut. Terima kasih.
miviclin
1
Sangat menarik! Bisakah Anda menjelaskan mengapa fungsi-fungsi ini bekerja lebih baik? Seperti apa "memikirkan GPU"?
Marco
4
@Marco GPU tidak pandai menangani if()konstruksi besar , tetapi pandai operasi vektor (operasi paralel pada beberapa nilai skalar). Fungsi di atas tidak pernah digunakan if(), mencoba memparalelkan operasi, dan secara umum mereka menggunakan lebih sedikit instruksi. Ini biasanya merupakan indikator yang baik bahwa mereka akan menjadi lebih cepat.
sam hocevar
Apakah fungsi-fungsi ini persis sama dengan rumus HSV standar (mengabaikan kesalahan pembulatan), atau apakah itu perkiraan?
Stefan Monov
3

Seperti yang disarankan Nicol Bolas dalam komentar posting asli, saya memposting solusi untuk masalah saya dalam jawaban terpisah.

Masalah pertama adalah gambar yang dirender dengan pita merah muda, seperti yang ditunjukkan gambar dalam posting asli. Saya memperbaikinya menggunakan fungsi yang disediakan oleh Sam Hocevar dalam jawabannya ( /gamedev//a/59808/22302 ).

Masalah kedua adalah bahwa saya mengalikan rona piksel tekstur dengan nilai yang saya kirim ke shader, yang dimaksudkan untuk mengimbangi rona piksel tekstur, jadi saya harus melakukan penambahan alih-alih penggandaan.
Saya masih melakukan penggandaan untuk saturasi dan kecerahan karena saya mendapatkan perilaku aneh sebaliknya, dan saya tidak benar-benar perlu menambahkannya lebih jauh daripada saturasi atau kecerahan tekstur asli saat ini.

Ini adalah metode utama () dari shader yang saya gunakan saat ini. Dengan ini saya dapat mengubah rona dari 0º ke 360º, desaturate gambar, dan mengurangi kecerahan.

void main() {
    vec4 textureColor = texture2D(sTexture, vTextureCoord);
    vec3 fragRGB = textureColor.rgb;
    vec3 fragHSV = rgb2hsv(fragRGB).xyz;
    fragHSV.x += vHSV.x / 360.0;
    fragHSV.yz *= vHSV.yz;
    fragHSV.xyz = mod(fragHSV.xyz, 1.0);
    fragRGB = hsv2rgb(fragHSV);
    gl_FragColor = vec4(fragRGB, textureColor.w);
} 
miviclin
sumber
2
tip: Anda mungkin ingin mod()rona, tetapi saturasi dan kecerahan Anda mungkin ingin clamp()sebaliknya.
Gustavo Maciel