LightMapPacker.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. //
  21. #include <ThirdParty/STB/stb_rect_pack.h>
  22. #include <Atomic/IO/Log.h>
  23. #include <Atomic/IO/FileSystem.h>
  24. #include <Atomic/Resource/Image.h>
  25. #include "BakeMesh.h"
  26. #include "LightMapPacker.h"
  27. namespace AtomicGlow
  28. {
  29. LightMapPacker::LightMapPacker(Context* context) : Object(context)
  30. {
  31. }
  32. LightMapPacker::~LightMapPacker()
  33. {
  34. }
  35. void LightMapPacker::AddRadianceMap(RadianceMap* radianceMap)
  36. {
  37. if (!radianceMap)
  38. return;
  39. radMaps_.Push(SharedPtr<RadianceMap>(radianceMap));
  40. }
  41. bool LightMapPacker::TryAddRadianceMap(RadianceMap* radMap)
  42. {
  43. if (radMap->packed_)
  44. return false;
  45. const int numnodes = GlobalGlowSettings.lightmapSize_;
  46. SharedArrayPtr<unsigned char> nodes(new unsigned char[sizeof(stbrp_node) * numnodes]);
  47. SharedArrayPtr<unsigned char> rects(new unsigned char[sizeof(stbrp_rect) * (workingSet_.Size() + 1)]);
  48. stbrp_context rectContext;
  49. stbrp_init_target (&rectContext, numnodes, numnodes, (stbrp_node *) nodes.Get(), numnodes);
  50. stbrp_rect* rect = (stbrp_rect*) rects.Get();
  51. // add working set, we do this brute force for best results
  52. for (unsigned i = 0; i < workingSet_.Size(); i++)
  53. {
  54. RadianceMap* wmap = workingSet_[i];
  55. rect->id = (int) i;
  56. rect->w = wmap->GetWidth() + LIGHTMAP_PADDING * 2;
  57. rect->h = wmap->GetHeight() + LIGHTMAP_PADDING * 2;
  58. rect++;
  59. }
  60. rect->id = (int) workingSet_.Size();
  61. rect->w = radMap->GetWidth() + LIGHTMAP_PADDING * 2;
  62. rect->h = radMap->GetHeight() + LIGHTMAP_PADDING * 2;
  63. if (!stbrp_pack_rects (&rectContext, (stbrp_rect *)rects.Get(), workingSet_.Size() + 1))
  64. {
  65. return false;
  66. }
  67. return true;
  68. }
  69. void LightMapPacker::DilatedBlit(const Image* srcImage, Image* destImage, const IntRect& destRect)
  70. {
  71. for (int y = 0; y < srcImage->GetHeight(); y++)
  72. {
  73. for (int x = 0; x < srcImage->GetWidth(); x++)
  74. {
  75. destImage->SetPixelInt(destRect.left_ + x + LIGHTMAP_PADDING,
  76. destRect.top_ + y + LIGHTMAP_PADDING,
  77. srcImage->GetPixelInt(x, y));
  78. }
  79. }
  80. // dilate top and bottom
  81. for (int x = 0; x < srcImage->GetWidth(); x++)
  82. {
  83. for (int i = 0; i < LIGHTMAP_PADDING; i++)
  84. {
  85. destImage->SetPixelInt(destRect.left_ + x + LIGHTMAP_PADDING,
  86. destRect.top_ + i,
  87. srcImage->GetPixelInt(x, 0));
  88. destImage->SetPixelInt(destRect.left_ + x + LIGHTMAP_PADDING,
  89. destRect.bottom_ - i,
  90. srcImage->GetPixelInt(x, srcImage->GetHeight() - 1));
  91. }
  92. }
  93. // dilate left and right
  94. for (int y = 0; y < srcImage->GetHeight(); y++)
  95. {
  96. for (int i = 0; i < LIGHTMAP_PADDING; i++)
  97. {
  98. destImage->SetPixelInt(destRect.left_ + i,
  99. destRect.top_ + y + LIGHTMAP_PADDING,
  100. srcImage->GetPixelInt(0, y));
  101. destImage->SetPixelInt(destRect.right_ - i,
  102. destRect.top_ + y + LIGHTMAP_PADDING,
  103. srcImage->GetPixelInt(srcImage->GetWidth() - 1, y));
  104. }
  105. }
  106. // dilate corners
  107. for (int y = LIGHTMAP_PADDING - 1; y >= 0 ; y--)
  108. {
  109. for (int x = LIGHTMAP_PADDING - 1; x >= 0 ; x--)
  110. {
  111. // upper left
  112. unsigned pixel = destImage->GetPixelInt(destRect.left_ + x + 1, destRect.top_ + y + 1);
  113. //pixel = Color::BLUE.ToUInt();
  114. destImage->SetPixelInt(destRect.left_ + x, destRect.top_ + y, pixel);
  115. // upper right
  116. pixel = destImage->GetPixelInt(destRect.right_ - x - 1, destRect.top_ + y + 1);
  117. //pixel = Color::RED.ToUInt();
  118. destImage->SetPixelInt(destRect.right_ - x, destRect.top_ + y, pixel);
  119. // lower left
  120. pixel = destImage->GetPixelInt(destRect.left_ + x + 1, destRect.bottom_ - y - 1);
  121. //pixel = Color::GREEN.ToUInt();
  122. destImage->SetPixelInt(destRect.left_ + x, destRect.bottom_ - y, pixel);
  123. // lower right
  124. pixel = destImage->GetPixelInt(destRect.right_ - x - 1, destRect.bottom_ - y - 1);
  125. //pixel = Color::MAGENTA.ToUInt();
  126. destImage->SetPixelInt(destRect.right_ - x, destRect.bottom_ - y, pixel);
  127. }
  128. }
  129. }
  130. void LightMapPacker::EmitLightmap(unsigned lightMapID)
  131. {
  132. int width = GlobalGlowSettings.lightmapSize_;
  133. int height = GlobalGlowSettings.lightmapSize_;
  134. // see note in stbrp_init_target docs
  135. int numnodes = width;
  136. SharedArrayPtr<unsigned char> nodes(new unsigned char[sizeof(stbrp_node) * numnodes]);
  137. SharedArrayPtr<unsigned char> rects(new unsigned char[sizeof(stbrp_rect) * workingSet_.Size()]);
  138. stbrp_context rectContext;
  139. stbrp_init_target (&rectContext, width, height, (stbrp_node *) nodes.Get(), numnodes);
  140. stbrp_rect* rect = (stbrp_rect*) rects.Get();
  141. for (unsigned i = 0; i < workingSet_.Size(); i++)
  142. {
  143. RadianceMap* radMap = workingSet_[i];
  144. rect->id = (int) i;
  145. rect->w = radMap->GetWidth() + LIGHTMAP_PADDING * 2;
  146. rect->h = radMap->GetHeight() + LIGHTMAP_PADDING * 2;
  147. rect++;
  148. }
  149. if (!stbrp_pack_rects (&rectContext, (stbrp_rect *)rects.Get(), workingSet_.Size()))
  150. {
  151. ATOMIC_LOGERROR("SceneBaker::Light() - not all rects packed");
  152. return;
  153. }
  154. SharedPtr<Image> image(new Image(context_));
  155. image->SetSize(width, height, 3);
  156. image->Clear(Color::CYAN);
  157. rect = (stbrp_rect*) rects.Get();
  158. for (unsigned i = 0; i < workingSet_.Size(); i++)
  159. {
  160. RadianceMap* radMap = workingSet_[i];
  161. if (!rect->was_packed)
  162. {
  163. ATOMIC_LOGERROR("LightMapPacker::Light() - skipping unpacked lightmap");
  164. continue;
  165. }
  166. DilatedBlit(radMap->image_, image, IntRect(rect->x, rect->y, rect->x + rect->w - 1, rect->y + rect->h - 1));
  167. radMap->bakeMesh_->Pack(lightMapID, Vector4(float(radMap->image_->GetWidth())/float(width),
  168. float(radMap->image_->GetHeight())/float(height),
  169. float(rect->x + LIGHTMAP_PADDING)/float(width),
  170. float(rect->y + LIGHTMAP_PADDING)/float(height)));
  171. rect++;
  172. }
  173. // dilate left and right maximum extents
  174. for (int i = 0; i < height; i++)
  175. {
  176. image->SetPixelInt(width -1, i, image->GetPixelInt(0, i));
  177. }
  178. for (int i = 0; i < width; i++)
  179. {
  180. image->SetPixelInt(i, height - 1, image->GetPixelInt(i, 0));
  181. }
  182. SharedPtr<LightMap> lightmap(new LightMap(context_));
  183. lightMaps_.Push(lightmap);
  184. lightmap->SetID(lightMapID);
  185. lightmap->SetImage(image);
  186. workingSet_.Clear();
  187. }
  188. bool LightMapPacker::SaveLightmaps(const String &projectPath, const String &scenePath)
  189. {
  190. FileSystem* fileSystem = GetSubsystem<FileSystem>();
  191. for (unsigned i = 0; i < lightMaps_.Size(); i++)
  192. {
  193. LightMap* lightmap = lightMaps_[i];
  194. const char* format = GlobalGlowSettings.outputFormat_ == GLOW_OUTPUT_PNG ? "png" : "dds";
  195. // Note: 2 scenes with the same name in project will collide for lightmap storage
  196. // this shouldn't be a general issue, and will be addressed once lightmaps are processed
  197. // to Cache with GUID
  198. String sceneName = GetFileName(scenePath);
  199. String folder = ToString("%sResources/AtomicGlow/Scenes/%s/Lightmaps/", projectPath.CString(), sceneName.CString());
  200. if (!fileSystem->DirExists(folder))
  201. {
  202. fileSystem->CreateDirsRecursive(folder);
  203. }
  204. if (!fileSystem->DirExists(folder))
  205. {
  206. ATOMIC_LOGERRORF("LightMapPacker::SaveLightmaps - Unable to create folder: %s", folder.CString());
  207. return false;
  208. }
  209. String filename = ToString("%sLightmap%u.%s", folder.CString(), lightmap->GetID(), format);
  210. ATOMIC_LOGINFOF("Saving Lightmap: %s", filename.CString());
  211. GlobalGlowSettings.outputFormat_ == GLOW_OUTPUT_PNG ? lightmap->GetImage()->SavePNG(filename) : lightmap->GetImage()->SaveDDS(filename);
  212. }
  213. return true;
  214. }
  215. static bool CompareRadianceMap(RadianceMap* lhs, RadianceMap* rhs)
  216. {
  217. int lhsWeight = lhs->GetWidth();
  218. lhsWeight += lhs->GetHeight();
  219. int rhsWeight = rhs->GetWidth();
  220. rhsWeight += rhs->GetHeight();
  221. // sort from biggest to smallest
  222. return lhsWeight > rhsWeight;
  223. }
  224. void LightMapPacker::Pack()
  225. {
  226. unsigned lightmapID = 0;
  227. SharedPtr<LightMap> lightmap;
  228. Sort(radMaps_.Begin(), radMaps_.End(), CompareRadianceMap);
  229. for (unsigned i = 0; i < radMaps_.Size(); i++)
  230. {
  231. RadianceMap* radMap = radMaps_[i];
  232. if (radMap->packed_)
  233. continue;
  234. if (radMap->GetWidth() >= GlobalGlowSettings.lightmapSize_ || radMap->GetHeight() >= GlobalGlowSettings.lightmapSize_)
  235. {
  236. lightmap = new LightMap(context_);
  237. lightMaps_.Push(lightmap);
  238. lightmap->SetID(lightmapID);
  239. lightmap->SetImage(radMap->image_);
  240. radMap->bakeMesh_->Pack(lightmapID, Vector4(1.0f, 1.0f, 0.0f, 0.0f));
  241. lightmapID++;
  242. continue;
  243. }
  244. workingSet_.Push(radMap);
  245. for (unsigned j = 0; j < radMaps_.Size(); j++)
  246. {
  247. if (i == j)
  248. continue;
  249. RadianceMap* otherMap = radMaps_[j];
  250. if (TryAddRadianceMap(otherMap))
  251. {
  252. workingSet_.Push(otherMap);
  253. }
  254. }
  255. EmitLightmap(lightmapID++);
  256. workingSet_.Clear();
  257. }
  258. }
  259. }