GPU上的HSL图像调整

ron*_*nag 8 c c++ shader image-processing glsl

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

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

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

Spa*_*ial 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 );
}
Run Code Online (Sandbox Code Playgroud)

此功能的汇编输出:

 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  
Run Code Online (Sandbox Code Playgroud)

请注意,没有分支指令.它是有条件的一直移动,几乎和我写的一样.

条件移动所需的硬件只是一个二进制比较器(每位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 );
}
Run Code Online (Sandbox Code Playgroud)

(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  
Run Code Online (Sandbox Code Playgroud)

再没有分支机构.百胜!

  • 使用`color.y =(color.y + saturation)*(1.0 +亮度); color.y = clamp(color.y,0.0,1.0);`似乎是在抑制人工制品.奇怪的. (2认同)

Vir*_*rne 9

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

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

替代文字

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

图片来自维基百科的公共领域.


小智 5

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