@@ -1184,6 +1184,88 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
1184
1184
mode = ImageBufAlgo::MakeTxTexture;
1185
1185
src = bumpslopes;
1186
1186
}
1187
+
1188
+ if (configspec.get_int_attribute (" maketx:cdf" )) {
1189
+ // Writes Gaussian CDF and Inverse Gaussian CDF as per-channel
1190
+ // metadata. We provide both the inverse transfrom and forward
1191
+ // transfrom, so in theory we're free to change the distribution.
1192
+ //
1193
+ // References:
1194
+ //
1195
+ // Brent Burley, On Histogram-Preserving Blending for Randomized
1196
+ // Texture Tiling, Journal of Computer Graphics Techniques (JCGT),
1197
+ // vol. 8, no. 4, 31-53, 2019
1198
+ //
1199
+ // Eric Heitz and Fabrice Neyret, High-Performance By-Example Noise
1200
+ // using a Histogram-Preserving Blending Operator,
1201
+ // https://hal.inria.fr/hal-01824773}, Proceedings of the ACM on
1202
+ // Computer Graphics and Interactive Techniques, ACM SIGGRAPH /
1203
+ // Eurographics Symposium on High-Performance Graphics 2018,
1204
+ //
1205
+ // Benedikt Bitterli
1206
+ // https://benedikt-bitterli.me/histogram-tiling/
1207
+
1208
+ const float cdf_sigma = configspec.get_float_attribute (
1209
+ " maketx:cdfsigma" );
1210
+ const int cdf_bits = configspec.get_int_attribute (" maketx:cdfbits" );
1211
+ const uint64_t bins = 1 << cdf_bits;
1212
+
1213
+ // Normalization coefficient for the truncated normal distribution
1214
+ const float c_sigma_inv = fast_erf (1 .0f / (2 .0f * M_SQRT2 * cdf_sigma));
1215
+
1216
+ // If there are channels other than R,G,B,A, we probably shouldn't do
1217
+ // anything to them.
1218
+ const int channels = std::min (4 , src->spec ().nchannels );
1219
+
1220
+ std::vector<float > invCDF (bins);
1221
+ std::vector<float > CDF (bins);
1222
+ std::vector<imagesize_t > hist;
1223
+
1224
+ for (int i = 0 ; i < channels; i++) {
1225
+ hist = ImageBufAlgo::histogram (*src, i, bins, 0 .0f , 1 .0f );
1226
+
1227
+ // Turn the histogram into a non-normalized CDF
1228
+ for (uint64_t j = 1 ; j < bins; j++) {
1229
+ hist[j] += hist[j - 1 ];
1230
+ }
1231
+
1232
+ // Store the inverse CDF as a lookup-table which we'll use to
1233
+ // transform the image data to a Guassian distribution. As
1234
+ // mentioned in Burley [2019] we're combining two steps here when
1235
+ // using the invCDF lookup table: we first "look up" the image
1236
+ // value through its CDF (the normalized histogram) which gives us
1237
+ // a uniformly distributed value, which we're then feeding in to
1238
+ // the Gaussian inverse CDF to transfrom the unifrom distribution
1239
+ // to Gaussian.
1240
+ for (uint64_t j = 0 ; j < bins; j++) {
1241
+ float u = float (hist[j]) / hist[bins - 1 ];
1242
+ float g = 0 .5f
1243
+ + cdf_sigma * M_SQRT2
1244
+ * fast_ierf (c_sigma_inv * (2 .0f * u - 1 .0f ));
1245
+ invCDF[j] = std::min (1 .0f , std::max (0 .0f , g));
1246
+ }
1247
+ configspec.attribute (" invCDF_" + std::to_string (i),
1248
+ TypeDesc (TypeDesc::FLOAT, bins),
1249
+ invCDF.data ());
1250
+
1251
+ // Store the forward CDF as a lookup table to transform back to
1252
+ // the original image distribution from a Gaussian distribution.
1253
+ for (uint64_t j = 0 ; j < bins; j++) {
1254
+ auto upper = std::upper_bound (invCDF.begin (), invCDF.end (),
1255
+ float (j) / (float (bins - 1 )));
1256
+ CDF[j] = clamp (float (upper - invCDF.begin ()) / float (bins - 1 ),
1257
+ 0 .0f , 1 .0f );
1258
+ }
1259
+
1260
+ configspec.attribute (" CDF_" + std::to_string (i),
1261
+ TypeDesc (TypeDesc::FLOAT, bins), CDF.data ());
1262
+ }
1263
+
1264
+ configspec[" CDF_bits" ] = cdf_bits;
1265
+
1266
+ mode = ImageBufAlgo::MakeTxTexture;
1267
+ }
1268
+
1187
1269
double misc_time_2 = alltime.lap ();
1188
1270
STATUS (" misc2" , misc_time_2);
1189
1271
0 commit comments