Shader 中的颜色计算

6,598 阅读4分钟

下面介绍 Shader 中 gl_FragColor 的计算与转换:

一、颜色计算

1. 加

这里要讲讲三原色和三基色:三原色一般指的是红、绿、蓝三种,简称 RGB,这是加色系。就是光源只含有特定的波段,本身就是色光,将不同颜色的光加在一起形成新的颜色。典型的例子是显示屏,关系如下:

显然,shader 中颜色的加法运算符合加色系规律。当我们把颜色相加时,会形成新的颜色,并且颜色会往白色靠拢。颜色的混合规律符合三原色规律。

2. 乘

讲完三原色,再讲讲三基色:一般指的是颜料三原色,在纯白光照射下颜色为绛红、黄、青,简称 CMYK,属于减色系它们本身不发光,靠反光被看见。由于材料吸收特定波段的光,所以只有不被吸收的部分反射了回来。加上的颜色越多吸收的光也越多。

当我们使用乘法来做颜色混合时,其规律符合三基色的混合规律,这个时候又是减色系。

两个颜色相乘,会算出两个颜色中 RGB 值的乘积并合成一个新的颜色。而且颜色总会越来越暗,回不到原来的白色。通常将颜色和一个值相乘,来弱化这个颜色。

3. 减

单纯的颜色相减似乎没有意义,不过通过1.0-color可以实现颜色的反相。

4. 真正的颜色混合

在图片或视频滤镜中,一般不会直接使用加减乘除来做颜色混合。而是使用 mix() 函数,它的公式是:x*(1−a)+y*a,其实也是颜色相加,但是算上了一定的比重。这样不会因为一个白色的颜色和其他颜色相加后只有白色,现实世界中也不是这样的。

mix()可以做单通道或多通道的融合:

①. 简单的颜色渐变

回到上面的案例,通过加法来表示重叠区域:

通过 mix() 来混合两个颜色的过渡:

为什么加法和mix()得到的过渡颜色不一样?各位可以思考一下。

②. 复杂的颜色渐变

为 rgb 三个通道赋以不同的函数变化曲线。plot 是封装好的画线函数,以xy二维笛卡尔坐标系做曲线的绘制,pct 表示x轴的变化速率,当x是线性变化时,曲线为直线。当x是非线性变化时,会有不一样的曲线,从而导致渐变色的多样变化:

二、颜色转换

1. 基于笛卡尔坐标系

RGB 是对机器很友好的色彩模式,但并不够人性化,因为我们对色彩的认识往往是”什么颜色?鲜艳不鲜艳?亮还是暗?”。HSL 模式和 HSV(HSB) 都是基于 RGB 的,是作为一个更方便友好的方法创建出来的 —— refer

  • HSL 为 色相,饱和度,亮度
  • HSV 为色相,饱和度,明度
  • HSB 为 色相,饱和度,明度

下图表达了两种颜色模型对人类来说的易理解程度:

HSL 和 HSB/HSV 又有一些区别:

这里提供转换公式:

// RGB 转 HSB
vec3 rgb2hsb( in 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);
}

// HSB 转 RGB
//  Function from Iñigo Quiles
//  https://www.shadertoy.com/view/MsS3Wc
vec3 hsb2rgb( in vec3 c ){
    vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
                             6.0)-3.0)-1.0,
                     0.0,
                     1.0 );
    rgb = rgb*rgb*(3.0-2.0*rgb);
    return c.z * mix(vec3(1.0), rgb, c.y);
}

那我们可以怎么应用 HSB 颜色呢?

当我们让色相 Hue 从0~1 递增时,你会发现所有颜色都一一取到了(这里的饱和度和亮度都设置为 1):

假设让亮度也一样从0~1,看看效果会如何:

你会发现水平方向的亮度变化不好看,如果是垂直方向的呢?

再改一下垂直方向的饱和度,你会发现有了 HSB,一切颜色变化都更好理解了:

2. 极坐标系

HSB 原本是在极坐标下产生的(以半径和角度定义)而并非在笛卡尔坐标系(基于xy定义)下。将 HSB 映射到极坐标我们需要取得角度和到像素屏中点的距离。由此我们运用 length() 函数和 atan(y,x) 函数。

当用到矢量和三角学函数时,vec2, vec3 和 vec4 被当做向量对待,即使有时候他们代表颜色。我们开始把颜色和向量同等的对待,事实上你会慢慢发现这种理念的灵活性有着相当强大的用途。—— refer