数值到颜色的自然映射

很久没更新博客了。额,一直想写这个来着。记得以前写过绘制三维标量场的文章,方法是找的渐变颜色纹理图片,然后用一维纹理坐标映射贴上去的。后面发现根本不需要去找图片,这种渐变色本来就是一种颜色空间的定义。请看hsv颜色模型,这种颜色模型类似于rgb,也是三个分量h(色调),s(饱和度),v(亮度)。h表示的绕圆锥的一周,也就是纯颜色的渐变,正是我们需要的东西。s表示从圆心到圆边缘的半径,0-1的范围,表示颜色纯度。v则是垂直的轴,表示亮度。如下图,我们要的就是绕圈一周的颜色渐变。

总体思路就是数值0-1,映射到hsv颜色空间的(0-360,1,255)。实现标量场的方法是,首先,将hsv的(0-360,1,255)转换为rgb,保存为一维数组。以该数组为数据,生成一维纹理,数值0-1作为纹理坐标,再贴图即可生成标量场。下面是用该方法生成标量场的相关代码。首先是构造一维纹理数组,然后用其生成1d纹理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int CTexture::BuildColorMapTexture()
{
const int COLOR_SIZE = 240;//红到蓝
ImageRGBA colorMap(COLOR_SIZE, 1);

for (int i = 0; i < COLOR_SIZE; ++i)
{
colorMap.pixel(i, 0) = Hsv2Rgb(i, 1, 255);
}

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, m_uTexName);
glBindTexture(GL_TEXTURE_1D, m_uTexName);

glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

//生成2d的Mipmap纹理
int nRet = gluBuild1DMipmaps(GL_TEXTURE_1D, 4, COLOR_SIZE, GL_RGBA, GL_UNSIGNED_BYTE,
colorMap.raw());

if (nRet != 0)
{
return 0;
}

return 1;
}

hsv2rgb的代码如下,从网上可以随便找个修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
PixelRGBA Hsv2Rgb(float H, float S, float V)
{
float R, G, B;

int i;
float f, p, q, t;
if(S == 0)
{
// achromatic (grey)
R = G = B = V;
return V4B(R, G, B, 255);
}

H /= 60; // sector 0 to 5
i = floor( H );
f = H - i; // factorial part of h
p = V * ( 1 - S );
q = V * ( 1 - S * f );
t = V * ( 1 - S * ( 1 - f ) );

switch( i )
{
case 0:
R = V;
G = t;
B = p;
break;
case 1:
R = q;
G = V;
B = p;
break;
case 2:
R = p;
G = V;
B = t;
break;
case 3:
R = p;
G = q;
B = V;
break;
case 4:
R = t;
G = p;
B = V;
break;
default: // case 5:
R = V;
G = p;
B = q;
break;
}

return V4B(R, G, B, 255);
}

剩下的事情就是使用BuildColorMapTexture生成1d纹理,然后将数值作为1d纹理坐标设置好就行了。下面是相关的效果图:

颜色空间转换生成纹理:

渐变纹理贴图:

不同的方法效果有区别,原因可能是贴图的数据更精细,而且同一范围比如0-0.3对应的颜色就可能不一致。马上要毕业了,工作内容是游戏开发,以后估计不会再更新读研期间类似的文章的了。