🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
12 KiB

  1. #include "tinyexr.h"
  2. #define STB_IMAGE_IMPLEMENTATION
  3. #include "stb_image.h"
  4. #define STB_IMAGE_WRITE_IMPLEMENTATION
  5. #include "stb_image_write.h"
  6. #include <array>
  7. #include <cmath>
  8. #include <iostream>
  9. #include <string>
  10. #include <vector>
  11. // From Filament.
  12. static inline void RGBMtoLinear(const float rgbm[4], float linear[3]) {
  13. linear[0] = rgbm[0] * rgbm[3] * 16.0f;
  14. linear[1] = rgbm[1] * rgbm[3] * 16.0f;
  15. linear[2] = rgbm[2] * rgbm[3] * 16.0f;
  16. // Gamma to linear space
  17. linear[0] = linear[0] * linear[0];
  18. linear[1] = linear[1] * linear[1];
  19. linear[2] = linear[2] * linear[2];
  20. }
  21. static inline void LinearToRGBM(const float linear[3], float rgbm[4]) {
  22. rgbm[0] = linear[0];
  23. rgbm[1] = linear[1];
  24. rgbm[2] = linear[2];
  25. rgbm[3] = 1.0f;
  26. // Linear to gamma space
  27. rgbm[0] = rgbm[0] * rgbm[0];
  28. rgbm[1] = rgbm[1] * rgbm[1];
  29. rgbm[2] = rgbm[2] * rgbm[2];
  30. // Set the range
  31. rgbm[0] /= 16.0f;
  32. rgbm[1] /= 16.0f;
  33. rgbm[2] /= 16.0f;
  34. float maxComponent =
  35. std::max(std::max(rgbm[0], rgbm[1]), std::max(rgbm[2], 1e-6f));
  36. // Don't let M go below 1 in the [0..16] range
  37. rgbm[3] = std::max(1.0f / 16.0f, std::min(maxComponent, 1.0f));
  38. rgbm[3] = std::ceil(rgbm[3] * 255.0f) / 255.0f;
  39. // saturate([0.0, 1.0])
  40. rgbm[0] = std::max(0.0f, std::min(1.0f, rgbm[0] / rgbm[3]));
  41. rgbm[1] = std::max(0.0f, std::min(1.0f, rgbm[1] / rgbm[3]));
  42. rgbm[2] = std::max(0.0f, std::min(1.0f, rgbm[2] / rgbm[3]));
  43. }
  44. static std::string GetFileExtension(const std::string& filename) {
  45. if (filename.find_last_of(".") != std::string::npos)
  46. return filename.substr(filename.find_last_of(".") + 1);
  47. return "";
  48. }
  49. struct Image {
  50. int width;
  51. int height;
  52. std::vector<float> data;
  53. };
  54. static bool LoadCubemaps(const std::array<std::string, 6> face_filenames,
  55. std::array<Image, 6>* output) {
  56. for (size_t i = 0; i < 6; i++) {
  57. std::string ext = GetFileExtension(face_filenames[i]);
  58. Image image;
  59. if ((ext.compare("exr") == 0) || (ext.compare("EXR") == 0)) {
  60. int width, height;
  61. float* rgba;
  62. const char* err;
  63. int ret =
  64. LoadEXR(&rgba, &width, &height, face_filenames[i].c_str(), &err);
  65. if (ret != 0) {
  66. if (err) {
  67. std::cerr << "EXR load error: " << err << std::endl;
  68. } else {
  69. std::cerr << "EXR load error: code " << ret << std::endl;
  70. }
  71. return false;
  72. }
  73. image.width = width;
  74. image.height = height;
  75. image.data.resize(width * height * 3);
  76. // RGBA -> RGB
  77. for (size_t j = 0; j < size_t(width * height); j++) {
  78. image.data[3 * j + 0] = rgba[4 * j + 0];
  79. image.data[3 * j + 1] = rgba[4 * j + 1];
  80. image.data[3 * j + 2] = rgba[4 * j + 2];
  81. }
  82. free(rgba);
  83. (*output)[i] = std::move(image);
  84. } else if ((ext.compare("rgbm") == 0) || (ext.compare("RGBM") == 0)) {
  85. int width, height;
  86. int n;
  87. unsigned char* data = stbi_load(face_filenames[i].c_str(), &width,
  88. &height, &n, STBI_default);
  89. if (!data) {
  90. std::cerr << "Failed to load file: " << face_filenames[i] << std::endl;
  91. return false;
  92. }
  93. if ((n != 4)) {
  94. std::cerr << "Not a RGBM encoded image: " << face_filenames[i]
  95. << std::endl;
  96. return false;
  97. }
  98. image.width = width;
  99. image.height = height;
  100. image.data.resize(size_t(width * height));
  101. for (size_t i = 0; i < size_t(width * height); i++) {
  102. float rgbm[4];
  103. // [0, 1.0]
  104. rgbm[0] = data[4 * i + 0] / 255.0f;
  105. rgbm[1] = data[4 * i + 1] / 255.0f;
  106. rgbm[2] = data[4 * i + 2] / 255.0f;
  107. rgbm[3] = data[4 * i + 3] / 255.0f;
  108. float linear[3];
  109. RGBMtoLinear(rgbm, linear);
  110. image.data[3 * i + 0] = linear[0];
  111. image.data[3 * i + 1] = linear[1];
  112. image.data[3 * i + 2] = linear[2];
  113. }
  114. (*output)[i] = std::move(image);
  115. } else {
  116. std::cerr << "Unknown file extension : " << ext << std::endl;
  117. return false;
  118. }
  119. std::cout << "Loaded " << face_filenames[i] << std::endl;
  120. }
  121. return true;
  122. }
  123. void convert_xyz_to_cube_uv(float x, float y, float z, int* index, float* u,
  124. float* v) {
  125. float absX = fabs(x);
  126. float absY = fabs(y);
  127. float absZ = fabs(z);
  128. int isXPositive = x > 0.0f ? 1 : 0;
  129. int isYPositive = y > 0.0f ? 1 : 0;
  130. int isZPositive = z > 0.0f ? 1 : 0;
  131. float maxAxis, uc, vc;
  132. // POSITIVE X
  133. if (isXPositive && absX >= absY && absX >= absZ) {
  134. // u (0 to 1) goes from +z to -z
  135. // v (0 to 1) goes from -y to +y
  136. maxAxis = absX;
  137. uc = -z;
  138. vc = y;
  139. *index = 0;
  140. }
  141. // NEGATIVE X
  142. if (!isXPositive && absX >= absY && absX >= absZ) {
  143. // u (0 to 1) goes from -z to +z
  144. // v (0 to 1) goes from -y to +y
  145. maxAxis = absX;
  146. uc = z;
  147. vc = y;
  148. *index = 1;
  149. }
  150. // POSITIVE Y
  151. if (isYPositive && absY >= absX && absY >= absZ) {
  152. // u (0 to 1) goes from -x to +x
  153. // v (0 to 1) goes from +z to -z
  154. maxAxis = absY;
  155. uc = x;
  156. vc = -z;
  157. *index = 2;
  158. }
  159. // NEGATIVE Y
  160. if (!isYPositive && absY >= absX && absY >= absZ) {
  161. // u (0 to 1) goes from -x to +x
  162. // v (0 to 1) goes from -z to +z
  163. maxAxis = absY;
  164. uc = x;
  165. vc = z;
  166. *index = 3;
  167. }
  168. // POSITIVE Z
  169. if (isZPositive && (absZ >= absX) && (absZ >= absY)) {
  170. // u (0 to 1) goes from -x to +x
  171. // v (0 to 1) goes from -y to +y
  172. maxAxis = absZ;
  173. uc = x;
  174. vc = y;
  175. *index = 4;
  176. }
  177. // NEGATIVE Z
  178. if (!isZPositive && (absZ >= absX) && (absZ >= absY)) {
  179. // u (0 to 1) goes from +x to -x
  180. // v (0 to 1) goes from -y to +y
  181. maxAxis = absZ;
  182. uc = -x;
  183. vc = y;
  184. *index = 5;
  185. }
  186. // Convert range from -1 to 1 to 0 to 1
  187. *u = 0.5f * (uc / maxAxis + 1.0f);
  188. *v = 0.5f * (vc / maxAxis + 1.0f);
  189. }
  190. //
  191. // Simple bilinear texture filtering.
  192. //
  193. static void SampleTexture(float* rgba, float u, float v, int width, int height,
  194. int channels, const float* texels) {
  195. float sx = std::floor(u);
  196. float sy = std::floor(v);
  197. // Wrap mode = repeat
  198. float uu = u - sx;
  199. float vv = v - sy;
  200. // clamp
  201. uu = std::max(uu, 0.0f);
  202. uu = std::min(uu, 1.0f);
  203. vv = std::max(vv, 0.0f);
  204. vv = std::min(vv, 1.0f);
  205. float px = (width - 1) * uu;
  206. float py = (height - 1) * vv;
  207. int x0 = std::max(0, std::min((int)px, (width - 1)));
  208. int y0 = std::max(0, std::min((int)py, (height - 1)));
  209. int x1 = std::max(0, std::min((x0 + 1), (width - 1)));
  210. int y1 = std::max(0, std::min((y0 + 1), (height - 1)));
  211. float dx = px - (float)x0;
  212. float dy = py - (float)y0;
  213. float w[4];
  214. w[0] = (1.0f - dx) * (1.0 - dy);
  215. w[1] = (1.0f - dx) * (dy);
  216. w[2] = (dx) * (1.0 - dy);
  217. w[3] = (dx) * (dy);
  218. int i00 = channels * (y0 * width + x0);
  219. int i01 = channels * (y0 * width + x1);
  220. int i10 = channels * (y1 * width + x0);
  221. int i11 = channels * (y1 * width + x1);
  222. for (int i = 0; i < channels; i++) {
  223. rgba[i] = w[0] * texels[i00 + i] + w[1] * texels[i10 + i] +
  224. w[2] * texels[i01 + i] + w[3] * texels[i11 + i];
  225. }
  226. }
  227. static void SampleCubemap(const std::array<Image, 6>& cubemap_faces,
  228. const float n[3], float col[3]) {
  229. int face;
  230. float u, v;
  231. convert_xyz_to_cube_uv(n[0], n[1], n[2], &face, &u, &v);
  232. v = 1.0f - v;
  233. // std::cout << "face = " << face << std::endl;
  234. // TODO(syoyo): Do we better consider seams on the cubemap face border?
  235. const Image& tex = cubemap_faces[face];
  236. // std::cout << "n = " << n[0] << ", " << n[1] << ", " << n[2] << ", uv = " <<
  237. // u << ", " << v << std::endl;
  238. SampleTexture(col, u, v, tex.width, tex.height, /* RGB */ 3, tex.data.data());
  239. // col[0] = u;
  240. // col[1] = v;
  241. // col[2] = 0.0f;
  242. #if 0
  243. if (face == 0) {
  244. col[0] = 1.0f;
  245. col[1] = 0.0f;
  246. col[2] = 0.0f;
  247. } else if (face == 1) {
  248. col[0] = 0.0f;
  249. col[1] = 1.0f;
  250. col[2] = 0.0f;
  251. } else if (face == 2) {
  252. col[0] = 0.0f;
  253. col[1] = 0.0f;
  254. col[2] = 1.0f;
  255. } else if (face == 3) {
  256. col[0] = 1.0f;
  257. col[1] = 0.0f;
  258. col[2] = 1.0f;
  259. } else if (face == 4) {
  260. col[0] = 0.0f;
  261. col[1] = 1.0f;
  262. col[2] = 1.0f;
  263. } else if (face == 5) {
  264. col[0] = 1.0f;
  265. col[1] = 1.0f;
  266. col[2] = 1.0f;
  267. }
  268. #endif
  269. }
  270. static void CubemapToLonglat(const std::array<Image, 6>& cubemap_faces,
  271. const float phi_offset, /* in angle */
  272. const int width, Image* longlat) {
  273. int height = width / 2;
  274. longlat->width = width;
  275. longlat->height = height;
  276. longlat->data.resize(size_t(width * height * 3)); // RGB
  277. const float kPI = 3.141592f;
  278. for (size_t y = 0; y < size_t(height); y++) {
  279. float theta = ((y + 0.5f) / float(height)) * kPI; // [0, pi]
  280. for (size_t x = 0; x < size_t(width); x++) {
  281. float phi = ((x + 0.5f) / float(width)) * 2.0f * kPI; // [0, 2 pi]
  282. phi += (phi_offset) * kPI / 180.0f;
  283. float n[3];
  284. // Y-up
  285. n[0] = std::sin(theta) * std::cos(phi);
  286. n[1] = std::cos(theta);
  287. n[2] = -std::sin(theta) * std::sin(phi);
  288. float col[3];
  289. SampleCubemap(cubemap_faces, n, col);
  290. longlat->data[3 * size_t(y * width + x) + 0] = col[0];
  291. longlat->data[3 * size_t(y * width + x) + 1] = col[1];
  292. longlat->data[3 * size_t(y * width + x) + 2] = col[2];
  293. }
  294. }
  295. }
  296. static unsigned char ftouc(const float f) {
  297. int i(f * 255.0f);
  298. i = std::max(0, std::min(255, i));
  299. return static_cast<unsigned char>(i);
  300. }
  301. int main(int argc, char** argv) {
  302. float phi_offset = 0.0f;
  303. if (argc < 9) {
  304. printf(
  305. "Usage: cube2longlat px.exr nx.exr py.exr ny.exr pz.exr nz.exr "
  306. "output_width output.exr\n");
  307. exit(-1);
  308. }
  309. std::array<std::string, 6> face_filenames;
  310. face_filenames[0] = argv[1];
  311. face_filenames[1] = argv[2];
  312. face_filenames[2] = argv[3];
  313. face_filenames[3] = argv[4];
  314. face_filenames[4] = argv[5];
  315. face_filenames[5] = argv[6];
  316. int output_width = atoi(argv[7]);
  317. std::string output_filename = argv[8];
  318. if (argc > 9) {
  319. phi_offset = atof(argv[9]);
  320. }
  321. std::array<Image, 6> cubemaps;
  322. if (!LoadCubemaps(face_filenames, &cubemaps)) {
  323. std::cerr << "Failed to load cubemap faces." << std::endl;
  324. return EXIT_FAILURE;
  325. }
  326. Image longlat;
  327. CubemapToLonglat(cubemaps, phi_offset, output_width, &longlat);
  328. {
  329. std::string ext = GetFileExtension(output_filename);
  330. if ((ext.compare("exr") == 0) || (ext.compare("EXR") == 0)) {
  331. const char *err;
  332. int ret = SaveEXR(longlat.data.data(), longlat.width, longlat.height,
  333. /* RGB */ 3, /* fp16 */ 0, output_filename.c_str(), &err);
  334. if (ret != TINYEXR_SUCCESS) {
  335. if (err) {
  336. std::cout << "Failed to save image as EXR. msg = " << err << ", code = " << ret << std::endl;
  337. FreeEXRErrorMessage(err);
  338. } else {
  339. std::cout << "Failed to save image as EXR. code = " << ret << std::endl;
  340. }
  341. return EXIT_FAILURE;
  342. }
  343. } else if ((ext.compare("rgbm") == 0) || (ext.compare("RGBM") == 0)) {
  344. std::vector<unsigned char> rgbm_image;
  345. for (size_t j = 0; j < size_t(longlat.width * longlat.height); j++) {
  346. float linear[3];
  347. linear[0] = longlat.data[3 * j + 0];
  348. linear[1] = longlat.data[3 * j + 1];
  349. linear[2] = longlat.data[3 * j + 2];
  350. float rgbm[4];
  351. LinearToRGBM(linear, rgbm);
  352. rgbm_image[4 * j + 0] = ftouc(rgbm[0]);
  353. rgbm_image[4 * j + 1] = ftouc(rgbm[1]);
  354. rgbm_image[4 * j + 2] = ftouc(rgbm[2]);
  355. rgbm_image[4 * j + 3] = ftouc(rgbm[2]);
  356. }
  357. // Save as PNG.
  358. int ret =
  359. stbi_write_png(output_filename.c_str(), longlat.width, longlat.height,
  360. 4, rgbm_image.data(), longlat.width * 4);
  361. if (ret == 0) {
  362. std::cerr << "Failed to save image as RGBM file : " << output_filename
  363. << std::endl;
  364. return EXIT_FAILURE;
  365. }
  366. } else {
  367. if ((ext.compare("hdr") == 0) || (ext.compare("HDR") == 0)) {
  368. // ok
  369. } else {
  370. std::cout << "Unknown file extension. Interpret it as RGBE format : "
  371. << ext << std::endl;
  372. }
  373. int ret = stbi_write_hdr(output_filename.c_str(), longlat.width,
  374. longlat.height, 3, longlat.data.data());
  375. if (ret == 0) {
  376. std::cerr << "Failed to save image as HDR file : " << output_filename
  377. << std::endl;
  378. return EXIT_FAILURE;
  379. }
  380. }
  381. }
  382. std::cout << "Write " << output_filename << std::endl;
  383. return 0;
  384. }