terrain_renderer.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. #include "terrain_renderer.h"
  2. #include "../../game/map/visibility_service.h"
  3. #include "../gl/buffer.h"
  4. #include "../gl/mesh.h"
  5. #include "../gl/resources.h"
  6. #include "../scene_renderer.h"
  7. #include <QDebug>
  8. #include <QElapsedTimer>
  9. #include <QQuaternion>
  10. #include <QVector2D>
  11. #include <QtGlobal>
  12. #include <algorithm>
  13. #include <cmath>
  14. #include <unordered_map>
  15. namespace {
  16. using std::uint32_t;
  17. const QMatrix4x4 kIdentityMatrix;
  18. inline uint32_t hashCoords(int x, int z, uint32_t salt = 0u) {
  19. uint32_t ux = static_cast<uint32_t>(x * 73856093);
  20. uint32_t uz = static_cast<uint32_t>(z * 19349663);
  21. return ux ^ uz ^ (salt * 83492791u);
  22. }
  23. inline float rand01(uint32_t &state) {
  24. state = state * 1664525u + 1013904223u;
  25. return static_cast<float>((state >> 8) & 0xFFFFFF) /
  26. static_cast<float>(0xFFFFFF);
  27. }
  28. inline float remap(float value, float minOut, float maxOut) {
  29. return minOut + (maxOut - minOut) * value;
  30. }
  31. inline QVector3D applyTint(const QVector3D &color, float tint) {
  32. QVector3D c = color * tint;
  33. return QVector3D(std::clamp(c.x(), 0.0f, 1.0f), std::clamp(c.y(), 0.0f, 1.0f),
  34. std::clamp(c.z(), 0.0f, 1.0f));
  35. }
  36. inline QVector3D clamp01(const QVector3D &c) {
  37. return QVector3D(std::clamp(c.x(), 0.0f, 1.0f), std::clamp(c.y(), 0.0f, 1.0f),
  38. std::clamp(c.z(), 0.0f, 1.0f));
  39. }
  40. inline float hashTo01(uint32_t h) {
  41. h ^= h >> 17;
  42. h *= 0xed5ad4bbu;
  43. h ^= h >> 11;
  44. h *= 0xac4c1b51u;
  45. h ^= h >> 15;
  46. h *= 0x31848babu;
  47. h ^= h >> 14;
  48. return (h & 0x00FFFFFFu) / float(0x01000000);
  49. }
  50. inline float valueNoise(float x, float z, uint32_t salt = 0u) {
  51. int x0 = int(std::floor(x)), z0 = int(std::floor(z));
  52. int x1 = x0 + 1, z1 = z0 + 1;
  53. float tx = x - float(x0), tz = z - float(z0);
  54. float n00 = hashTo01(hashCoords(x0, z0, salt));
  55. float n10 = hashTo01(hashCoords(x1, z0, salt));
  56. float n01 = hashTo01(hashCoords(x0, z1, salt));
  57. float n11 = hashTo01(hashCoords(x1, z1, salt));
  58. float nx0 = n00 * (1 - tx) + n10 * tx;
  59. float nx1 = n01 * (1 - tx) + n11 * tx;
  60. return nx0 * (1 - tz) + nx1 * tz;
  61. }
  62. } // namespace
  63. namespace Render::GL {
  64. TerrainRenderer::TerrainRenderer() = default;
  65. TerrainRenderer::~TerrainRenderer() = default;
  66. void TerrainRenderer::configure(const Game::Map::TerrainHeightMap &heightMap,
  67. const Game::Map::BiomeSettings &biomeSettings) {
  68. m_width = heightMap.getWidth();
  69. m_height = heightMap.getHeight();
  70. m_tileSize = heightMap.getTileSize();
  71. m_heightData = heightMap.getHeightData();
  72. m_terrainTypes = heightMap.getTerrainTypes();
  73. m_biomeSettings = biomeSettings;
  74. m_noiseSeed = biomeSettings.seed;
  75. buildMeshes();
  76. qDebug() << "TerrainRenderer configured:" << m_width << "x" << m_height
  77. << "grid";
  78. }
  79. void TerrainRenderer::submit(Renderer &renderer, ResourceManager &resources) {
  80. if (m_chunks.empty()) {
  81. return;
  82. }
  83. Q_UNUSED(resources);
  84. auto &visibility = Game::Map::VisibilityService::instance();
  85. const bool useVisibility = visibility.isInitialized();
  86. for (const auto &chunk : m_chunks) {
  87. if (!chunk.mesh)
  88. continue;
  89. if (useVisibility) {
  90. bool anyVisible = false;
  91. for (int gz = chunk.minZ; gz <= chunk.maxZ && !anyVisible; ++gz) {
  92. for (int gx = chunk.minX; gx <= chunk.maxX; ++gx) {
  93. if (visibility.stateAt(gx, gz) ==
  94. Game::Map::VisibilityState::Visible) {
  95. anyVisible = true;
  96. break;
  97. }
  98. }
  99. }
  100. if (!anyVisible)
  101. continue;
  102. }
  103. renderer.terrainChunk(chunk.mesh.get(), kIdentityMatrix, chunk.params,
  104. 0x0080u);
  105. }
  106. }
  107. int TerrainRenderer::sectionFor(Game::Map::TerrainType type) const {
  108. switch (type) {
  109. case Game::Map::TerrainType::Mountain:
  110. return 2;
  111. case Game::Map::TerrainType::Hill:
  112. return 1;
  113. case Game::Map::TerrainType::Flat:
  114. default:
  115. return 0;
  116. }
  117. }
  118. void TerrainRenderer::buildMeshes() {
  119. QElapsedTimer timer;
  120. timer.start();
  121. m_chunks.clear();
  122. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  123. return;
  124. }
  125. const float halfWidth = m_width * 0.5f - 0.5f;
  126. const float halfHeight = m_height * 0.5f - 0.5f;
  127. const int vertexCount = m_width * m_height;
  128. std::vector<QVector3D> positions(vertexCount);
  129. std::vector<QVector3D> normals(vertexCount, QVector3D(0.0f, 0.0f, 0.0f));
  130. for (int z = 0; z < m_height; ++z) {
  131. for (int x = 0; x < m_width; ++x) {
  132. int idx = z * m_width + x;
  133. float worldX = (x - halfWidth) * m_tileSize;
  134. float worldZ = (z - halfHeight) * m_tileSize;
  135. positions[idx] = QVector3D(worldX, m_heightData[idx], worldZ);
  136. }
  137. }
  138. auto accumulateNormal = [&](int i0, int i1, int i2) {
  139. const QVector3D &v0 = positions[i0];
  140. const QVector3D &v1 = positions[i1];
  141. const QVector3D &v2 = positions[i2];
  142. QVector3D normal = QVector3D::crossProduct(v1 - v0, v2 - v0);
  143. normals[i0] += normal;
  144. normals[i1] += normal;
  145. normals[i2] += normal;
  146. };
  147. auto sampleHeightAt = [&](float gx, float gz) {
  148. gx = std::clamp(gx, 0.0f, float(m_width - 1));
  149. gz = std::clamp(gz, 0.0f, float(m_height - 1));
  150. int x0 = int(std::floor(gx));
  151. int z0 = int(std::floor(gz));
  152. int x1 = std::min(x0 + 1, m_width - 1);
  153. int z1 = std::min(z0 + 1, m_height - 1);
  154. float tx = gx - float(x0);
  155. float tz = gz - float(z0);
  156. float h00 = m_heightData[z0 * m_width + x0];
  157. float h10 = m_heightData[z0 * m_width + x1];
  158. float h01 = m_heightData[z1 * m_width + x0];
  159. float h11 = m_heightData[z1 * m_width + x1];
  160. float h0 = h00 * (1.0f - tx) + h10 * tx;
  161. float h1 = h01 * (1.0f - tx) + h11 * tx;
  162. return h0 * (1.0f - tz) + h1 * tz;
  163. };
  164. auto normalFromHeightsAt = [&](float gx, float gz) {
  165. float gx0 = std::clamp(gx - 1.0f, 0.0f, float(m_width - 1));
  166. float gx1 = std::clamp(gx + 1.0f, 0.0f, float(m_width - 1));
  167. float gz0 = std::clamp(gz - 1.0f, 0.0f, float(m_height - 1));
  168. float gz1 = std::clamp(gz + 1.0f, 0.0f, float(m_height - 1));
  169. float hL = sampleHeightAt(gx0, gz);
  170. float hR = sampleHeightAt(gx1, gz);
  171. float hD = sampleHeightAt(gx, gz0);
  172. float hU = sampleHeightAt(gx, gz1);
  173. QVector3D dx(2.0f * m_tileSize, hR - hL, 0.0f);
  174. QVector3D dz(0.0f, hU - hD, 2.0f * m_tileSize);
  175. QVector3D n = QVector3D::crossProduct(dz, dx);
  176. if (n.lengthSquared() > 0.0f)
  177. n.normalize();
  178. return n.isNull() ? QVector3D(0, 1, 0) : n;
  179. };
  180. for (int z = 0; z < m_height - 1; ++z) {
  181. for (int x = 0; x < m_width - 1; ++x) {
  182. int idx0 = z * m_width + x;
  183. int idx1 = idx0 + 1;
  184. int idx2 = (z + 1) * m_width + x;
  185. int idx3 = idx2 + 1;
  186. accumulateNormal(idx0, idx1, idx2);
  187. accumulateNormal(idx2, idx1, idx3);
  188. }
  189. }
  190. for (int i = 0; i < vertexCount; ++i) {
  191. normals[i].normalize();
  192. if (normals[i].isNull()) {
  193. normals[i] = QVector3D(0.0f, 1.0f, 0.0f);
  194. }
  195. }
  196. {
  197. std::vector<QVector3D> smoothed = normals;
  198. auto getN = [&](int x, int z) -> QVector3D & {
  199. return normals[z * m_width + x];
  200. };
  201. for (int z = 1; z < m_height - 1; ++z) {
  202. for (int x = 1; x < m_width - 1; ++x) {
  203. QVector3D acc(0, 0, 0);
  204. for (int dz = -1; dz <= 1; ++dz)
  205. for (int dx = -1; dx <= 1; ++dx)
  206. acc += getN(x + dx, z + dz);
  207. acc.normalize();
  208. smoothed[z * m_width + x] = acc;
  209. }
  210. }
  211. normals.swap(smoothed);
  212. }
  213. auto quadSection = [&](Game::Map::TerrainType a, Game::Map::TerrainType b,
  214. Game::Map::TerrainType c, Game::Map::TerrainType d) {
  215. int priorityA = sectionFor(a);
  216. int priorityB = sectionFor(b);
  217. int priorityC = sectionFor(c);
  218. int priorityD = sectionFor(d);
  219. int result = priorityA;
  220. result = std::max(result, priorityB);
  221. result = std::max(result, priorityC);
  222. result = std::max(result, priorityD);
  223. return result;
  224. };
  225. const int chunkSize = 16;
  226. std::size_t totalTriangles = 0;
  227. for (int chunkZ = 0; chunkZ < m_height - 1; chunkZ += chunkSize) {
  228. int chunkMaxZ = std::min(chunkZ + chunkSize, m_height - 1);
  229. for (int chunkX = 0; chunkX < m_width - 1; chunkX += chunkSize) {
  230. int chunkMaxX = std::min(chunkX + chunkSize, m_width - 1);
  231. struct SectionData {
  232. std::vector<Vertex> vertices;
  233. std::vector<unsigned int> indices;
  234. std::unordered_map<int, unsigned int> remap;
  235. float heightSum = 0.0f;
  236. int heightCount = 0;
  237. float rotationDeg = 0.0f;
  238. bool flipU = false;
  239. float tint = 1.0f;
  240. QVector3D normalSum = QVector3D(0, 0, 0);
  241. float slopeSum = 0.0f;
  242. float heightVarSum = 0.0f;
  243. int statCount = 0;
  244. float aoSum = 0.0f;
  245. int aoCount = 0;
  246. };
  247. SectionData sections[3];
  248. uint32_t chunkSeed = hashCoords(chunkX, chunkZ, m_noiseSeed);
  249. uint32_t variantSeed = chunkSeed ^ 0x9e3779b9u;
  250. float rotationStep = static_cast<float>((variantSeed >> 5) & 3) * 90.0f;
  251. bool flip = ((variantSeed >> 7) & 1u) != 0u;
  252. static const float tintVariants[7] = {0.9f, 0.94f, 0.97f, 1.0f,
  253. 1.03f, 1.06f, 1.1f};
  254. float tint = tintVariants[(variantSeed >> 12) % 7];
  255. for (auto &section : sections) {
  256. section.rotationDeg = rotationStep;
  257. section.flipU = flip;
  258. section.tint = tint;
  259. }
  260. auto ensureVertex = [&](SectionData &section,
  261. int globalIndex) -> unsigned int {
  262. auto it = section.remap.find(globalIndex);
  263. if (it != section.remap.end()) {
  264. return it->second;
  265. }
  266. Vertex v{};
  267. const QVector3D &pos = positions[globalIndex];
  268. const QVector3D &normal = normals[globalIndex];
  269. int gx = globalIndex % m_width;
  270. int gz = globalIndex / m_width;
  271. v.position[0] = pos.x();
  272. v.position[1] = pos.y();
  273. v.position[2] = pos.z();
  274. v.normal[0] = normal.x();
  275. v.normal[1] = normal.y();
  276. v.normal[2] = normal.z();
  277. float texScale = 0.2f / std::max(1.0f, m_tileSize);
  278. float uu = pos.x() * texScale;
  279. float vv = pos.z() * texScale;
  280. if (section.flipU)
  281. uu = -uu;
  282. float ru = uu, rv = vv;
  283. switch (static_cast<int>(section.rotationDeg)) {
  284. case 90: {
  285. float t = ru;
  286. ru = -rv;
  287. rv = t;
  288. } break;
  289. case 180:
  290. ru = -ru;
  291. rv = -rv;
  292. break;
  293. case 270: {
  294. float t = ru;
  295. ru = rv;
  296. rv = -t;
  297. } break;
  298. default:
  299. break;
  300. }
  301. v.texCoord[0] = ru;
  302. v.texCoord[1] = rv;
  303. section.vertices.push_back(v);
  304. unsigned int localIndex =
  305. static_cast<unsigned int>(section.vertices.size() - 1);
  306. section.remap.emplace(globalIndex, localIndex);
  307. section.normalSum += normal;
  308. return localIndex;
  309. };
  310. for (int z = chunkZ; z < chunkMaxZ; ++z) {
  311. for (int x = chunkX; x < chunkMaxX; ++x) {
  312. int idx0 = z * m_width + x;
  313. int idx1 = idx0 + 1;
  314. int idx2 = (z + 1) * m_width + x;
  315. int idx3 = idx2 + 1;
  316. float maxHeight =
  317. std::max(std::max(m_heightData[idx0], m_heightData[idx1]),
  318. std::max(m_heightData[idx2], m_heightData[idx3]));
  319. int sectionIndex =
  320. quadSection(m_terrainTypes[idx0], m_terrainTypes[idx1],
  321. m_terrainTypes[idx2], m_terrainTypes[idx3]);
  322. if (sectionIndex > 0) {
  323. SectionData &section = sections[sectionIndex];
  324. unsigned int v0 = ensureVertex(section, idx0);
  325. unsigned int v1 = ensureVertex(section, idx1);
  326. unsigned int v2 = ensureVertex(section, idx2);
  327. unsigned int v3 = ensureVertex(section, idx3);
  328. section.indices.push_back(v0);
  329. section.indices.push_back(v1);
  330. section.indices.push_back(v2);
  331. section.indices.push_back(v2);
  332. section.indices.push_back(v1);
  333. section.indices.push_back(v3);
  334. float quadHeight = (m_heightData[idx0] + m_heightData[idx1] +
  335. m_heightData[idx2] + m_heightData[idx3]) *
  336. 0.25f;
  337. section.heightSum += quadHeight;
  338. section.heightCount += 1;
  339. float nY = (normals[idx0].y() + normals[idx1].y() +
  340. normals[idx2].y() + normals[idx3].y()) *
  341. 0.25f;
  342. float slope = 1.0f - std::clamp(nY, 0.0f, 1.0f);
  343. section.slopeSum += slope;
  344. float hmin =
  345. std::min(std::min(m_heightData[idx0], m_heightData[idx1]),
  346. std::min(m_heightData[idx2], m_heightData[idx3]));
  347. float hmax =
  348. std::max(std::max(m_heightData[idx0], m_heightData[idx1]),
  349. std::max(m_heightData[idx2], m_heightData[idx3]));
  350. section.heightVarSum += (hmax - hmin);
  351. section.statCount += 1;
  352. auto H = [&](int gx, int gz) {
  353. gx = std::clamp(gx, 0, m_width - 1);
  354. gz = std::clamp(gz, 0, m_height - 1);
  355. return m_heightData[gz * m_width + gx];
  356. };
  357. int cx = x, cz = z;
  358. float hC = quadHeight;
  359. float ao = 0.0f;
  360. ao += std::max(0.0f, H(cx - 1, cz) - hC);
  361. ao += std::max(0.0f, H(cx + 1, cz) - hC);
  362. ao += std::max(0.0f, H(cx, cz - 1) - hC);
  363. ao += std::max(0.0f, H(cx, cz + 1) - hC);
  364. ao = std::clamp(ao * 0.15f, 0.0f, 1.0f);
  365. section.aoSum += ao;
  366. section.aoCount += 1;
  367. }
  368. }
  369. }
  370. for (int i = 0; i < 3; ++i) {
  371. SectionData &section = sections[i];
  372. if (section.indices.empty()) {
  373. continue;
  374. }
  375. auto mesh = std::make_unique<Mesh>(section.vertices, section.indices);
  376. if (!mesh)
  377. continue;
  378. ChunkMesh chunk;
  379. chunk.mesh = std::move(mesh);
  380. chunk.minX = chunkX;
  381. chunk.maxX = chunkMaxX - 1;
  382. chunk.minZ = chunkZ;
  383. chunk.maxZ = chunkMaxZ - 1;
  384. chunk.type = (i == 0) ? Game::Map::TerrainType::Flat
  385. : (i == 1) ? Game::Map::TerrainType::Hill
  386. : Game::Map::TerrainType::Mountain;
  387. chunk.averageHeight =
  388. (section.heightCount > 0)
  389. ? section.heightSum / float(section.heightCount)
  390. : 0.0f;
  391. QVector3D baseColor = getTerrainColor(chunk.type, chunk.averageHeight);
  392. float avgSlope = (section.statCount > 0)
  393. ? (section.slopeSum / float(section.statCount))
  394. : 0.0f;
  395. float roughness =
  396. (section.statCount > 0)
  397. ? (section.heightVarSum / float(section.statCount))
  398. : 0.0f;
  399. QVector3D rockTint = m_biomeSettings.rockLow;
  400. float slopeMix = std::clamp(
  401. avgSlope *
  402. ((chunk.type == Game::Map::TerrainType::Flat)
  403. ? 0.3f
  404. : (chunk.type == Game::Map::TerrainType::Hill ? 0.6f
  405. : 0.85f)),
  406. 0.0f, 1.0f);
  407. QVector3D avgN = section.normalSum;
  408. if (avgN.lengthSquared() > 0.0f)
  409. avgN.normalize();
  410. QVector3D lightDir = QVector3D(0.35f, 0.8f, 0.45f);
  411. lightDir.normalize();
  412. float ndl = std::clamp(
  413. QVector3D::dotProduct(avgN, lightDir) * 0.5f + 0.5f, 0.0f, 1.0f);
  414. float dirShade = 0.9f + 0.25f * ndl;
  415. float valleyShade =
  416. 1.0f -
  417. std::clamp((4.0f - chunk.averageHeight) * 0.06f, 0.0f, 0.15f);
  418. float roughShade = 1.0f - std::clamp(roughness * 0.35f, 0.0f, 0.2f);
  419. QVector3D north(0, 0, 1);
  420. float northness = std::clamp(
  421. QVector3D::dotProduct(avgN, north) * 0.5f + 0.5f, 0.0f, 1.0f);
  422. QVector3D coolTint(0.95f, 1.03f, 1.05f);
  423. QVector3D warmTint(1.05f, 1.0f, 0.95f);
  424. QVector3D aspectTint =
  425. coolTint * northness + warmTint * (1.0f - northness);
  426. float centerGX = 0.5f * (chunk.minX + chunk.maxX);
  427. float centerGZ = 0.5f * (chunk.minZ + chunk.maxZ);
  428. float centerWX = (centerGX - halfWidth) * m_tileSize;
  429. float centerWZ = (centerGZ - halfHeight) * m_tileSize;
  430. float macro = valueNoise(centerWX * 0.02f, centerWZ * 0.02f,
  431. m_noiseSeed ^ 0x51C3u);
  432. float macroShade = 0.9f + 0.2f * macro;
  433. float aoAvg = (section.aoCount > 0)
  434. ? (section.aoSum / float(section.aoCount))
  435. : 0.0f;
  436. float aoShade = 1.0f - 0.35f * aoAvg;
  437. chunk.tint = section.tint;
  438. QVector3D color = baseColor * (1.0f - slopeMix) + rockTint * slopeMix;
  439. color = applyTint(color, chunk.tint);
  440. color *= dirShade * valleyShade * roughShade * macroShade;
  441. color.setX(color.x() * aspectTint.x());
  442. color.setY(color.y() * aspectTint.y());
  443. color.setZ(color.z() * aspectTint.z());
  444. color *= aoShade;
  445. color = color * 0.96f + QVector3D(0.04f, 0.04f, 0.04f);
  446. chunk.color = clamp01(color);
  447. TerrainChunkParams params;
  448. auto tintColor = [&](const QVector3D &base) {
  449. return clamp01(applyTint(base, chunk.tint));
  450. };
  451. params.grassPrimary = tintColor(m_biomeSettings.grassPrimary);
  452. params.grassSecondary = tintColor(m_biomeSettings.grassSecondary);
  453. params.grassDry = tintColor(m_biomeSettings.grassDry);
  454. params.soilColor = tintColor(m_biomeSettings.soilColor);
  455. params.rockLow = tintColor(m_biomeSettings.rockLow);
  456. params.rockHigh = tintColor(m_biomeSettings.rockHigh);
  457. params.tileSize = std::max(0.001f, m_tileSize);
  458. params.macroNoiseScale = m_biomeSettings.terrainMacroNoiseScale;
  459. params.detailNoiseScale = m_biomeSettings.terrainDetailNoiseScale;
  460. float slopeThreshold = m_biomeSettings.terrainRockThreshold;
  461. if (chunk.type == Game::Map::TerrainType::Hill)
  462. slopeThreshold -= 0.05f;
  463. else if (chunk.type == Game::Map::TerrainType::Mountain)
  464. slopeThreshold -= 0.12f;
  465. slopeThreshold -= std::clamp(avgSlope * 0.25f, 0.0f, 0.15f);
  466. params.slopeRockThreshold = std::clamp(slopeThreshold, 0.05f, 0.9f);
  467. params.slopeRockSharpness =
  468. std::max(1.0f, m_biomeSettings.terrainRockSharpness);
  469. float soilHeight = m_biomeSettings.terrainSoilHeight;
  470. if (chunk.type == Game::Map::TerrainType::Hill)
  471. soilHeight -= 0.08f;
  472. else if (chunk.type == Game::Map::TerrainType::Mountain)
  473. soilHeight -= 0.18f;
  474. soilHeight += std::clamp((0.35f - avgSlope) * 0.18f, -0.12f, 0.12f);
  475. params.soilBlendHeight = soilHeight;
  476. params.soilBlendSharpness =
  477. std::max(0.75f, m_biomeSettings.terrainSoilSharpness);
  478. const uint32_t noiseKeyA =
  479. hashCoords(chunk.minX, chunk.minZ, m_noiseSeed ^ 0xB5297A4Du);
  480. const uint32_t noiseKeyB =
  481. hashCoords(chunk.minX, chunk.minZ, m_noiseSeed ^ 0x68E31DA4u);
  482. params.noiseOffset = QVector2D(hashTo01(noiseKeyA) * 256.0f,
  483. hashTo01(noiseKeyB) * 256.0f);
  484. params.heightNoiseStrength =
  485. m_biomeSettings.heightNoiseAmplitude *
  486. (0.7f + 0.3f * std::clamp(roughness * 0.6f, 0.0f, 1.0f));
  487. params.heightNoiseFrequency = m_biomeSettings.heightNoiseFrequency;
  488. params.ambientBoost =
  489. m_biomeSettings.terrainAmbientBoost *
  490. (0.9f + 0.1f * (1.0f - std::clamp(aoAvg * 1.6f, 0.0f, 1.0f)));
  491. params.rockDetailStrength =
  492. m_biomeSettings.terrainRockDetailStrength *
  493. (0.75f + 0.25f * std::clamp(avgSlope * 1.4f, 0.0f, 1.0f));
  494. params.tint = clamp01(QVector3D(chunk.tint, chunk.tint, chunk.tint));
  495. params.lightDirection = QVector3D(0.35f, 0.8f, 0.45f);
  496. chunk.params = params;
  497. totalTriangles += chunk.mesh->getIndices().size() / 3;
  498. m_chunks.push_back(std::move(chunk));
  499. }
  500. }
  501. }
  502. qDebug() << "TerrainRenderer: built" << m_chunks.size() << "chunks in"
  503. << timer.elapsed() << "ms" << "triangles:" << totalTriangles;
  504. }
  505. QVector3D TerrainRenderer::getTerrainColor(Game::Map::TerrainType type,
  506. float height) const {
  507. switch (type) {
  508. case Game::Map::TerrainType::Mountain:
  509. if (height > 4.0f) {
  510. return m_biomeSettings.rockHigh;
  511. }
  512. return m_biomeSettings.rockLow;
  513. case Game::Map::TerrainType::Hill: {
  514. float t = std::clamp(height / 3.0f, 0.0f, 1.0f);
  515. QVector3D grass = m_biomeSettings.grassSecondary * (1.0f - t) +
  516. m_biomeSettings.grassDry * t;
  517. QVector3D rock =
  518. m_biomeSettings.rockLow * (1.0f - t) + m_biomeSettings.rockHigh * t;
  519. float rockBlend = std::clamp(0.25f + 0.5f * t, 0.0f, 0.75f);
  520. return grass * (1.0f - rockBlend) + rock * rockBlend;
  521. }
  522. case Game::Map::TerrainType::Flat:
  523. default: {
  524. float moisture = std::clamp((height - 0.5f) * 0.2f, 0.0f, 0.4f);
  525. QVector3D base = m_biomeSettings.grassPrimary * (1.0f - moisture) +
  526. m_biomeSettings.grassSecondary * moisture;
  527. float dryBlend = std::clamp((height - 2.0f) * 0.12f, 0.0f, 0.3f);
  528. return base * (1.0f - dryBlend) + m_biomeSettings.grassDry * dryBlend;
  529. }
  530. }
  531. }
  532. } // namespace Render::GL