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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
| void TextureGenerator::putColor(Grid::Index pos, const Vector& lightDirection, char* rgb) {
float lab[3];
float xyz[3];
float hue = heightToHue(grid_.getElevation(pos));
float light = positionToLightness(pos, lightDirection);
LCHtoLAB(light*100.0f, 50.0f, hue, lab);
LABtoXYZ(lab[0], lab[1], lab[2], xyz);
XYZtoSRGB(xyz[0], xyz[1], xyz[2], rgb);
}
float TextureGenerator::positionToLightness(const Grid::Index& pos, const Vector& light) {
// The vertex to shade
Vector v(grid_.getVertex(pos));
// We can not determine dx/dy on a global edge, so check for that
if (pos.x > 0 && pos.y > 0 &&
pos.x < (grid_.edge()-1) && pos.y < (grid_.edge()-1))
{
// The x/y slopes, their cross product will be the unnormalized normal.
Vector dx = (grid_.getVertex(Grid::Index(pos.x+1, pos.y)) - grid_.getVertex(Grid::Index(pos.x-1, pos.y)));
Vector dy = (grid_.getVertex(Grid::Index(pos.x, pos.y+1)) - grid_.getVertex(Grid::Index(pos.x, pos.y-1)));
// Dot product with normal gives cos of angle between light and normal
float shade = light % normalize(dy * dx);
// Light on the back of a polygon is not visible
if (shade < 0.0f) shade = 0.0f;
// Add some ambient light, and shade the remaining range
//float ambient = 0.05f;
//float maxlightness = 0.7f;
float ambient = 0.00f;
float maxlightness = 1.0f;
return ambient + (shade * (maxlightness - ambient));
} else {
// Neutral light on the edges
return 0.5f;
}
}
float TextureGenerator::heightToHue(float height) const {
// Hue is given as angle between 0-360 degrees.
const static float colormap[][2] = {
{0.0000f, 240.0f},
// {0.1042f, 163.0f},
{0.1042f, 130.0f},
{0.2083f, 118.0f},
{0.3125f, 95.0f},
{0.4167f, 72.0f},
{0.5208f, 54.0f},
{0.6250f, 42.0f},
{0.7292f, 30.0f},
{0.8333f, 19.0f},
{0.9375f, 7.0f},
{1.0000f, 0.0f}
};
// Scale height to the [0,1] range
float scaledHeight = height / (256.0f*16.0f);
if (scaledHeight > 1.0f) scaledHeight = 1.0f;
if (scaledHeight < 0.0f) scaledHeight = 0.0f;
float hue = 0.0f;
for (int i=0; i<sizeof(colormap)-1; i++) {
if (scaledHeight >= colormap[i][0] &&
scaledHeight <= colormap[i+1][0])
{
// perc is in [0,1]
float perc = (scaledHeight - colormap[i][0]) / (colormap[i+1][0] - colormap[i][0]);
hue = colormap[i][1] + perc*(colormap[i+1][1] - colormap[i][1]);
break;
}
}
return hue;
}
// Convert CIE LCH to CIE LAB (no loss of color)
// H (hue) is given in degrees, not radians
void TextureGenerator::LCHtoLAB(float L, float C, float H, float* lab) const {
lab[0] = L; // L = L
// a = +-C / sqrt(tan(H)^2 + 1)
float tan = std::tan(static_cast<float>(RAD(H)));
float tmp = 1 / std::sqrt(tan*tan+1);
if (H <= 90.0f || H >= 270.0f) {
lab[1] = C * tmp;
} else {
lab[1] = -C * tmp;
}
// b = +- sqrt(C^2 - a^2)
lab[2] = std::sqrt((C*C) - (lab[1]*lab[1]));
if (H > 180.0f) lab[2] = -lab[2];
}
// Convert CIE LAB to CIE XYZ (uses sRGB reference white D65, no loss of color)
void TextureGenerator::LABtoXYZ(float L, float A, float B, float* xyz) const {
const static Vector D65(0.9505f, 1.0f, 1.0891f);
const static float e = 216.0f/24389;
const static float k = 24389/27.0f;
// Calculate Y
float y;
if (L > k*e) {
float tmp = (L+16)/116.0f;
y = tmp * tmp * tmp;
} else {
y = L / k;
}
// Now we can calc Fy
float fy;
if (y > e) {
fy = (L+16)/116.0f;
} else {
fy = (k*y+16)/116.0f;
}
// Now we can calc fx and fz
float fx = (A/500.0f) + fy;
float fz = fy - (B/200.0f);
// and finally x and z
float x, z;
float fx3 = fx * fx * fx;
float fz3 = fz * fz * fz;
if (fx3 > e) {
x = fx3;
} else {
x = (116.0f*fx - 16) / k;
}
if (fz3 > e) {
z = fz3;
} else {
z = (116.0f*fz - 16) / k;
}
// Now scale to the reference white point (D65 for sRGB)
xyz[0] = x * D65.x;
xyz[1] = y * D65.y;
xyz[2] = z * D65.z;
}
// Convert CIE XYZ (with sRGB ref.white D65) to sRGB (losses some color information)
void TextureGenerator::XYZtoSRGB(float X, float Y, float Z, char* rgb) const {
const static PTRS::matx44<float> XYZtoSRGBMatrix(
3.24071f, -0.969258f, 0.0556352f, 0.0f,
-1.53726f, 1.87599f, -0.203996f, 0.0f,
-0.498571f, 0.0415557f, 1.05707f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
Vector srgbVec = Vector(X, Y, Z) * XYZtoSRGBMatrix;
rgb[0] = sRGBValue(srgbVec.x);
rgb[1] = sRGBValue(srgbVec.y);
rgb[2] = sRGBValue(srgbVec.z);
}
// Gamma correct linear srgb values to non-linear rgb
unsigned char TextureGenerator::sRGBValue(float linear) const {
const static float treshhold = 0.0031308f;
const static float exponent = 1.0f/2.4f;
float result;
// Compute non-linear sRGB
if (linear <= treshhold) {
result = 12.92f * linear;
} else {
result = 1.055f * std::pow(linear, exponent) - 0.055f;
}
// Clamp RGB to [0,1] range
if (result < 0.0f) result = 0.0f;
if (result > 1.0f) result = 1.0f;
// Scale to discrete byte range [0,255]
return static_cast<unsigned char>(255 * result);
} |