2011-01-18 39 views
8

Tôi có một ứng dụng mà người dùng có thể sửa đổi hình ảnh bằng các thanh trượt cho màu sắc, độ bão hòa và độ sáng. Tất cả xử lý hình ảnh được thực hiện trên GPU bằng cách sử dụng trình đổ bóng đoạn GLSL.Điều chỉnh hình ảnh HSL trên GPU

Vấn đề của tôi là chuyển đổi RGB -> HSL -> RGB khá đắt trên GPU do phân nhánh mở rộng.

Câu hỏi của tôi là liệu tôi có thể chuyển đổi "điều chỉnh màu" của người dùng sang một số không gian màu khác có thể tính toán hiệu quả hình ảnh được điều chỉnh trên GPU hay không.

Trả lời

8

Để biết độ sáng và độ bão hòa, bạn có thể sử dụng YUV(actually YCbCr). Thật dễ dàng để chuyển đổi từ RGB và ngược lại. Không cần phân nhánh. Độ bão hòa được kiểm soát bằng cách tăng hoặc giảm cả Cr và Cb. Độ sáng là Y.

Bạn nhận được một cái gì đó tương tự như HSL biến đổi màu sắc bằng cách quay Cb và Cr thành phần (nó thực tế là một vector 3D), nhưng tất nhiên nó phụ thuộc vào ứng dụng của bạn nếu đó là đủ.

alt text

Edit: Một thành phần màu (Cb, Cr) là một điểm trong một chiếc máy bay màu như trên. Nếu bạn lấy bất kỳ điểm ngẫu nhiên nào và xoay nó quanh tâm, kết quả sẽ thay đổi. Nhưng khi cơ chế khác đôi chút so với HSL, kết quả không giống nhau.

Image là miền công cộng từ Wikipedia.

+0

Câu trả lời hay, tuy nhiên tôi vẫn không chắc cách tương tự "tương tự" liên quan đến sửa đổi màu sắc. – ronag

+1

Tôi đã thêm làm rõ cho vấn đề màu sắc. – Virne

1

Bạn có thể sử dụng Bảng tra cứu 3D để lưu trữ biến đổi màu, bảng sẽ được cập nhật bởi các biến người dùng, nhưng có thể có các giao thức đơn giản hơn.

Thông tin khác có sẵn trong GPU Gems 2.

1

Tôi tin rằng chuyển đổi giữa RGB và HSV/HSL có thể được mã hóa mà không cần phân nhánh gì cả. Ví dụ: cách chuyển đổi RGB -> HSV mà không cần phân nhánh có thể tìm trong GLSL:

vec3 RGBtoHSV(float r, float g, float b) { 
    float minv, maxv, delta; 
    vec3 res = vec3(0.0); 

    minv = min(min(r, g), b); 
    maxv = max(max(r, g), b); 
    res.z = maxv; 
    delta = maxv - minv; 

    // branch1 maxv == 0.0 
    float br1 = 1.0 - abs(sign(maxv)); 
    res.y = mix(delta/maxv, 0.0, br1); 
    res.x = mix(res.x, -1.0, br1); 

    // branch2 r == maxv 
    float br2 = abs(sign(r - maxv)); 
    float br2_or_br1 = max(br2,br1); 
    res.x = mix((g - b)/delta, res.x, br2_or_br1); 

    // branch3 g == maxv 
    float br3 = abs(sign(g - maxv)); 
    float br3_or_br1 = max(br3,br1); 
    res.x = mix(2.0 + (b - r)/delta, res.x, br3_or_br1); 

    // branch4 r != maxv && g != maxv 
    float br4 = 1.0 - br2*br3; 
    float br4_or_br1 = max(br4,br1); 
    res.x = mix(4.0 + (r - g)/delta, res.x, br4_or_br1); 

    res.x = mix(res.x * 60.0, res.x, br1); 

    // branch5 res.x < 0.0 
    float br5 = clamp(sign(res.x),-1.0,0.0) + 1.0; 
    float br5_or_br1 = max(br5,br1); 
    res.x = mix(res.x + 360.0, res.x, br5_or_br1); 

    return res; 
} 

Nhưng tôi chưa đánh giá được giải pháp này. Nó có thể là một số hiệu suất đạt được mà chúng tôi giành chiến thắng mà không cần phân nhánh ở đây có thể được bù đắp bởi tổn thất hiệu suất của việc thực thi mã dự phòng. Vì vậy, thử nghiệm rộng rãi là cần thiết ...

+0

Nó không hoạt động đối với tôi và phàn nàn về res.x chưa được khởi tạo. – kaoD

+0

Khởi tạo 'res' cố định. Những gì bạn có nghĩa là chính xác bằng cách nói _It không hoạt động_? –

+0

Toàn bộ hình ảnh được tô màu đỏ và đặt sóng sin trên màu sắc không hoạt động. Các triển khai khác chạy tốt, nhưng tôi đặc biệt quan tâm đến hiệu quả của việc này. Điều gì có thể gây ra điều này? Tôi có thể làm gì để giúp gỡ lỗi? – kaoD

3

Tôi đã có cùng một câu hỏi, nhưng tôi đã tìm thấy một giải pháp rất đơn giản phù hợp với nhu cầu của tôi, có lẽ nó cũng hữu ích cho bạn. Độ bão hòa của một màu về cơ bản nó lan rộng, tôi tin rằng đó là khoảng cách euclide giữa các giá trị RGB và giá trị trung bình của chúng. bất kể điều đó, nếu bạn chỉ đơn giản là lấy giá trị trung bình của giá trị RGB tối đa và tối thiểu, và tỷ lệ màu sắc tương ứng với trục xoay đó, hiệu ứng là sự gia tăng rất tốt (hoặc giảm) trong độ bão hòa.

trong một Shader GLSL bạn sẽ viết:

float pivot=(min(min(color.x, color.y), color.z)+max(max(color.x, color.y), color.z))/2.0; 
color.xyz -= vec3(pivot); 
color.xyz *= saturationScale; 
color.xyz += vec3(pivot); 
11

Đó là một sai lầm khi cho rằng nhánh trong GPU và phân nhánh trong mã là những điều tương tự.

Đối với các điều kiện đơn giản, không bao giờ có phân nhánh nào cả. GPU có hướng dẫn di chuyển có điều kiện trực tiếp dịch sang các biểu thức bậc ba và các câu lệnh if-else đơn giản.

Trường hợp mọi thứ gặp sự cố là khi bạn có điều kiện lồng nhau hoặc nhiều thao tác phụ thuộc vào điều kiện. Sau đó, bạn phải xem xét liệu trình biên dịch GLSL là đủ thông minh để dịch tất cả thành các cmoves. Bất cứ khi nào có thể trình biên dịch sẽ phát ra mã thực hiện tất cả các nhánh và kết hợp lại kết quả với các chuyển động có điều kiện, nhưng nó không thể luôn làm điều đó.

Bạn phải biết khi nào cần trợ giúp. Không bao giờ đoán khi bạn có thể đo lường - sử dụng GPU Shader Analyzer của AMD hoặc GCG của Nvidia để xem đầu ra lắp ráp. Bộ hướng dẫn của GPU rất hạn chế và đơn giản nên đừng sợ cụm từ 'lắp ráp'.

Đây là một cặp chức năng chuyển đổi RGB/HSL mà tôi đã thay đổi xung quanh để chúng phát độc đáo với trình biên dịch GLSL của AMD, cùng với đầu ra lắp ráp. Tín dụng chuyển đến Paul Bourke cho mã chuyển đổi C ban đầu.

// HSL range 0:1 
vec4 convertRGBtoHSL(vec4 col) 
{ 
    float red = col.r; 
    float green = col.g; 
    float blue = col.b; 

    float minc = min3(col.r, col.g, col.b); 
    float maxc = max3(col.r, col.g, col.b); 
    float delta = maxc - minc; 

    float lum = (minc + maxc) * 0.5; 
    float sat = 0.0; 
    float hue = 0.0; 

    if (lum > 0.0 && lum < 1.0) { 
     float mul = (lum < 0.5) ? (lum) : (1.0-lum); 
     sat = delta/(mul * 2.0); 
    } 

    vec3 masks = vec3(
     (maxc == red && maxc != green) ? 1.0 : 0.0, 
     (maxc == green && maxc != blue) ? 1.0 : 0.0, 
     (maxc == blue && maxc != red) ? 1.0 : 0.0 
    ); 

    vec3 adds = vec3(
       ((green - blue)/delta), 
     2.0 + ((blue - red )/delta), 
     4.0 + ((red - green)/delta) 
    ); 

    float deltaGtz = (delta > 0.0) ? 1.0 : 0.0; 

    hue += dot(adds, masks); 
    hue *= deltaGtz; 
    hue /= 6.0; 

    if (hue < 0.0) 
     hue += 1.0; 

    return vec4(hue, sat, lum, col.a); 
} 

đầu ra hội cho chức năng này:

1 x: MIN   ____, R0.y, R0.z  
    y: ADD   R127.y, -R0.x, R0.z  
    z: MAX   ____, R0.y, R0.z  
    w: ADD   R127.w, R0.x, -R0.y  
    t: ADD   R127.x, R0.y, -R0.z  
2 y: MAX   R126.y, R0.x, PV1.z  
    w: MIN   R126.w, R0.x, PV1.x  
    t: MOV   R1.w, R0.w  
3 x: ADD   R125.x, -PV2.w, PV2.y  
    y: SETE_DX10 ____, R0.x, PV2.y  
    z: SETNE_DX10 ____, R0.y, PV2.y  
    w: SETE_DX10 ____, R0.y, PV2.y  
    t: SETNE_DX10 ____, R0.z, PV2.y  
4 x: CNDE_INT R123.x, PV3.y, 0.0f, PV3.z  
    y: CNDE_INT R125.y, PV3.w, 0.0f, PS3  
    z: SETNE_DX10 ____, R0.x, R126.y  
    w: SETE_DX10 ____, R0.z, R126.y  
    t: RCP_e  R125.w, PV3.x  
5 x: MUL_e  ____, PS4,  R127.y  
    y: CNDE_INT R123.y, PV4.w, 0.0f, PV4.z  
    z: ADD/2  R127.z, R126.w, R126.y  VEC_021 
    w: MUL_e  ____, PS4,  R127.w  
    t: CNDE_INT R126.x, PV4.x, 0.0f, 1065353216  
6 x: MUL_e  ____, R127.x, R125.w  
    y: CNDE_INT R123.y, R125.y, 0.0f, 1065353216  
    z: CNDE_INT R123.z, PV5.y, 0.0f, 1065353216  
    w: ADD   ____, PV5.x, (0x40000000, 2.0f).y  
    t: ADD   ____, PV5.w, (0x40800000, 4.0f).z  
7 x: DOT4  ____, R126.x, PV6.x  
    y: DOT4  ____, PV6.y, PV6.w  
    z: DOT4  ____, PV6.z, PS6  
    w: DOT4  ____, (0x80000000, -0.0f).x, 0.0f  
    t: SETGT_DX10 R125.w, 0.5,  R127.z  
8 x: ADD   R126.x, PV7.x, 0.0f  
    y: SETGT_DX10 ____, R127.z, 0.0f  
    z: ADD   ____, -R127.z, 1.0f  
    w: SETGT_DX10 ____, R125.x, 0.0f  
    t: SETGT_DX10 ____, 1.0f, R127.z  
9 x: CNDE_INT R127.x, PV8.y, 0.0f, PS8  
    y: CNDE_INT R123.y, R125.w, PV8.z, R127.z  
    z: CNDE_INT R123.z, PV8.w, 0.0f, 1065353216  
    t: MOV   R1.z, R127.z  
10 x: MOV*2  ____, PV9.y  
    w: MUL   ____, PV9.z, R126.x  
11 z: MUL_e  R127.z, PV10.w, (0x3E2AAAAB, 0.1666666716f).x  
    t: RCP_e  ____, PV10.x  
12 x: ADD   ____, PV11.z, 1.0f  
    y: SETGT_DX10 ____, 0.0f, PV11.z  
    z: MUL_e  ____, R125.x, PS11  
13 x: CNDE_INT R1.x, PV12.y, R127.z, PV12.x  
    y: CNDE_INT R1.y, R127.x, 0.0f, PV12.z 

Chú ý rằng không có hướng dẫn phân nhánh. Đó là di chuyển có điều kiện tất cả các cách, khá nhiều chính xác như tôi đã viết chúng.

Phần cứng cần thiết cho di chuyển có điều kiện chỉ là một bộ so sánh nhị phân (5 cửa mỗi bit) và một loạt các dấu vết. Rất nhanh.

Một điều thú vị khác cần lưu ý là không có dải phân cách. Thay vào đó trình biên dịch sử dụng một đối ứng gần đúng và một lệnh nhân. Nó làm điều này cho các hoạt động sqrt cũng như rất nhiều thời gian. Bạn có thể kéo các thủ thuật tương tự trên một CPU với (ví dụ) các lệnh rcpps SSE và rsqrtps.

Bây giờ hoạt động ngược lại:

// HSL [0:1] to RGB [0:1] 
vec4 convertHSLtoRGB(vec4 col) 
{ 
    const float onethird = 1.0/3.0; 
    const float twothird = 2.0/3.0; 
    const float rcpsixth = 6.0; 

    float hue = col.x; 
    float sat = col.y; 
    float lum = col.z; 

    vec3 xt = vec3(
     rcpsixth * (hue - twothird), 
     0.0, 
     rcpsixth * (1.0 - hue) 
    ); 

    if (hue < twothird) { 
     xt.r = 0.0; 
     xt.g = rcpsixth * (twothird - hue); 
     xt.b = rcpsixth * (hue  - onethird); 
    } 

    if (hue < onethird) { 
     xt.r = rcpsixth * (onethird - hue); 
     xt.g = rcpsixth * hue; 
     xt.b = 0.0; 
    } 

    xt = min(xt, 1.0); 

    float sat2 = 2.0 * sat; 
    float satinv = 1.0 - sat; 
    float luminv = 1.0 - lum; 
    float lum2m1 = (2.0 * lum) - 1.0; 
    vec3 ct  = (sat2 * xt) + satinv; 

    vec3 rgb; 
    if (lum >= 0.5) 
     rgb = (luminv * ct) + lum2m1; 
    else rgb = lum * ct; 

    return vec4(rgb, col.a); 
} 

(sửa 05/July/2013:. Tôi đã phạm sai lầm khi dịch chức năng này orignally Việc lắp ráp cũng đã được cập nhật).

đầu ra hội:

1 x: ADD   ____, -R2.x, 1.0f  
    y: ADD   ____, R2.x, (0xBF2AAAAB, -0.6666666865f).x  
    z: ADD   R0.z, -R2.x, (0x3F2AAAAB, 0.6666666865f).y  
    w: ADD   R0.w, R2.x, (0xBEAAAAAB, -0.3333333433f).z  
2 x: SETGT_DX10 R0.x, (0x3F2AAAAB, 0.6666666865f).x, R2.x  
    y: MUL   R0.y, PV2.x, (0x40C00000, 6.0f).y  
    z: MOV   R1.z, 0.0f  
    w: MUL   R1.w, PV2.y, (0x40C00000, 6.0f).y  
3 x: MUL   ____, R0.w, (0x40C00000, 6.0f).x  
    y: MUL   ____, R0.z, (0x40C00000, 6.0f).x  
    z: ADD   R0.z, -R2.x, (0x3EAAAAAB, 0.3333333433f).y  
    w: MOV   ____, 0.0f  
4 x: CNDE_INT R0.x, R0.x, R0.y, PV4.x  
    y: CNDE_INT R0.y, R0.x, R1.z, PV4.y  
    z: CNDE_INT R1.z, R0.x, R1.w, PV4.w  
    w: SETGT_DX10 R1.w, (0x3EAAAAAB, 0.3333333433f).x, R2.x  
5 x: MUL   ____, R2.x, (0x40C00000, 6.0f).x  
    y: MUL   ____, R0.z, (0x40C00000, 6.0f).x  
    z: ADD   R0.z, -R2.y, 1.0f  
    w: MOV   ____, 0.0f  
6 x: CNDE_INT R127.x, R1.w, R0.x, PV6.w  
    y: CNDE_INT R127.y, R1.w, R0.y, PV6.x  
    z: CNDE_INT R127.z, R1.w, R1.z, PV6.y  
    w: ADD   R1.w, -R2.z, 1.0f  
7 x: MULADD  R0.x, R2.z, (0x40000000, 2.0f).x, -1.0f  
    y: MIN*2  ____, PV7.x, 1.0f  
    z: MIN*2  ____, PV7.y, 1.0f  
    w: MIN*2  ____, PV7.z, 1.0f  
8 x: MULADD  R1.x, PV8.z, R2.y, R0.z  
    y: MULADD  R127.y, PV8.w, R2.y, R0.z  
    z: SETGE_DX10 R1.z, R2.z,   0.5  
    w: MULADD  R0.w, PV8.y, R2.y, R0.z  
9 x: MULADD  R0.x, R1.w, PV9.x, R0.x  
    y: MULADD  R0.y, R1.w, PV9.y, R0.x  
    z: MUL   R0.z, R2.z, PV9.y  
    w: MULADD  R1.w, R1.w, PV9.w, R0.x  
10 x: MUL   ____, R2.z, R0.w  
    y: MUL   ____, R2.z, R1.x  
    w: MOV   R2.w, R2.w  
11 x: CNDE_INT R2.x, R1.z, R0.z, R0.y  
    y: CNDE_INT R2.y, R1.z, PV11.y, R0.x  
    z: CNDE_INT R2.z, R1.z, PV11.x, R1.w 

Một lần nữa không có chi nhánh. Yum!

+0

Tôi ước gì tôi có thể nâng đỡ bạn nhiều hơn. Đây là một câu trả lời tuyệt vời. Bao gồm cả mã lắp ráp shader là một ý tưởng tuyệt vời, chúng ta nên luôn luôn kiểm tra nó. Làm cho tôi cảm thấy tội lỗi vì không làm điều đó thường xuyên hơn. Bây giờ tôi mơ về một trình soạn thảo biên dịch nguồn đổ bóng trong nền và hiển thị "sống" đầu ra lắp ráp trong một cửa sổ riêng biệt! :) – wil

+1

mã này có lỗi ở đâu đó khi giảm L.Tôi đã sử dụng: \t 'vec4 color = convertRGBtoHSL (texture2D (bản đồ, vUv)); \t color.x = color.x + hue; color.x = color.x - floor (color.x); \t color.y = color.y + saturation; color.y = kẹp (color.y, 0.0, 1.0); \t color.z = color.z + lightness; color.z = kẹp (color.z, 0.0, 1.0); \t gl_FragColor = convertHSLtoRGB (màu); ' và nó hoạt động ở mọi nơi trừ khi nhẹ <0 – makc

+0

thực sự ... Tôi chỉ thay thế mã với mã khác và lỗi vẫn còn đó ... có lẽ HSL không không gian tôi đang tìm kiếm? – makc

Các vấn đề liên quan