terrain_renderer.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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 linstep(float a, float b, float x) {
  51. return std::clamp((x - a) / std::max(1e-6f, (b - a)), 0.0f, 1.0f);
  52. }
  53. inline float smooth(float a, float b, float x) {
  54. float t = linstep(a, b, x);
  55. return t * t * (3.0f - 2.0f * t);
  56. }
  57. inline float valueNoise(float x, float z, uint32_t salt = 0u) {
  58. int x0 = int(std::floor(x)), z0 = int(std::floor(z));
  59. int x1 = x0 + 1, z1 = z0 + 1;
  60. float tx = x - float(x0), tz = z - float(z0);
  61. float n00 = hashTo01(hashCoords(x0, z0, salt));
  62. float n10 = hashTo01(hashCoords(x1, z0, salt));
  63. float n01 = hashTo01(hashCoords(x0, z1, salt));
  64. float n11 = hashTo01(hashCoords(x1, z1, salt));
  65. float nx0 = n00 * (1 - tx) + n10 * tx;
  66. float nx1 = n01 * (1 - tx) + n11 * tx;
  67. return nx0 * (1 - tz) + nx1 * tz;
  68. }
  69. } // namespace
  70. namespace Render::GL {
  71. TerrainRenderer::TerrainRenderer() = default;
  72. TerrainRenderer::~TerrainRenderer() = default;
  73. void TerrainRenderer::configure(const Game::Map::TerrainHeightMap &heightMap,
  74. const Game::Map::BiomeSettings &biomeSettings) {
  75. m_width = heightMap.getWidth();
  76. m_height = heightMap.getHeight();
  77. m_tileSize = heightMap.getTileSize();
  78. m_heightData = heightMap.getHeightData();
  79. m_terrainTypes = heightMap.getTerrainTypes();
  80. m_biomeSettings = biomeSettings;
  81. m_noiseSeed = biomeSettings.seed;
  82. buildMeshes();
  83. }
  84. void TerrainRenderer::submit(Renderer &renderer, ResourceManager *resources) {
  85. if (m_chunks.empty()) {
  86. return;
  87. }
  88. Q_UNUSED(resources);
  89. auto &visibility = Game::Map::VisibilityService::instance();
  90. const bool useVisibility = visibility.isInitialized();
  91. for (const auto &chunk : m_chunks) {
  92. if (!chunk.mesh) {
  93. continue;
  94. }
  95. if (useVisibility) {
  96. bool anyVisible = false;
  97. for (int gz = chunk.minZ; gz <= chunk.maxZ && !anyVisible; ++gz) {
  98. for (int gx = chunk.minX; gx <= chunk.maxX; ++gx) {
  99. if (visibility.stateAt(gx, gz) ==
  100. Game::Map::VisibilityState::Visible) {
  101. anyVisible = true;
  102. break;
  103. }
  104. }
  105. }
  106. if (!anyVisible) {
  107. continue;
  108. }
  109. }
  110. renderer.terrainChunk(chunk.mesh.get(), kIdentityMatrix, chunk.params,
  111. 0x0080u, true, 0.0f);
  112. }
  113. }
  114. int TerrainRenderer::sectionFor(Game::Map::TerrainType type) const {
  115. switch (type) {
  116. case Game::Map::TerrainType::Mountain:
  117. return 2;
  118. case Game::Map::TerrainType::Hill:
  119. return 1;
  120. case Game::Map::TerrainType::Flat:
  121. default:
  122. return 0;
  123. }
  124. }
  125. void TerrainRenderer::buildMeshes() {
  126. QElapsedTimer timer;
  127. timer.start();
  128. m_chunks.clear();
  129. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  130. return;
  131. }
  132. float minH = std::numeric_limits<float>::infinity();
  133. float maxH = -std::numeric_limits<float>::infinity();
  134. for (float h : m_heightData) {
  135. minH = std::min(minH, h);
  136. maxH = std::max(maxH, h);
  137. }
  138. const float heightRange = std::max(1e-4f, maxH - minH);
  139. const float halfWidth = m_width * 0.5f - 0.5f;
  140. const float halfHeight = m_height * 0.5f - 0.5f;
  141. const int vertexCount = m_width * m_height;
  142. std::vector<QVector3D> positions(vertexCount);
  143. std::vector<QVector3D> normals(vertexCount, QVector3D(0.0f, 0.0f, 0.0f));
  144. std::vector<QVector3D> faceAccum(vertexCount, QVector3D(0, 0, 0));
  145. for (int z = 0; z < m_height; ++z) {
  146. for (int x = 0; x < m_width; ++x) {
  147. int idx = z * m_width + x;
  148. float worldX = (x - halfWidth) * m_tileSize;
  149. float worldZ = (z - halfHeight) * m_tileSize;
  150. positions[idx] = QVector3D(worldX, m_heightData[idx], worldZ);
  151. }
  152. }
  153. auto accumulateNormal = [&](int i0, int i1, int i2) {
  154. const QVector3D &v0 = positions[i0];
  155. const QVector3D &v1 = positions[i1];
  156. const QVector3D &v2 = positions[i2];
  157. QVector3D normal = QVector3D::crossProduct(v1 - v0, v2 - v0);
  158. normals[i0] += normal;
  159. normals[i1] += normal;
  160. normals[i2] += normal;
  161. };
  162. auto sampleHeightAt = [&](float gx, float gz) {
  163. gx = std::clamp(gx, 0.0f, float(m_width - 1));
  164. gz = std::clamp(gz, 0.0f, float(m_height - 1));
  165. int x0 = int(std::floor(gx));
  166. int z0 = int(std::floor(gz));
  167. int x1 = std::min(x0 + 1, m_width - 1);
  168. int z1 = std::min(z0 + 1, m_height - 1);
  169. float tx = gx - float(x0);
  170. float tz = gz - float(z0);
  171. float h00 = m_heightData[z0 * m_width + x0];
  172. float h10 = m_heightData[z0 * m_width + x1];
  173. float h01 = m_heightData[z1 * m_width + x0];
  174. float h11 = m_heightData[z1 * m_width + x1];
  175. float h0 = h00 * (1.0f - tx) + h10 * tx;
  176. float h1 = h01 * (1.0f - tx) + h11 * tx;
  177. return h0 * (1.0f - tz) + h1 * tz;
  178. };
  179. auto normalFromHeightsAt = [&](float gx, float gz) {
  180. float gx0 = std::clamp(gx - 1.0f, 0.0f, float(m_width - 1));
  181. float gx1 = std::clamp(gx + 1.0f, 0.0f, float(m_width - 1));
  182. float gz0 = std::clamp(gz - 1.0f, 0.0f, float(m_height - 1));
  183. float gz1 = std::clamp(gz + 1.0f, 0.0f, float(m_height - 1));
  184. float hL = sampleHeightAt(gx0, gz);
  185. float hR = sampleHeightAt(gx1, gz);
  186. float hD = sampleHeightAt(gx, gz0);
  187. float hU = sampleHeightAt(gx, gz1);
  188. QVector3D dx(2.0f * m_tileSize, hR - hL, 0.0f);
  189. QVector3D dz(0.0f, hU - hD, 2.0f * m_tileSize);
  190. QVector3D n = QVector3D::crossProduct(dz, dx);
  191. if (n.lengthSquared() > 0.0f) {
  192. n.normalize();
  193. }
  194. return n.isNull() ? QVector3D(0, 1, 0) : n;
  195. };
  196. for (int z = 0; z < m_height - 1; ++z) {
  197. for (int x = 0; x < m_width - 1; ++x) {
  198. int idx0 = z * m_width + x;
  199. int idx1 = idx0 + 1;
  200. int idx2 = (z + 1) * m_width + x;
  201. int idx3 = idx2 + 1;
  202. accumulateNormal(idx0, idx1, idx2);
  203. accumulateNormal(idx2, idx1, idx3);
  204. }
  205. }
  206. for (int i = 0; i < vertexCount; ++i) {
  207. normals[i].normalize();
  208. if (normals[i].isNull()) {
  209. normals[i] = QVector3D(0.0f, 1.0f, 0.0f);
  210. }
  211. faceAccum[i] = normals[i];
  212. }
  213. {
  214. std::vector<QVector3D> filtered = normals;
  215. auto getN = [&](int x, int z) -> QVector3D & {
  216. return normals[z * m_width + x];
  217. };
  218. for (int z = 1; z < m_height - 1; ++z) {
  219. for (int x = 1; x < m_width - 1; ++x) {
  220. const int idx = z * m_width + x;
  221. const float h0 = m_heightData[idx];
  222. const float nh = (h0 - minH) / heightRange;
  223. const float hL = m_heightData[z * m_width + (x - 1)];
  224. const float hR = m_heightData[z * m_width + (x + 1)];
  225. const float hD = m_heightData[(z - 1) * m_width + x];
  226. const float hU = m_heightData[(z + 1) * m_width + x];
  227. const float avgNbr = 0.25f * (hL + hR + hD + hU);
  228. const float convexity = h0 - avgNbr;
  229. const QVector3D n0 = normals[idx];
  230. const float slope = 1.0f - std::clamp(n0.y(), 0.0f, 1.0f);
  231. const float ridgeS = smooth(0.35f, 0.70f, slope);
  232. const float ridgeC = smooth(0.00f, 0.20f, convexity);
  233. const float ridgeFactor =
  234. std::clamp(0.5f * ridgeS + 0.5f * ridgeC, 0.0f, 1.0f);
  235. const float baseBoost = 0.6f * (1.0f - nh);
  236. QVector3D acc(0, 0, 0);
  237. float wsum = 0.0f;
  238. for (int dz = -1; dz <= 1; ++dz) {
  239. for (int dx = -1; dx <= 1; ++dx) {
  240. const int nx = x + dx;
  241. const int nz = z + dz;
  242. const int nIdx = nz * m_width + nx;
  243. const float dh = std::abs(m_heightData[nIdx] - h0);
  244. const QVector3D nn = getN(nx, nz);
  245. const float ndot = std::max(0.0f, QVector3D::dotProduct(n0, nn));
  246. const float w_h = 1.0f / (1.0f + 2.0f * dh);
  247. const float w_n = std::pow(ndot, 8.0f);
  248. const float w_b = 1.0f + baseBoost;
  249. const float w_r = 1.0f - ridgeFactor * 0.85f;
  250. const float w = w_h * w_n * w_b * w_r;
  251. acc += nn * w;
  252. wsum += w;
  253. }
  254. }
  255. QVector3D nFiltered = (wsum > 0.0f) ? (acc / wsum) : n0;
  256. nFiltered.normalize();
  257. const QVector3D nOrig = faceAccum[idx];
  258. const QVector3D nFinal =
  259. (ridgeFactor > 0.0f)
  260. ? (nFiltered * (1.0f - ridgeFactor) + nOrig * ridgeFactor)
  261. : nFiltered;
  262. filtered[idx] = nFinal.normalized();
  263. }
  264. }
  265. normals.swap(filtered);
  266. }
  267. auto quadSection = [&](Game::Map::TerrainType a, Game::Map::TerrainType b,
  268. Game::Map::TerrainType c, Game::Map::TerrainType d) {
  269. int priorityA = sectionFor(a);
  270. int priorityB = sectionFor(b);
  271. int priorityC = sectionFor(c);
  272. int priorityD = sectionFor(d);
  273. int result = priorityA;
  274. result = std::max(result, priorityB);
  275. result = std::max(result, priorityC);
  276. result = std::max(result, priorityD);
  277. return result;
  278. };
  279. const int chunkSize = 16;
  280. std::size_t totalTriangles = 0;
  281. for (int chunkZ = 0; chunkZ < m_height - 1; chunkZ += chunkSize) {
  282. int chunkMaxZ = std::min(chunkZ + chunkSize, m_height - 1);
  283. for (int chunkX = 0; chunkX < m_width - 1; chunkX += chunkSize) {
  284. int chunkMaxX = std::min(chunkX + chunkSize, m_width - 1);
  285. struct SectionData {
  286. std::vector<Vertex> vertices;
  287. std::vector<unsigned int> indices;
  288. std::unordered_map<int, unsigned int> remap;
  289. float heightSum = 0.0f;
  290. int heightCount = 0;
  291. float rotationDeg = 0.0f;
  292. bool flipU = false;
  293. float tint = 1.0f;
  294. QVector3D normalSum = QVector3D(0, 0, 0);
  295. float slopeSum = 0.0f;
  296. float heightVarSum = 0.0f;
  297. int statCount = 0;
  298. float aoSum = 0.0f;
  299. int aoCount = 0;
  300. };
  301. SectionData sections[3];
  302. uint32_t chunkSeed = hashCoords(chunkX, chunkZ, m_noiseSeed);
  303. uint32_t variantSeed = chunkSeed ^ 0x9e3779b9u;
  304. float rotationStep = static_cast<float>((variantSeed >> 5) & 3) * 90.0f;
  305. bool flip = ((variantSeed >> 7) & 1u) != 0u;
  306. static const float tintVariants[7] = {0.9f, 0.94f, 0.97f, 1.0f,
  307. 1.03f, 1.06f, 1.1f};
  308. float tint = tintVariants[(variantSeed >> 12) % 7];
  309. for (auto &section : sections) {
  310. section.rotationDeg = rotationStep;
  311. section.flipU = flip;
  312. section.tint = tint;
  313. }
  314. auto ensureVertex = [&](SectionData &section,
  315. int globalIndex) -> unsigned int {
  316. auto it = section.remap.find(globalIndex);
  317. if (it != section.remap.end()) {
  318. return it->second;
  319. }
  320. Vertex v{};
  321. const QVector3D &pos = positions[globalIndex];
  322. const QVector3D &normal = normals[globalIndex];
  323. v.position[0] = pos.x();
  324. v.position[1] = pos.y();
  325. v.position[2] = pos.z();
  326. v.normal[0] = normal.x();
  327. v.normal[1] = normal.y();
  328. v.normal[2] = normal.z();
  329. float texScale = 0.2f / std::max(1.0f, m_tileSize);
  330. float uu = pos.x() * texScale;
  331. float vv = pos.z() * texScale;
  332. if (section.flipU) {
  333. uu = -uu;
  334. }
  335. float ru = uu, rv = vv;
  336. switch (static_cast<int>(section.rotationDeg)) {
  337. case 90: {
  338. float t = ru;
  339. ru = -rv;
  340. rv = t;
  341. } break;
  342. case 180:
  343. ru = -ru;
  344. rv = -rv;
  345. break;
  346. case 270: {
  347. float t = ru;
  348. ru = rv;
  349. rv = -t;
  350. } break;
  351. default:
  352. break;
  353. }
  354. v.texCoord[0] = ru;
  355. v.texCoord[1] = rv;
  356. section.vertices.push_back(v);
  357. unsigned int localIndex =
  358. static_cast<unsigned int>(section.vertices.size() - 1);
  359. section.remap.emplace(globalIndex, localIndex);
  360. section.normalSum += normal;
  361. return localIndex;
  362. };
  363. for (int z = chunkZ; z < chunkMaxZ; ++z) {
  364. for (int x = chunkX; x < chunkMaxX; ++x) {
  365. int idx0 = z * m_width + x;
  366. int idx1 = idx0 + 1;
  367. int idx2 = (z + 1) * m_width + x;
  368. int idx3 = idx2 + 1;
  369. int sectionIndex =
  370. quadSection(m_terrainTypes[idx0], m_terrainTypes[idx1],
  371. m_terrainTypes[idx2], m_terrainTypes[idx3]);
  372. if (sectionIndex > 0) {
  373. SectionData &section = sections[sectionIndex];
  374. unsigned int v0 = ensureVertex(section, idx0);
  375. unsigned int v1 = ensureVertex(section, idx1);
  376. unsigned int v2 = ensureVertex(section, idx2);
  377. unsigned int v3 = ensureVertex(section, idx3);
  378. section.indices.push_back(v0);
  379. section.indices.push_back(v1);
  380. section.indices.push_back(v2);
  381. section.indices.push_back(v2);
  382. section.indices.push_back(v1);
  383. section.indices.push_back(v3);
  384. float quadHeight = (m_heightData[idx0] + m_heightData[idx1] +
  385. m_heightData[idx2] + m_heightData[idx3]) *
  386. 0.25f;
  387. section.heightSum += quadHeight;
  388. section.heightCount += 1;
  389. float nY = (normals[idx0].y() + normals[idx1].y() +
  390. normals[idx2].y() + normals[idx3].y()) *
  391. 0.25f;
  392. float slope = 1.0f - std::clamp(nY, 0.0f, 1.0f);
  393. section.slopeSum += slope;
  394. float hmin =
  395. std::min(std::min(m_heightData[idx0], m_heightData[idx1]),
  396. std::min(m_heightData[idx2], m_heightData[idx3]));
  397. float hmax =
  398. std::max(std::max(m_heightData[idx0], m_heightData[idx1]),
  399. std::max(m_heightData[idx2], m_heightData[idx3]));
  400. section.heightVarSum += (hmax - hmin);
  401. section.statCount += 1;
  402. auto H = [&](int gx, int gz) {
  403. gx = std::clamp(gx, 0, m_width - 1);
  404. gz = std::clamp(gz, 0, m_height - 1);
  405. return m_heightData[gz * m_width + gx];
  406. };
  407. int cx = x, cz = z;
  408. float hC = quadHeight;
  409. float ao = 0.0f;
  410. ao += std::max(0.0f, H(cx - 1, cz) - hC);
  411. ao += std::max(0.0f, H(cx + 1, cz) - hC);
  412. ao += std::max(0.0f, H(cx, cz - 1) - hC);
  413. ao += std::max(0.0f, H(cx, cz + 1) - hC);
  414. ao = std::clamp(ao * 0.15f, 0.0f, 1.0f);
  415. section.aoSum += ao;
  416. section.aoCount += 1;
  417. }
  418. }
  419. }
  420. for (int i = 0; i < 3; ++i) {
  421. SectionData &section = sections[i];
  422. if (section.indices.empty()) {
  423. continue;
  424. }
  425. auto mesh = std::make_unique<Mesh>(section.vertices, section.indices);
  426. if (!mesh) {
  427. continue;
  428. }
  429. ChunkMesh chunk;
  430. chunk.mesh = std::move(mesh);
  431. chunk.minX = chunkX;
  432. chunk.maxX = chunkMaxX - 1;
  433. chunk.minZ = chunkZ;
  434. chunk.maxZ = chunkMaxZ - 1;
  435. chunk.type = (i == 0) ? Game::Map::TerrainType::Flat
  436. : (i == 1) ? Game::Map::TerrainType::Hill
  437. : Game::Map::TerrainType::Mountain;
  438. chunk.averageHeight =
  439. (section.heightCount > 0)
  440. ? section.heightSum / float(section.heightCount)
  441. : 0.0f;
  442. const float nhChunk = (chunk.averageHeight - minH) / heightRange;
  443. const float avgSlope =
  444. (section.statCount > 0)
  445. ? (section.slopeSum / float(section.statCount))
  446. : 0.0f;
  447. const float roughness =
  448. (section.statCount > 0)
  449. ? (section.heightVarSum / float(section.statCount))
  450. : 0.0f;
  451. const float centerGX = 0.5f * (chunk.minX + chunk.maxX);
  452. const float centerGZ = 0.5f * (chunk.minZ + chunk.maxZ);
  453. auto Hgrid = [&](int gx, int gz) {
  454. gx = std::clamp(gx, 0, m_width - 1);
  455. gz = std::clamp(gz, 0, m_height - 1);
  456. return m_heightData[gz * m_width + gx];
  457. };
  458. const int cxi = int(centerGX);
  459. const int czi = int(centerGZ);
  460. const float hC = Hgrid(cxi, czi);
  461. const float hL = Hgrid(cxi - 1, czi);
  462. const float hR = Hgrid(cxi + 1, czi);
  463. const float hD = Hgrid(cxi, czi - 1);
  464. const float hU = Hgrid(cxi, czi + 1);
  465. const float convexity = hC - 0.25f * (hL + hR + hD + hU);
  466. const float edgeFactor = smooth(0.25f, 0.55f, avgSlope);
  467. const float entranceFactor =
  468. (1.0f - edgeFactor) * smooth(0.00f, 0.15f, -convexity);
  469. const float plateauFlat = 1.0f - smooth(0.10f, 0.25f, avgSlope);
  470. const float plateauHeight = smooth(0.60f, 0.80f, nhChunk);
  471. const float plateauFactor = plateauFlat * plateauHeight;
  472. QVector3D baseColor = getTerrainColor(chunk.type, chunk.averageHeight);
  473. QVector3D rockTint = m_biomeSettings.rockLow;
  474. float slopeMix = std::clamp(
  475. avgSlope * ((chunk.type == Game::Map::TerrainType::Flat) ? 0.30f
  476. : (chunk.type == Game::Map::TerrainType::Hill) ? 0.60f
  477. : 0.90f),
  478. 0.0f, 1.0f);
  479. slopeMix += 0.15f * edgeFactor;
  480. slopeMix -= 0.10f * entranceFactor;
  481. slopeMix -= 0.08f * plateauFactor;
  482. slopeMix = std::clamp(slopeMix, 0.0f, 1.0f);
  483. float centerWX = (centerGX - halfWidth) * m_tileSize;
  484. float centerWZ = (centerGZ - halfHeight) * m_tileSize;
  485. float macro = valueNoise(centerWX * 0.02f, centerWZ * 0.02f,
  486. m_noiseSeed ^ 0x51C3u);
  487. float macroShade = 0.9f + 0.2f * macro;
  488. float aoAvg = (section.aoCount > 0)
  489. ? (section.aoSum / float(section.aoCount))
  490. : 0.0f;
  491. float aoShade = 1.0f - 0.35f * aoAvg;
  492. QVector3D avgN = section.normalSum;
  493. if (avgN.lengthSquared() > 0.0f) {
  494. avgN.normalize();
  495. }
  496. QVector3D north(0, 0, 1);
  497. float northness = std::clamp(
  498. QVector3D::dotProduct(avgN, north) * 0.5f + 0.5f, 0.0f, 1.0f);
  499. QVector3D coolTint(0.96f, 1.02f, 1.04f);
  500. QVector3D warmTint(1.03f, 1.0f, 0.97f);
  501. QVector3D aspectTint =
  502. coolTint * northness + warmTint * (1.0f - northness);
  503. float featureBright =
  504. 1.0f + 0.08f * plateauFactor - 0.05f * entranceFactor;
  505. QVector3D featureTint =
  506. QVector3D(1.0f + 0.03f * plateauFactor - 0.03f * entranceFactor,
  507. 1.0f + 0.01f * plateauFactor - 0.01f * entranceFactor,
  508. 1.0f - 0.02f * plateauFactor + 0.03f * entranceFactor);
  509. chunk.tint = section.tint;
  510. QVector3D color = baseColor * (1.0f - slopeMix) + rockTint * slopeMix;
  511. color = applyTint(color, chunk.tint);
  512. color *= macroShade;
  513. color.setX(color.x() * aspectTint.x() * featureTint.x());
  514. color.setY(color.y() * aspectTint.y() * featureTint.y());
  515. color.setZ(color.z() * aspectTint.z() * featureTint.z());
  516. color *= aoShade * featureBright;
  517. color = color * 0.96f + QVector3D(0.04f, 0.04f, 0.04f);
  518. chunk.color = clamp01(color);
  519. TerrainChunkParams params;
  520. auto tintColor = [&](const QVector3D &base) {
  521. return clamp01(applyTint(base, chunk.tint));
  522. };
  523. params.grassPrimary = tintColor(m_biomeSettings.grassPrimary);
  524. params.grassSecondary = tintColor(m_biomeSettings.grassSecondary);
  525. params.grassDry = tintColor(m_biomeSettings.grassDry);
  526. params.soilColor = tintColor(m_biomeSettings.soilColor);
  527. params.rockLow = tintColor(m_biomeSettings.rockLow);
  528. params.rockHigh = tintColor(m_biomeSettings.rockHigh);
  529. params.tileSize = std::max(0.001f, m_tileSize);
  530. params.macroNoiseScale = m_biomeSettings.terrainMacroNoiseScale;
  531. params.detailNoiseScale = m_biomeSettings.terrainDetailNoiseScale;
  532. float slopeThreshold = m_biomeSettings.terrainRockThreshold;
  533. float sharpnessMul = 1.0f;
  534. if (chunk.type == Game::Map::TerrainType::Hill) {
  535. slopeThreshold -= 0.08f;
  536. sharpnessMul = 1.25f;
  537. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  538. slopeThreshold -= 0.16f;
  539. sharpnessMul = 1.60f;
  540. }
  541. slopeThreshold -= 0.05f * edgeFactor;
  542. slopeThreshold += 0.04f * entranceFactor;
  543. slopeThreshold = std::clamp(
  544. slopeThreshold - std::clamp(avgSlope * 0.20f, 0.0f, 0.12f), 0.05f,
  545. 0.9f);
  546. params.slopeRockThreshold = slopeThreshold;
  547. params.slopeRockSharpness =
  548. std::max(1.0f, m_biomeSettings.terrainRockSharpness * sharpnessMul);
  549. float soilHeight = m_biomeSettings.terrainSoilHeight;
  550. if (chunk.type == Game::Map::TerrainType::Hill) {
  551. soilHeight -= 0.06f;
  552. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  553. soilHeight -= 0.12f;
  554. }
  555. soilHeight += 0.05f * entranceFactor - 0.03f * plateauFactor;
  556. params.soilBlendHeight = soilHeight;
  557. params.soilBlendSharpness =
  558. std::max(0.75f, m_biomeSettings.terrainSoilSharpness *
  559. (chunk.type == Game::Map::TerrainType::Mountain
  560. ? 0.80f
  561. : 0.95f));
  562. const uint32_t noiseKeyA =
  563. hashCoords(chunk.minX, chunk.minZ, m_noiseSeed ^ 0xB5297A4Du);
  564. const uint32_t noiseKeyB =
  565. hashCoords(chunk.minX, chunk.minZ, m_noiseSeed ^ 0x68E31DA4u);
  566. params.noiseOffset = QVector2D(hashTo01(noiseKeyA) * 256.0f,
  567. hashTo01(noiseKeyB) * 256.0f);
  568. float baseAmp =
  569. m_biomeSettings.heightNoiseAmplitude *
  570. (0.7f + 0.3f * std::clamp(roughness * 0.6f, 0.0f, 1.0f));
  571. if (chunk.type == Game::Map::TerrainType::Mountain) {
  572. baseAmp *= 1.25f;
  573. }
  574. baseAmp *= (1.0f + 0.10f * edgeFactor - 0.08f * plateauFactor -
  575. 0.06f * entranceFactor);
  576. params.heightNoiseStrength = baseAmp;
  577. params.heightNoiseFrequency = m_biomeSettings.heightNoiseFrequency;
  578. params.ambientBoost =
  579. m_biomeSettings.terrainAmbientBoost *
  580. ((chunk.type == Game::Map::TerrainType::Mountain) ? 0.90f : 0.95f);
  581. params.rockDetailStrength =
  582. m_biomeSettings.terrainRockDetailStrength *
  583. (0.75f + 0.35f * std::clamp(avgSlope * 1.2f, 0.0f, 1.0f) +
  584. 0.15f * edgeFactor - 0.10f * plateauFactor -
  585. 0.08f * entranceFactor);
  586. params.tint = clamp01(QVector3D(chunk.tint, chunk.tint, chunk.tint));
  587. params.lightDirection = QVector3D(0.35f, 0.8f, 0.45f);
  588. chunk.params = params;
  589. totalTriangles += chunk.mesh->getIndices().size() / 3;
  590. m_chunks.push_back(std::move(chunk));
  591. }
  592. }
  593. }
  594. }
  595. QVector3D TerrainRenderer::getTerrainColor(Game::Map::TerrainType type,
  596. float height) const {
  597. switch (type) {
  598. case Game::Map::TerrainType::Mountain:
  599. if (height > 4.0f) {
  600. return m_biomeSettings.rockHigh;
  601. }
  602. return m_biomeSettings.rockLow;
  603. case Game::Map::TerrainType::Hill: {
  604. float t = std::clamp(height / 3.0f, 0.0f, 1.0f);
  605. QVector3D grass = m_biomeSettings.grassSecondary * (1.0f - t) +
  606. m_biomeSettings.grassDry * t;
  607. QVector3D rock =
  608. m_biomeSettings.rockLow * (1.0f - t) + m_biomeSettings.rockHigh * t;
  609. float rockBlend = std::clamp(0.25f + 0.5f * t, 0.0f, 0.75f);
  610. return grass * (1.0f - rockBlend) + rock * rockBlend;
  611. }
  612. case Game::Map::TerrainType::Flat:
  613. default: {
  614. float moisture = std::clamp((height - 0.5f) * 0.2f, 0.0f, 0.4f);
  615. QVector3D base = m_biomeSettings.grassPrimary * (1.0f - moisture) +
  616. m_biomeSettings.grassSecondary * moisture;
  617. float dryBlend = std::clamp((height - 2.0f) * 0.12f, 0.0f, 0.3f);
  618. return base * (1.0f - dryBlend) + m_biomeSettings.grassDry * dryBlend;
  619. }
  620. }
  621. }
  622. } // namespace Render::GL