GPU上的HSL图像调整

时间:2011-01-18 20:17:22

标签: c++ c image-processing glsl shader

我有一个应用程序,用户应该可以使用滑块修改图像的色调,饱和度和亮度。所有图像处理都是使用GLSL片段着色器在GPU上完成的。

我的问题是RGB - > HSL - >由于广泛的分支,RGB转换在gpu上相当昂贵。

我的问题是我是否可以将用户“颜色调整”转换为其他颜色空间,以便更有效地计算GPU上调整后的图像。

5 个答案:

答案 0 :(得分:12)

假设在GPU中分支 并在代码中分支是相同的事情是错误的。

对于简单的条件,根本就没有任何分支。 GPU具有条件移动指令,可直接转换为三元表达式和简单的if-else语句。

当问题出现时,您有嵌套条件或多个条件相关的操作。然后你必须考虑GLSL编译器是否足够智能将它全部转换为cmoves。只要有可能,编译器将发出执行所有分支的代码并使用条件移动重新组合结果,但它不能总是这样做。

你必须知道何时提供帮助。永远不要猜测何时可以测量 - 使用AMD的GPU着色器分析器或Nvidia的GCG来查看组件输出。 GPU的指令集非常有限且简单,所以不要害怕“汇编”这个词。

这是一对RGB / HSL转换函数,我已经改变了它们,因此它们与AMD的GLSL编译器以及汇编输出很好地配合。感谢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月5日编辑:我在翻译这个函数时犯了错误。组装也已更新。)

装配输出:

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  

再次没有分支机构。百胜

答案 1 :(得分:9)

对于亮度和饱和度,您可以使用YUV (actually YCbCr)。它很容易从RGB转换回来。不需要分支。通过增加或减少Cr和Cb来控制饱和度。亮度是Y.

通过旋转Cb和Cr组件(它实际上是一个3D矢量),您可以获得类似于HSL色调修改的东西,但当然这取决于您的应用程序是否足够。

alt text

编辑:颜色分量(Cb,Cr)是如上所述的颜色平面中的点。如果你采取任何随机点并围绕中心旋转,结果是色调变化。但由于机制与HSL略有不同,结果并不完全相同。

Image是维基百科的公共领域。

答案 2 :(得分:4)

我有同样的问题,但我找到了一个非常简单的解决方案,可以满足我的需求,也许对你也很有用。颜色的饱和度基本上是它的扩散,我相信这是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 );

答案 3 :(得分:1)

您可以使用3D查找表来存储颜色转换,表格将由用户变量更新,但可能会有更简单的方法。

GPU Gems 2中提供了更多信息。

答案 4 :(得分:1)

我相信RGB和HSV / HSL之间的转换可以在没有分支的情况下进行编码。例如,如何转换RGB - &gt;没有分支的HSV可以查看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;
}

但我没有对此解决方案进行基准测试。我们在没有分支的情况下获得的一些性能增益可以通过冗余代码执行的性能损失来补偿。因此需要进行广泛的测试......