Tag DirectX下的博客主要用于记录DirectX的学习过程,主要参考《DirectX 12 3D 游戏实战开发》。
计算机色彩基础
颜色的表示
人眼对红、绿、蓝三色光最为敏感,而绝大多数颜色都可以通过这三种颜色按不同比例混合产生,同时,绝大多数单色光也可以分解为红、绿、蓝三色光,这就是色度学中光的三原色原理(为了与色彩三原色作区分,通常把红、绿、蓝称为三基色)。
色彩的全息显示,即在显示器中显示色彩,就是基于三基色原理。基于这个原理,可以得出:
- 自然界的任何光色都可以由3中基色按比例混合(blending)而成。
- 三基色是相互独立的,任一基色都无法用其余两种光色表示。
- 混合所得的光色的饱和度(saturation)由3中光色的比例决定,混合色的亮度(brightness)为参与混合的光色的亮度之和。
既然光可以表示为三基色的混合,那么光就可以由一个三维向量(r,g,b)量化,其中r、g、b分别为红绿蓝三种光色的强度。为了便于描述光的强度,我们通常把光量化为区间[0,1]中的值,0为无强度,1为最大强度。以此为基础的色彩表示法也称为颜色的RGB模式,这个模式是对于计算机来说最为直观的模式(对人来说,HSV模式比较直观)。
光色的基本混合
光色的混合即两种光色通过混合得到新的光色。(下图为最大强度三基色等比混合所得的混合图)
在光的混合理论中:
- 红色+绿色=黄色;红色+蓝色=品红;绿色+蓝色=青色;红色+绿色+蓝色=白色
- 在色环中,过色环圆心的直线与色环相交得到的两种颜色为互补色
光色的混合在现实中是直观的,但在计算机中由于经过了量化,我们就必须对量化的数据定义混合的表示方法。光色的混合可以通过向量运算实现。在颜色运算过程中可能会超出区间,通常在可能产生风险的时候我们需要进行钳制(clamp)操作,所谓的钳制即把值大于1.0分量置为1.0,把值小于0.0的分量置为0.0。
相加混合
\[(0.0,0.5,0.0)+(0.0,0.0,0.25)=(0.0,0.5,0.25)\]上式的含义是:把中等强度的绿色和低强度的蓝色混合会得到深绿色。相加混合通常用于RGB模式中。
相减混合
\[(1.0,1.0,1.0)-(1.0,1.0,0.0)=(0.0,0.0,1.0)\]上式的含义是:从白色中减去红色和绿色的部分会得到蓝色。相减混合通常用于CMYK模式中。
标量运算
\[0.5\times (1.0,1.0,1.0)=(0.5,0.5,0.5)\]上式的含义是:把白色的各个分量都取一半会得到中等强度的灰色。
分量式(modulation)
分量式是颜色向量专属的向量运算法则,定义为: \((r_1,g_1,b_1)\bigotimes(r_2,g_2,b_2)=(r_1r_2,g_1g_2,b_1b_2)\) 分量式主要用于光照计算中。上式可以这样理解:假设有颜色为$(r_1,g_1,b_1)$的入射光线,照射到一个反射$r_2\times 100\%$红色光、$g_2\times 100\%$绿色光、$b_2\times 100\%$蓝色光且吸收剩余光的表面上,那么计算的结果为反射光线的颜色。
透明度
事实上,我们通常所使用的颜色向量多为四维向量,这并不是单纯为了发挥SIMD加速,而是因为颜色的第四维分量是用来表示一个在混合计算中非常重要的值:不透明度(opacity)。这个分量通常称为alpha分量,它的值也是位于区间[0,1]内。
DirectX中的颜色及相关函数
由于颜色也是一个四维向量,所以在DirectX中也可以使用DirectXMath库提供的XMVECTOR
类型表示颜色。
分量式运算
XMVECTOR XM_CALLCONV XMColorModulate(FXMVECTOR C1, FXMVECTOR C2);
32位色
在DirectX中,颜色的数据类型通常有两种大小:32位色和128位色。其中,32位色的定义如下:
namespace DirectX {
namespace PackedVector {
struct XMCOLOR {
union {
struct {
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
};
uint32_t c;
};
XMCOLOR() {}
XMCOLOR(uint32_t Color) : c(Color) {}
XMCOLOR(float _r, float _g, float _b, float _a);
explicit XMCOLOR(_In_reads_(4) const float *pArray);
operator uint32_t () const { return c; }
XMCOLOR& operator=(const XMCOLOR& Color) { c = Color.c; return *this; }
XMCOLOR& operator=(const uint32_t Color) { c = Color; return *this; }
};
}
}
可以看到,DirectXMath库把32位色存放到一个unsigned int
中,位按由高到低分别表示颜色的a、r、g、b分量,每个分量都用一个8位的无符号数表示,即强度区间为整数区间[0,255]。
128位色
128位色是最直观的色彩记录方式,即每个分量都用一个float
表示。
位色转换
通过把整数区间[0,255]映射到实数区间[0,1]就可以把32位色转换为128位色。具体做法很简单:把每个分量都除以255即可。
由于128位色的颜色精度非常高,高精度的优势在于减少计算误差,因此其通常用于像素着色器中进行像素颜色的计算。但目前的显示设备还不足以体现高精度色彩的优势,因此在计算完成后,存储的计算结果往往是32位色。即:颜色的加载和存储实际上是存储结构和位色的转换。为此,库提供了相应的加载/存储(或称位色转换)函数:
XMVECTOR XM_CALLCONV PackedVector::XMLoadColor(const XMCOLOR* pSource);
void XM_CALLCONV PackedVector::XMStoreColor(XMCOLOR* pDestination, FXMVECTOR V);