3
0

TerrainMacroMaterialManager.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <TerrainRenderer/TerrainMacroMaterialManager.h>
  9. #include <Atom/RPI.Public/View.h>
  10. namespace Terrain
  11. {
  12. namespace
  13. {
  14. [[maybe_unused]] static const char* TerrainMacroMaterialManagerName = "TerrainMacroMaterialManager";
  15. }
  16. namespace TerrainSrgInputs
  17. {
  18. static const char* const MacroMaterialData("m_macroMaterialData");
  19. static const char* const MacroMaterialGridRefs("m_macroMaterialGridRefs");
  20. }
  21. bool TerrainMacroMaterialManager::MacroMaterialShaderData::Overlaps(const AZ::Vector2& min, const AZ::Vector2& max) const
  22. {
  23. return AZ::Vector2::CreateFromFloat2(m_boundsMin.data()).IsLessThan(max) &&
  24. AZ::Vector2::CreateFromFloat2(m_boundsMax.data()).IsGreaterThan(min);
  25. }
  26. void TerrainMacroMaterialManager::Initialize(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
  27. {
  28. AZ_Error(TerrainMacroMaterialManagerName, terrainSrg, "terrainSrg must not be null.");
  29. AZ_Error(TerrainMacroMaterialManagerName, !m_isInitialized, "Already initialized.");
  30. if (!terrainSrg || m_isInitialized)
  31. {
  32. return;
  33. }
  34. if (UpdateSrgIndices(terrainSrg))
  35. {
  36. TerrainMacroMaterialNotificationBus::Handler::BusConnect();
  37. m_terrainSizeChanged = true;
  38. m_isInitialized = true;
  39. }
  40. }
  41. void TerrainMacroMaterialManager::Reset()
  42. {
  43. m_isInitialized = false;
  44. RemoveAllImages();
  45. m_materialDataBuffer = {};
  46. m_materialRefGridDataBuffer = {};
  47. m_materialData.Clear();
  48. m_materialRefGridShaderData.clear();
  49. m_entityToMaterialHandle.clear();
  50. TerrainMacroMaterialNotificationBus::Handler::BusDisconnect();
  51. }
  52. bool TerrainMacroMaterialManager::IsInitialized()
  53. {
  54. return m_isInitialized;
  55. }
  56. bool TerrainMacroMaterialManager::UpdateSrgIndices(AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
  57. {
  58. const AZ::RHI::ShaderResourceGroupLayout* terrainSrgLayout = terrainSrg->GetLayout();
  59. AZ::Render::GpuBufferHandler::Descriptor desc;
  60. desc.m_srgLayout = terrainSrgLayout;
  61. // Set up the gpu buffer for macro material data
  62. desc.m_bufferName = "Macro Material Data";
  63. desc.m_bufferSrgName = TerrainSrgInputs::MacroMaterialData;
  64. desc.m_elementSize = sizeof(MacroMaterialShaderData);
  65. m_materialDataBuffer = AZ::Render::GpuBufferHandler(desc);
  66. desc.m_bufferName = "Macro Material Ref Grid";
  67. desc.m_bufferSrgName = TerrainSrgInputs::MacroMaterialGridRefs;
  68. desc.m_elementSize = sizeof(TileMaterials);
  69. m_materialRefGridDataBuffer = AZ::Render::GpuBufferHandler(desc);
  70. m_bufferNeedsUpdate = true;
  71. return m_materialDataBuffer.IsValid() && m_materialRefGridDataBuffer.IsValid();
  72. }
  73. void TerrainMacroMaterialManager::OnTerrainMacroMaterialCreated(AZ::EntityId entityId, const MacroMaterialData& newMaterialData)
  74. {
  75. // If terrainSizeChanged, everything will rebuild later, so don't do any work here.
  76. if (m_terrainSizeChanged)
  77. {
  78. return;
  79. }
  80. AZ_Assert(
  81. !m_entityToMaterialHandle.contains(entityId),
  82. "OnTerrainMacroMaterialCreated called for a macro material that already exists. This indicates that either the bus is incorrectly sending out "
  83. "OnCreated announcements for existing materials, or the terrain feature processor isn't properly cleaning up macro materials.");
  84. AZ_Assert(m_materialData.GetSize() < AZStd::numeric_limits<uint16_t>::max(), "No more room for terrain macro materials.");
  85. MaterialHandle materialHandle = MaterialHandle(m_materialData.Reserve());
  86. m_entityToMaterialHandle[entityId] = materialHandle;
  87. UpdateMacroMaterialShaderEntry(materialHandle, newMaterialData);
  88. ForMacroMaterialsInBounds(newMaterialData.m_bounds,
  89. [&](TileHandle tileHandle, [[maybe_unused]] const AZ::Vector2& corner)
  90. {
  91. AddMacroMaterialToTile(materialHandle, tileHandle);
  92. }
  93. );
  94. }
  95. void TerrainMacroMaterialManager::OnTerrainMacroMaterialChanged(AZ::EntityId entityId, const MacroMaterialData& newMaterialData)
  96. {
  97. // If terrainSizeChanged, everything will rebuild later, so don't do any work here.
  98. if (m_terrainSizeChanged)
  99. {
  100. return;
  101. }
  102. AZ_Assert(
  103. m_entityToMaterialHandle.contains(entityId),
  104. "OnTerrainMacroMaterialChanged called for a macro material that TerrainFeatureProcessor isn't tracking. This indicates that either the bus is sending out "
  105. "Changed announcements for materials that haven't had a OnCreated event sent, or the terrain feature processor isn't properly tracking macro materials.");
  106. MaterialHandle materialHandle = m_entityToMaterialHandle[entityId];
  107. UpdateMacroMaterialShaderEntry(materialHandle, newMaterialData);
  108. }
  109. void TerrainMacroMaterialManager::OnTerrainMacroMaterialRegionChanged(
  110. AZ::EntityId entityId, [[maybe_unused]] const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
  111. {
  112. // If terrainSizeChanged, everything will rebuild later, so don't do any work here.
  113. if (m_terrainSizeChanged)
  114. {
  115. return;
  116. }
  117. AZ_Assert(
  118. m_entityToMaterialHandle.contains(entityId),
  119. "OnTerrainMacroMaterialChanged called for a macro material that TerrainFeatureProcessor isn't tracking. This indicates that either the bus is sending out "
  120. "Changed announcements for materials that haven't had a OnCreated event sent, or the terrain feature processor isn't properly tracking macro materials.");
  121. MaterialHandle materialHandle = m_entityToMaterialHandle[entityId];
  122. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  123. const AZ::Vector2 boundsMin = AZ::Vector2(newRegion.GetMin());
  124. const AZ::Vector2 boundsMax = AZ::Vector2(newRegion.GetMax());
  125. boundsMin.StoreToFloat2(shaderData.m_boundsMin.data());
  126. boundsMax.StoreToFloat2(shaderData.m_boundsMax.data());
  127. AZ::Aabb changedRegion = oldRegion;
  128. changedRegion.AddAabb(newRegion);
  129. ForMacroMaterialsInBounds(changedRegion,
  130. [&](TileHandle tileHandle, const AZ::Vector2& tileMin)
  131. {
  132. AZ::Vector2 tileMax = tileMin + AZ::Vector2(MacroMaterialGridSize);
  133. bool overlapsNew =
  134. tileMin.IsLessThan(boundsMax) &&
  135. tileMax.IsGreaterThan(boundsMin);
  136. if (overlapsNew)
  137. {
  138. AddMacroMaterialToTile(materialHandle, tileHandle);
  139. }
  140. else
  141. {
  142. RemoveMacroMaterialFromTile(materialHandle, tileHandle, tileMin);
  143. }
  144. }
  145. );
  146. m_bufferNeedsUpdate = true;
  147. }
  148. void TerrainMacroMaterialManager::OnTerrainMacroMaterialDestroyed(AZ::EntityId entityId)
  149. {
  150. // If terrainSizeChanged, everything will rebuild later, so don't do any work here.
  151. if (m_terrainSizeChanged)
  152. {
  153. return;
  154. }
  155. AZ_Assert(
  156. m_entityToMaterialHandle.contains(entityId),
  157. "OnTerrainMacroMaterialChanged called for a macro material that TerrainFeatureProcessor isn't tracking. This indicates that either the bus is sending out "
  158. "Changed announcements for materials that haven't had a OnCreated event sent, or the terrain feature processor isn't properly tracking macro materials.");
  159. MaterialHandle materialHandle = m_entityToMaterialHandle[entityId];
  160. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  161. ForMacroMaterialsInBounds(shaderData.m_boundsMin, shaderData.m_boundsMax,
  162. [&](TileHandle tileHandle, [[maybe_unused]] const AZ::Vector2& corner)
  163. {
  164. RemoveMacroMaterialFromTile(materialHandle, tileHandle, corner);
  165. }
  166. );
  167. m_materialData.Release(materialHandle.GetIndex());
  168. m_entityToMaterialHandle.erase(entityId);
  169. m_bufferNeedsUpdate = true;
  170. }
  171. void TerrainMacroMaterialManager::UpdateMacroMaterialShaderEntry(MaterialHandle materialHandle, const MacroMaterialData& macroMaterialData)
  172. {
  173. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  174. shaderData.m_flags = (MacroMaterialShaderFlags)(
  175. (macroMaterialData.m_normalFlipX ? MacroMaterialShaderFlags::FlipMacroNormalX : 0) |
  176. (macroMaterialData.m_normalFlipY ? MacroMaterialShaderFlags::FlipMacroNormalY : 0)
  177. );
  178. shaderData.m_normalFactor = macroMaterialData.m_normalFactor;
  179. AZ::Vector2(macroMaterialData.m_bounds.GetMin()).StoreToFloat2(shaderData.m_boundsMin.data());
  180. AZ::Vector2(macroMaterialData.m_bounds.GetMax()).StoreToFloat2(shaderData.m_boundsMax.data());
  181. auto UpdateImageIndex = [&](uint32_t& indexRef, const AZ::Data::Instance<AZ::RPI::Image>& imageView)
  182. {
  183. indexRef = imageView ? imageView->GetImageView()->GetBindlessReadIndex() : InvalidImageIndex;
  184. };
  185. UpdateImageIndex(shaderData.m_colorMapId, macroMaterialData.m_colorImage);
  186. UpdateImageIndex(shaderData.m_normalMapId, macroMaterialData.m_normalImage);
  187. MacroMaterialPriority& priority = m_materialData.GetElement<1>(materialHandle.GetIndex());
  188. priority.m_priority = macroMaterialData.m_priority;
  189. priority.m_hash = uint32_t(AZ::u64(macroMaterialData.m_entityId) >> 32) ^ uint32_t(AZ::u64(macroMaterialData.m_entityId) & 0xFFFFFFFF);
  190. m_bufferNeedsUpdate = true;
  191. }
  192. void TerrainMacroMaterialManager::AddMacroMaterialToTile(MaterialHandle newMaterialHandle, TileHandle tileHandle)
  193. {
  194. TileMaterials& tileMaterials = m_materialRefGridShaderData.at(tileHandle.GetIndex());
  195. MacroMaterialPriority& newPriority = m_materialData.GetElement<1>(newMaterialHandle.GetIndex());
  196. for (uint16_t materialIndex = 0; materialIndex < MacroMaterialsPerTile; ++materialIndex)
  197. {
  198. MaterialHandle& materialHandle = tileMaterials.at(materialIndex);
  199. if (materialHandle == MaterialHandle::Null)
  200. {
  201. // Empty spot, just add the material
  202. materialHandle = newMaterialHandle;
  203. return;
  204. }
  205. else if (materialHandle == newMaterialHandle)
  206. {
  207. return;
  208. }
  209. else
  210. {
  211. // Check the priority. If the new material's priority is greater, insert.
  212. MacroMaterialPriority& priority = m_materialData.GetElement<1>(materialHandle.GetIndex());
  213. if (newPriority > priority)
  214. {
  215. MaterialHandle temphandle = newMaterialHandle;
  216. for (; materialIndex < MacroMaterialsPerTile; ++materialIndex)
  217. {
  218. MaterialHandle& materialHandle2 = tileMaterials.at(materialIndex);
  219. AZStd::swap(materialHandle2, temphandle);
  220. }
  221. return;
  222. }
  223. }
  224. }
  225. }
  226. void TerrainMacroMaterialManager::RemoveMacroMaterialFromTile(MaterialHandle materialHandleToRemove, TileHandle tileHandle, const AZ::Vector2& tileMin)
  227. {
  228. TileMaterials& tileMaterials = m_materialRefGridShaderData.at(tileHandle.GetIndex());
  229. for (uint16_t materialIndex = 0; materialIndex < MacroMaterialsPerTile; ++materialIndex)
  230. {
  231. if (tileMaterials.at(materialIndex) == materialHandleToRemove)
  232. {
  233. // Remove the macro material entry from this tile by copying the remaining entries on top.
  234. for (++materialIndex; materialIndex < MacroMaterialsPerTile; ++materialIndex)
  235. {
  236. tileMaterials.at(materialIndex - 1) = tileMaterials.at(materialIndex);
  237. }
  238. break;
  239. }
  240. }
  241. // Disable or replace the last entry.
  242. MaterialHandle& lastEntry = tileMaterials.at(MacroMaterialsPerTile - 1);
  243. if (lastEntry != MaterialHandle::Null)
  244. {
  245. lastEntry = MaterialHandle::Null;
  246. MacroMaterialPriority lastPriority;
  247. // Check all the macro materials to see if any overlap this tile. Since the tile was full, when a macro material
  248. // was removed, there may be a macro material that can be placed in the empty spot.
  249. // Create a list of macro materials to ignore when searching for possible entries to add at the end.
  250. // This is basically all the materials except the last one, plus the material currently being removed.
  251. AZStd::array<MaterialHandle, MacroMaterialsPerTile> alreadyUsedMaterials;
  252. AZStd::copy(tileMaterials.begin(), tileMaterials.end(), alreadyUsedMaterials.begin());
  253. alreadyUsedMaterials.at(MacroMaterialsPerTile - 1) = materialHandleToRemove;
  254. AZ::Vector2 tileMax = tileMin + AZ::Vector2(MacroMaterialGridSize);
  255. for (auto& [entityId, materialHandle] : m_entityToMaterialHandle)
  256. {
  257. if (AZStd::find(alreadyUsedMaterials.begin(), alreadyUsedMaterials.end(), materialHandle) != nullptr)
  258. {
  259. continue;
  260. }
  261. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  262. MacroMaterialPriority priority = m_materialData.GetElement<1>(materialHandle.GetIndex());
  263. if (shaderData.Overlaps(tileMin, tileMax) && (lastEntry == MaterialHandle::Null || priority > lastPriority))
  264. {
  265. lastEntry = materialHandle;
  266. lastPriority = priority;
  267. }
  268. }
  269. }
  270. }
  271. template<typename Callback>
  272. void TerrainMacroMaterialManager::ForMacroMaterialsInRegion(const ClipmapBoundsRegion& region, Callback callback)
  273. {
  274. AZ::Vector2 regionCorner = AZ::Vector2(region.m_worldAabb.GetMin());
  275. Vector2i extents = region.m_localAabb.m_max - region.m_localAabb.m_min;
  276. for (int32_t y = 0; y < extents.m_y; ++y)
  277. {
  278. for (int32_t x = 0; x < extents.m_x; ++x)
  279. {
  280. const Vector2i local = region.m_localAabb.m_min + Vector2i(x, y);
  281. TileHandle tileHandle = TileHandle(local.m_y * m_tiles1D + local.m_x);
  282. const AZ::Vector2 corner = regionCorner + AZ::Vector2(x * MacroMaterialGridSize, y * MacroMaterialGridSize);
  283. callback(tileHandle, corner);
  284. }
  285. }
  286. }
  287. template<typename Callback>
  288. void TerrainMacroMaterialManager::ForMacroMaterialsInBounds(const AZ::Vector2& minBounds, const AZ::Vector2& maxBounds, Callback callback)
  289. {
  290. auto updateRegionsList = m_macroMaterialTileBounds.TransformRegion(minBounds, maxBounds);
  291. for (const auto& region : updateRegionsList)
  292. {
  293. ForMacroMaterialsInRegion(region, callback);
  294. }
  295. }
  296. template<typename Callback>
  297. void TerrainMacroMaterialManager::ForMacroMaterialsInBounds(const AZ::Aabb& bounds, Callback callback)
  298. {
  299. ForMacroMaterialsInBounds(AZ::Vector2(bounds.GetMin()), AZ::Vector2(bounds.GetMax()), callback);
  300. }
  301. template<typename Callback>
  302. void TerrainMacroMaterialManager::ForMacroMaterialsInBounds(const AZStd::array<float, 2>& minBounds, const AZStd::array<float, 2>& maxBounds, Callback callback)
  303. {
  304. ForMacroMaterialsInBounds(AZ::Vector2::CreateFromFloat2(minBounds.data()), AZ::Vector2::CreateFromFloat2(maxBounds.data()), callback);
  305. }
  306. void TerrainMacroMaterialManager::SetRenderDistance(float distance)
  307. {
  308. uint16_t newTiles1D = aznumeric_cast<uint16_t>(AZStd::ceilf((distance) / MacroMaterialGridSize)) + 1;
  309. newTiles1D *= 2; // distance is radius, grid covers diameter.
  310. if (newTiles1D != m_tiles1D)
  311. {
  312. m_tiles1D = newTiles1D;
  313. m_terrainSizeChanged = true;
  314. }
  315. }
  316. void TerrainMacroMaterialManager::Update(const AZ::RPI::ViewPtr mainView, AZ::Data::Instance<AZ::RPI::ShaderResourceGroup>& terrainSrg)
  317. {
  318. AZ::Vector3 mainCameraPosition = mainView->GetCameraTransform().GetTranslation();
  319. if (m_terrainSizeChanged)
  320. {
  321. m_terrainSizeChanged = false;
  322. m_bufferNeedsUpdate = true;
  323. ClipmapBoundsDescriptor desc;
  324. desc.m_clipmapToWorldScale = MacroMaterialGridSize;
  325. desc.m_clipmapUpdateMultiple = 1;
  326. desc.m_size = m_tiles1D;
  327. desc.m_worldSpaceCenter = AZ::Vector2(mainCameraPosition);
  328. m_macroMaterialTileBounds = ClipmapBounds(desc);
  329. // Rebuild the macro material tiles from scratch when the world size changes. This could be made more efficient
  330. // but is fine for now since world resizes are rare.
  331. RemoveAllImages();
  332. m_entityToMaterialHandle.clear();
  333. m_materialData.Clear();
  334. m_materialRefGridShaderData.clear();
  335. const uint32_t macroMaterialTileCount = m_tiles1D * m_tiles1D;
  336. m_materialRefGridShaderData.resize(macroMaterialTileCount);
  337. AZStd::fill(m_materialRefGridShaderData.begin(), m_materialRefGridShaderData.end(), DefaultTileMaterials);
  338. TerrainMacroMaterialRequestBus::EnumerateHandlers(
  339. [&](TerrainMacroMaterialRequests* handler)
  340. {
  341. MacroMaterialData macroMaterial = handler->GetTerrainMacroMaterialData();
  342. AZ::EntityId entityId = *(Terrain::TerrainMacroMaterialRequestBus::GetCurrentBusId());
  343. OnTerrainMacroMaterialCreated(entityId, macroMaterial);
  344. return true;
  345. }
  346. );
  347. }
  348. else
  349. {
  350. auto updateRegionList = m_macroMaterialTileBounds.UpdateCenter(AZ::Vector2(mainCameraPosition));
  351. for (const auto& updateRegion : updateRegionList)
  352. {
  353. AZStd::vector<MaterialHandle> affectedMaterials;
  354. affectedMaterials.reserve(AZStd::GetMin(m_entityToMaterialHandle.size(), size_t(128)));
  355. AZ::Vector2 regionMin = AZ::Vector2(updateRegion.m_worldAabb.GetMin());
  356. AZ::Vector2 regionMax = AZ::Vector2(updateRegion.m_worldAabb.GetMax());
  357. // Do a coarse check of which materials might affect this region's tiles by gathering all
  358. // macro materials that overlap the region. This should reduce the number of checks that need
  359. // to be done per-tile.
  360. for (auto& [entityId, materialHandle] : m_entityToMaterialHandle)
  361. {
  362. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  363. if (shaderData.Overlaps(regionMin, regionMax))
  364. {
  365. affectedMaterials.push_back(materialHandle);
  366. }
  367. }
  368. // Check the list of macro materials against all the tiles in this region.
  369. ForMacroMaterialsInRegion(updateRegion,
  370. [&](TileHandle tileHandle, const AZ::Vector2& tileMin)
  371. {
  372. AZ::Vector2 tileMax = tileMin + AZ::Vector2(MacroMaterialGridSize);
  373. m_materialRefGridShaderData.at(tileHandle.GetIndex()) = DefaultTileMaterials; // clear out current materials
  374. for (MaterialHandle materialHandle : affectedMaterials)
  375. {
  376. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  377. if (shaderData.Overlaps(tileMin, tileMax))
  378. {
  379. AddMacroMaterialToTile(materialHandle, tileHandle);
  380. }
  381. }
  382. }
  383. );
  384. m_bufferNeedsUpdate = true;
  385. }
  386. }
  387. if (m_bufferNeedsUpdate && terrainSrg)
  388. {
  389. m_bufferNeedsUpdate = false;
  390. m_materialDataBuffer.UpdateBuffer(m_materialData.GetRawData<0>(), aznumeric_cast<uint32_t>(m_materialData.GetSize()));
  391. m_materialRefGridDataBuffer.UpdateBuffer(m_materialRefGridShaderData.data(), aznumeric_cast<uint32_t>(m_materialRefGridShaderData.size()));
  392. MacroMaterialGridShaderData macroMaterialGridShaderData;
  393. macroMaterialGridShaderData.m_tileCount1D = m_tiles1D;
  394. macroMaterialGridShaderData.m_tileSize = MacroMaterialGridSize;
  395. m_materialDataBuffer.UpdateSrg(terrainSrg.get());
  396. m_materialRefGridDataBuffer.UpdateSrg(terrainSrg.get());
  397. terrainSrg->SetConstant(m_macroMaterialGridIndex, macroMaterialGridShaderData);
  398. }
  399. }
  400. void TerrainMacroMaterialManager::RemoveAllImages()
  401. {
  402. for (const auto& [entity, materialRef] : m_entityToMaterialHandle)
  403. {
  404. RemoveImagesForMaterial(materialRef);
  405. }
  406. }
  407. void TerrainMacroMaterialManager::RemoveImagesForMaterial(MaterialHandle materialHandle)
  408. {
  409. MacroMaterialShaderData& shaderData = m_materialData.GetElement<0>(materialHandle.GetIndex());
  410. shaderData.m_colorMapId = InvalidImageIndex;
  411. shaderData.m_normalMapId = InvalidImageIndex;
  412. }
  413. }