2011-01-18 3 views
8

사용자가 색조, 채도 및 밝기 슬라이더를 사용하여 이미지를 수정할 수 있어야하는 애플리케이션이 있습니다. 모든 이미지 처리는 GLSL 조각 셰이더를 사용하여 GPU에서 수행됩니다.GPL의 HSL 이미지 조정

제 문제는 RGB -> HSL -> RGB 변환은 광범위한 분기 때문에 GPP에서 다소 비싸다는 것입니다.

제 질문은 사용자 "색 조정"을 GPU에서 조정 된 이미지를보다 효율적으로 계산할 수있는 다른 색 공간으로 변환 할 수 있는지 여부입니다.

답변

8

밝기 및 채도를 입력하면 YUV(actually YCbCr)을 사용할 수 있습니다. RGB에서 변환하기 쉽습니다. 분기가 필요하지 않습니다. 채도는 Cr과 Cb를 모두 늘리거나 줄임으로써 제어됩니다. 밝기는 Y입니다.

Cb 및 Cr 구성 요소를 회전하여 (사실 3D 벡터입니다) HSL 색조 수정과 비슷한 것을 얻을 수 있지만, 물론 충분하다면 응용 프로그램에 따라 다릅니다.

alt text

편집 : 색성분의 색차 (Cb, Cr)가 상기와 같은 컬러 평면 내의 지점이다. 무작위로 점을 찍고 중심 주위로 돌리면 색조가 바뀝니다. 그러나 메커니즘이 HSL과 약간 다르므로 결과가 정확히 동일하지 않습니다.

는 Wikipedia의 퍼블릭 도메인입니다.

+0

위대한 답변, 그러나 나는 여전히 색상 변경과 관련하여 "유사한"비슷한 방법을 확신하지 못합니다. – ronag

+1

색조 문제에 대한 설명이 추가되었습니다. – Virne

1

3D 변환 테이블을 사용하여 색상 변환을 저장할 수 있습니다.이 테이블은 사용자 변수별로 업데이트되지만보다 간단한 승인이있을 수 있습니다.

자세한 내용은 GPU Gems 2입니다.

1

나는 RGB와 HSV/HSL 간의 변환이 전혀 분기하지 않고 코딩 될 수 있다고 믿는다. 예를 들어, 어떻게 변환 RGB의 경우 -> GLSL에서 볼 수 분기없이 HSV :

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

을하지만이 솔루션을 벤치마킹 적이 없다. 여기서 분기하지 않고 얻은 성능 향상은 중복 코드 실행의 성능 손실로 인해 보상 될 수 있습니다. 그래서 광범위한 테스트가 필요합니다 ...

+0

그것은 나를 위해 작동하지 않으며 초기화되지 않은 res.x에 대해 불평합니다. – kaoD

+0

'res '의 초기화가 수정되었습니다. 당신이 말하는 것이 정확히 무슨 뜻입니까? 작동하지 않습니다. –

+0

전체 이미지가 빨간색으로 착색되고 색조에 사인파를 설정하면 작동하지 않습니다. 다른 구현은 정상적으로 실행되었지만 특별히이 효율성에 관심이있었습니다. 이 문제의 원인은 무엇일까요? 디버깅을 돕기 위해 할 수있는 일은 무엇입니까? – kaoD

3

나는 같은 질문을했지만, 내 요구에 맞는 매우 간단한 해결책을 찾았습니다. 아마 당신에게도 유용 할 것입니다. 색상의 채도는 기본적으로 퍼져 있습니다. RGB 값과 평균 사이의 유클리드 거리라고 생각합니다. 관계없이 RGB 값의 최대 값과 최소값의 평균을 취하고 해당 피벗을 기준으로 색상의 크기를 조정하면 효과는 채도가 매우 적절하게 증가 (또는 감소)됩니다.

은 GLSL 쉐이더에서 당신은 작성합니다

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

IT는 GPU에 분기 및 코드분기 것은 똑같은 것을 가정하는 실수입니다.

간단한 조건의 경우 분기가 전혀 발생하지 않습니다. GPU에는 조건부 이동 명령이있어 3 진 표현식과 간단한 if-else 문으로 직접 변환됩니다.

중첩 된 조건부 또는 여러 조건부 종속 작업이있는 경우 문제가 발생합니다. 그런 다음 GLSL 컴파일러가 모든 것을 cmoves로 변환 할만큼 똑똑한 지 여부를 고려해야합니다. 가능할 때마다 컴파일러는 모든 분기를 실행하고 결과를 조건부 이동과 재결합하는 코드를 방출하지만 항상 그렇게 할 수는 없습니다.

도움이 필요할 때가 있습니다. 측정 할 수있는 때를 절대 추측하지 마십시오. AMD의 GPU 쉐이더 분석기 또는 엔비디아의 GCG를 사용하여 어셈블리 출력을 봅니다. GPU의 명령어 세트는 매우 제한적이며 단순하므로 '어셈블리'라는 단어를 두려워하지 마십시오.

다음은 어셈블리 출력과 함께 AMD의 GLSL 컴파일러와 잘 어울리게 변경 한 RGB/HSL 변환 함수입니다. Paul Bourke는 원래의 C 전환 코드를 신용했습니다. 이 기능에 대한

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

조립 출력 :

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 

주의 (非) 분화의 지시가 없음. 조건부 동작은 내가 작성한 것과 거의 똑같습니다.

조건부 이동에 필요한 하드웨어는 이진 비교기 (비트 당 5 게이트)와 추적 모음입니다. 매우 빠릅니다.

재미있는 점은 나누기가 없다는 것입니다. 대신 컴파일러는 대략적인 역수 및 곱셈 명령을 사용했습니다. 그것은 많은 시간뿐만 아니라 sqrt 작업을 위해 이것을 수행합니다. SSE rcpps 및 rsqrtps 명령과 함께 동일한 트릭을 CPU에서 가져올 수 있습니다.

지금은 역 동작 :

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

(편집/2013 년 7 월/05 :. orignally이 기능을 번역 할 때 내가 실수를 조립도 업데이트되었습니다).

조립 출력 :

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 
다시

없는 지점. 얌!

+0

나는 더 너를 upvote 수 있으면 좋겠다. 이것은 훌륭한 대답입니다. 셰이더 어셈블리 코드를 포함시키는 것이 훌륭한 아이디어이며 항상 검사해야합니다. 더 자주하지 않는 것에 대해 죄책감을 느끼게합니다. 이제는 백그라운드에서 쉐이더 소스를 컴파일하는 에디터를 꿈꾸며 어셈블리 출력을 별도의 창에 "라이브"로 표시합니다! :) – wil

+1

이 코드는 L을 줄이면 어딘가에 버그가 있습니다.나는 사용했다 : \t'vec4 color = convertRGBtoHSL (texture2D (map, vUv)); \t color.x = color.x + hue; color.x = color.x - floor (color.x); \t color.y = color.y + 채도; color.y = clamp (color.y, 0.0, 1.0); \t color.z = color.z + 밝기; color.z = 클램프 (color.z, 0.0, 1.0); \t gl_FragColor = convertHSLtoRGB (색상); 밝기가 0보다 작은 경우를 제외하고는 어디에서나 작동합니다. – makc

+0

... 실제로 코드를 다른 것으로 바꿨습니다. 버그가 여전히 있습니다 ... 아마도 HSL이 아닙니다. 내가 찾고있는 공간? – makc