terrain_renderer.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. #include "terrain_renderer.h"
  2. #include "../../game/map/visibility_service.h"
  3. #include "../gl/mesh.h"
  4. #include "../gl/render_constants.h"
  5. #include "../gl/resources.h"
  6. #include "../scene_renderer.h"
  7. #include "ground/terrain_gpu.h"
  8. #include "ground_utils.h"
  9. #include "map/terrain.h"
  10. #include <QDebug>
  11. #include <QElapsedTimer>
  12. #include <QQuaternion>
  13. #include <QVector2D>
  14. #include <QtGlobal>
  15. #include <algorithm>
  16. #include <cmath>
  17. #include <cstddef>
  18. #include <cstdint>
  19. #include <limits>
  20. #include <memory>
  21. #include <qelapsedtimer.h>
  22. #include <qglobal.h>
  23. #include <qmatrix4x4.h>
  24. #include <qvectornd.h>
  25. #include <unordered_map>
  26. #include <utility>
  27. #include <vector>
  28. namespace {
  29. using std::uint32_t;
  30. using namespace Render::GL::BitShift;
  31. using namespace Render::GL::Geometry;
  32. using namespace Render::GL::HashXorShift;
  33. using namespace Render::Ground;
  34. const QMatrix4x4 k_identity_matrix;
  35. inline auto applyTint(const QVector3D &color, float tint) -> QVector3D {
  36. QVector3D const c = color * tint;
  37. return {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 auto clamp01(const QVector3D &c) -> QVector3D {
  41. return {std::clamp(c.x(), 0.0F, 1.0F), std::clamp(c.y(), 0.0F, 1.0F),
  42. std::clamp(c.z(), 0.0F, 1.0F)};
  43. }
  44. inline auto linstep(float a, float b, float x) -> float {
  45. return std::clamp((x - a) / std::max(1e-6F, (b - a)), 0.0F, 1.0F);
  46. }
  47. inline auto smooth(float a, float b, float x) -> float {
  48. float const t = linstep(a, b, x);
  49. return t * t * (3.0F - 2.0F * t);
  50. }
  51. inline auto valueNoise(float x, float z, uint32_t salt = 0U) -> float {
  52. int const x0 = int(std::floor(x));
  53. int const z0 = int(std::floor(z));
  54. int const x1 = x0 + 1;
  55. int const z1 = z0 + 1;
  56. float const tx = x - float(x0);
  57. float const tz = z - float(z0);
  58. float const n00 = hash_to_01(hash_coords(x0, z0, salt));
  59. float const n10 = hash_to_01(hash_coords(x1, z0, salt));
  60. float const n01 = hash_to_01(hash_coords(x0, z1, salt));
  61. float const n11 = hash_to_01(hash_coords(x1, z1, salt));
  62. float const nx0 = n00 * (1 - tx) + n10 * tx;
  63. float const nx1 = n01 * (1 - tx) + n11 * tx;
  64. return nx0 * (1 - tz) + nx1 * tz;
  65. }
  66. } // namespace
  67. namespace Render::GL {
  68. TerrainRenderer::TerrainRenderer() = default;
  69. TerrainRenderer::~TerrainRenderer() = default;
  70. void TerrainRenderer::configure(const Game::Map::TerrainHeightMap &height_map,
  71. const Game::Map::BiomeSettings &biomeSettings) {
  72. m_width = height_map.getWidth();
  73. m_height = height_map.getHeight();
  74. m_tile_size = height_map.getTileSize();
  75. m_heightData = height_map.getHeightData();
  76. m_terrain_types = height_map.getTerrainTypes();
  77. m_biomeSettings = biomeSettings;
  78. m_noiseSeed = biomeSettings.seed;
  79. buildMeshes();
  80. }
  81. void TerrainRenderer::submit(Renderer &renderer, ResourceManager *resources) {
  82. if (m_chunks.empty()) {
  83. return;
  84. }
  85. Q_UNUSED(resources);
  86. auto &visibility = Game::Map::VisibilityService::instance();
  87. const bool use_visibility = visibility.isInitialized();
  88. for (const auto &chunk : m_chunks) {
  89. if (!chunk.mesh) {
  90. continue;
  91. }
  92. if (use_visibility) {
  93. bool any_visible = false;
  94. for (int gz = chunk.minZ; gz <= chunk.maxZ && !any_visible; ++gz) {
  95. for (int gx = chunk.minX; gx <= chunk.maxX; ++gx) {
  96. if (visibility.stateAt(gx, gz) ==
  97. Game::Map::VisibilityState::Visible) {
  98. any_visible = true;
  99. break;
  100. }
  101. }
  102. }
  103. if (!any_visible) {
  104. continue;
  105. }
  106. }
  107. renderer.terrainChunk(chunk.mesh.get(), k_identity_matrix, chunk.params,
  108. 0x0080U, true, 0.0F);
  109. }
  110. }
  111. auto TerrainRenderer::sectionFor(Game::Map::TerrainType type) -> int {
  112. switch (type) {
  113. case Game::Map::TerrainType::Mountain:
  114. return 2;
  115. case Game::Map::TerrainType::Hill:
  116. return 1;
  117. case Game::Map::TerrainType::Flat:
  118. default:
  119. return 0;
  120. }
  121. }
  122. void TerrainRenderer::buildMeshes() {
  123. QElapsedTimer timer;
  124. timer.start();
  125. m_chunks.clear();
  126. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  127. return;
  128. }
  129. float min_h = std::numeric_limits<float>::infinity();
  130. float max_h = -std::numeric_limits<float>::infinity();
  131. for (float const h : m_heightData) {
  132. min_h = std::min(min_h, h);
  133. max_h = std::max(max_h, h);
  134. }
  135. const float height_range = std::max(1e-4F, max_h - min_h);
  136. const float half_width = m_width * 0.5F - 0.5F;
  137. const float half_height = m_height * 0.5F - 0.5F;
  138. const int vertex_count = m_width * m_height;
  139. std::vector<QVector3D> positions(vertex_count);
  140. std::vector<QVector3D> normals(vertex_count, QVector3D(0.0F, 0.0F, 0.0F));
  141. std::vector<QVector3D> face_accum(vertex_count, QVector3D(0, 0, 0));
  142. for (int z = 0; z < m_height; ++z) {
  143. for (int x = 0; x < m_width; ++x) {
  144. int const idx = z * m_width + x;
  145. float const world_x = (x - half_width) * m_tile_size;
  146. float const world_z = (z - half_height) * m_tile_size;
  147. positions[idx] = QVector3D(world_x, m_heightData[idx], world_z);
  148. }
  149. }
  150. auto accumulate_normal = [&](int i0, int i1, int i2) {
  151. const QVector3D &v0 = positions[i0];
  152. const QVector3D &v1 = positions[i1];
  153. const QVector3D &v2 = positions[i2];
  154. QVector3D const normal = QVector3D::crossProduct(v1 - v0, v2 - v0);
  155. normals[i0] += normal;
  156. normals[i1] += normal;
  157. normals[i2] += normal;
  158. };
  159. auto sample_height_at = [&](float gx, float gz) {
  160. gx = std::clamp(gx, 0.0F, float(m_width - 1));
  161. gz = std::clamp(gz, 0.0F, float(m_height - 1));
  162. int const x0 = int(std::floor(gx));
  163. int const z0 = int(std::floor(gz));
  164. int const x1 = std::min(x0 + 1, m_width - 1);
  165. int const z1 = std::min(z0 + 1, m_height - 1);
  166. float const tx = gx - float(x0);
  167. float const tz = gz - float(z0);
  168. float const h00 = m_heightData[z0 * m_width + x0];
  169. float const h10 = m_heightData[z0 * m_width + x1];
  170. float const h01 = m_heightData[z1 * m_width + x0];
  171. float const h11 = m_heightData[z1 * m_width + x1];
  172. float const h0 = h00 * (1.0F - tx) + h10 * tx;
  173. float const h1 = h01 * (1.0F - tx) + h11 * tx;
  174. return h0 * (1.0F - tz) + h1 * tz;
  175. };
  176. auto normal_from_heights_at = [&](float gx, float gz) {
  177. float const gx0 = std::clamp(gx - 1.0F, 0.0F, float(m_width - 1));
  178. float const gx1 = std::clamp(gx + 1.0F, 0.0F, float(m_width - 1));
  179. float const gz0 = std::clamp(gz - 1.0F, 0.0F, float(m_height - 1));
  180. float const gz1 = std::clamp(gz + 1.0F, 0.0F, float(m_height - 1));
  181. float const hL = sample_height_at(gx0, gz);
  182. float const hR = sample_height_at(gx1, gz);
  183. float const hD = sample_height_at(gx, gz0);
  184. float const hU = sample_height_at(gx, gz1);
  185. QVector3D const dx(2.0F * m_tile_size, hR - hL, 0.0F);
  186. QVector3D const dz(0.0F, hU - hD, 2.0F * m_tile_size);
  187. QVector3D n = QVector3D::crossProduct(dz, dx);
  188. if (n.lengthSquared() > 0.0F) {
  189. n.normalize();
  190. }
  191. return n.isNull() ? QVector3D(0, 1, 0) : n;
  192. };
  193. for (int z = 0; z < m_height - 1; ++z) {
  194. for (int x = 0; x < m_width - 1; ++x) {
  195. int const idx0 = z * m_width + x;
  196. int const idx1 = idx0 + 1;
  197. int const idx2 = (z + 1) * m_width + x;
  198. int const idx3 = idx2 + 1;
  199. accumulate_normal(idx0, idx1, idx2);
  200. accumulate_normal(idx2, idx1, idx3);
  201. }
  202. }
  203. for (int i = 0; i < vertex_count; ++i) {
  204. normals[i].normalize();
  205. if (normals[i].isNull()) {
  206. normals[i] = QVector3D(0.0F, 1.0F, 0.0F);
  207. }
  208. face_accum[i] = normals[i];
  209. }
  210. {
  211. std::vector<QVector3D> filtered = normals;
  212. auto getN = [&](int x, int z) -> QVector3D & {
  213. return normals[z * m_width + x];
  214. };
  215. for (int z = 1; z < m_height - 1; ++z) {
  216. for (int x = 1; x < m_width - 1; ++x) {
  217. const int idx = z * m_width + x;
  218. const float h0 = m_heightData[idx];
  219. const float nh = (h0 - min_h) / height_range;
  220. const float hL = m_heightData[z * m_width + (x - 1)];
  221. const float hR = m_heightData[z * m_width + (x + 1)];
  222. const float hD = m_heightData[(z - 1) * m_width + x];
  223. const float hU = m_heightData[(z + 1) * m_width + x];
  224. const float avg_nbr = 0.25F * (hL + hR + hD + hU);
  225. const float convexity = h0 - avg_nbr;
  226. const QVector3D n0 = normals[idx];
  227. const float slope = 1.0F - std::clamp(n0.y(), 0.0F, 1.0F);
  228. const float ridge_s = smooth(0.35F, 0.70F, slope);
  229. const float ridge_c = smooth(0.00F, 0.20F, convexity);
  230. const float ridge_factor =
  231. std::clamp(0.5F * ridge_s + 0.5F * ridge_c, 0.0F, 1.0F);
  232. const float base_boost = 0.6F * (1.0F - nh);
  233. QVector3D acc(0, 0, 0);
  234. float wsum = 0.0F;
  235. for (int dz = -1; dz <= 1; ++dz) {
  236. for (int dx = -1; dx <= 1; ++dx) {
  237. const int nx = x + dx;
  238. const int nz = z + dz;
  239. const int nIdx = nz * m_width + nx;
  240. const float dh = std::abs(m_heightData[nIdx] - h0);
  241. const QVector3D nn = getN(nx, nz);
  242. const float ndot = std::max(0.0F, QVector3D::dotProduct(n0, nn));
  243. const float w_h = 1.0F / (1.0F + 2.0F * dh);
  244. const float w_n = std::pow(ndot, 8.0F);
  245. const float w_b = 1.0F + base_boost;
  246. const float w_r = 1.0F - ridge_factor * 0.85F;
  247. const float w = w_h * w_n * w_b * w_r;
  248. acc += nn * w;
  249. wsum += w;
  250. }
  251. }
  252. QVector3D n_filtered = (wsum > 0.0F) ? (acc / wsum) : n0;
  253. n_filtered.normalize();
  254. const QVector3D n_orig = face_accum[idx];
  255. const QVector3D n_final =
  256. (ridge_factor > 0.0F)
  257. ? (n_filtered * (1.0F - ridge_factor) + n_orig * ridge_factor)
  258. : n_filtered;
  259. filtered[idx] = n_final.normalized();
  260. }
  261. }
  262. normals.swap(filtered);
  263. }
  264. auto quad_section = [&](Game::Map::TerrainType a, Game::Map::TerrainType b,
  265. Game::Map::TerrainType c, Game::Map::TerrainType d) {
  266. int const priority_a = sectionFor(a);
  267. int const priority_b = sectionFor(b);
  268. int const priority_c = sectionFor(c);
  269. int const priority_d = sectionFor(d);
  270. int result = priority_a;
  271. result = std::max(result, priority_b);
  272. result = std::max(result, priority_c);
  273. result = std::max(result, priority_d);
  274. return result;
  275. };
  276. const int chunk_size = DefaultChunkSize;
  277. std::size_t total_triangles = 0;
  278. for (int chunk_z = 0; chunk_z < m_height - 1; chunk_z += chunk_size) {
  279. int const chunk_max_z = std::min(chunk_z + chunk_size, m_height - 1);
  280. for (int chunk_x = 0; chunk_x < m_width - 1; chunk_x += chunk_size) {
  281. int const chunk_max_x = std::min(chunk_x + chunk_size, m_width - 1);
  282. struct SectionData {
  283. std::vector<Vertex> vertices;
  284. std::vector<unsigned int> indices;
  285. std::unordered_map<int, unsigned int> remap;
  286. float heightSum = 0.0F;
  287. int heightCount = 0;
  288. float rotationDeg = 0.0F;
  289. bool flipU = false;
  290. float tint = 1.0F;
  291. QVector3D normalSum = QVector3D(0, 0, 0);
  292. float slopeSum = 0.0F;
  293. float heightVarSum = 0.0F;
  294. int statCount = 0;
  295. float aoSum = 0.0F;
  296. int aoCount = 0;
  297. };
  298. SectionData sections[3];
  299. uint32_t const chunk_seed = hash_coords(chunk_x, chunk_z, m_noiseSeed);
  300. uint32_t const variant_seed = chunk_seed ^ k_golden_ratio;
  301. float const rotation_step =
  302. static_cast<float>((variant_seed >> 5) & 3) * 90.0F;
  303. bool const flip = ((variant_seed >> 7) & 1U) != 0U;
  304. static const float tint_variants[7] = {0.9F, 0.94F, 0.97F, 1.0F,
  305. 1.03F, 1.06F, 1.1F};
  306. float const tint = tint_variants[(variant_seed >> 12) % 7];
  307. for (auto &section : sections) {
  308. section.rotationDeg = rotation_step;
  309. section.flipU = flip;
  310. section.tint = tint;
  311. }
  312. auto ensure_vertex = [&](SectionData &section,
  313. int globalIndex) -> unsigned int {
  314. auto it = section.remap.find(globalIndex);
  315. if (it != section.remap.end()) {
  316. return it->second;
  317. }
  318. Vertex v{};
  319. const QVector3D &pos = positions[globalIndex];
  320. const QVector3D &normal = normals[globalIndex];
  321. v.position[0] = pos.x();
  322. v.position[1] = pos.y();
  323. v.position[2] = pos.z();
  324. v.normal[0] = normal.x();
  325. v.normal[1] = normal.y();
  326. v.normal[2] = normal.z();
  327. float const tex_scale = 0.2F / std::max(1.0F, m_tile_size);
  328. float uu = pos.x() * tex_scale;
  329. float const vv = pos.z() * tex_scale;
  330. if (section.flipU) {
  331. uu = -uu;
  332. }
  333. float ru = uu;
  334. float rv = vv;
  335. switch (static_cast<int>(section.rotationDeg)) {
  336. case 90: {
  337. float const t = ru;
  338. ru = -rv;
  339. rv = t;
  340. } break;
  341. case 180:
  342. ru = -ru;
  343. rv = -rv;
  344. break;
  345. case 270: {
  346. float const t = ru;
  347. ru = rv;
  348. rv = -t;
  349. } break;
  350. default:
  351. break;
  352. }
  353. v.tex_coord[0] = ru;
  354. v.tex_coord[1] = rv;
  355. section.vertices.push_back(v);
  356. auto const local_index =
  357. static_cast<unsigned int>(section.vertices.size() - 1);
  358. section.remap.emplace(globalIndex, local_index);
  359. section.normalSum += normal;
  360. return local_index;
  361. };
  362. for (int z = chunk_z; z < chunk_max_z; ++z) {
  363. for (int x = chunk_x; x < chunk_max_x; ++x) {
  364. int const idx0 = z * m_width + x;
  365. int const idx1 = idx0 + 1;
  366. int const idx2 = (z + 1) * m_width + x;
  367. int const idx3 = idx2 + 1;
  368. int const section_index =
  369. quad_section(m_terrain_types[idx0], m_terrain_types[idx1],
  370. m_terrain_types[idx2], m_terrain_types[idx3]);
  371. if (section_index > 0) {
  372. SectionData &section = sections[section_index];
  373. unsigned int const v0 = ensure_vertex(section, idx0);
  374. unsigned int const v1 = ensure_vertex(section, idx1);
  375. unsigned int const v2 = ensure_vertex(section, idx2);
  376. unsigned int const v3 = ensure_vertex(section, idx3);
  377. section.indices.push_back(v0);
  378. section.indices.push_back(v1);
  379. section.indices.push_back(v2);
  380. section.indices.push_back(v2);
  381. section.indices.push_back(v1);
  382. section.indices.push_back(v3);
  383. float const quad_height =
  384. (m_heightData[idx0] + m_heightData[idx1] + m_heightData[idx2] +
  385. m_heightData[idx3]) *
  386. 0.25F;
  387. section.heightSum += quad_height;
  388. section.heightCount += 1;
  389. float const nY = (normals[idx0].y() + normals[idx1].y() +
  390. normals[idx2].y() + normals[idx3].y()) *
  391. 0.25F;
  392. float const slope = 1.0F - std::clamp(nY, 0.0F, 1.0F);
  393. section.slopeSum += slope;
  394. float const hmin =
  395. std::min(std::min(m_heightData[idx0], m_heightData[idx1]),
  396. std::min(m_heightData[idx2], m_heightData[idx3]));
  397. float const 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 const cx = x;
  408. int const cz = z;
  409. float const hC = quad_height;
  410. float ao = 0.0F;
  411. ao += std::max(0.0F, H(cx - 1, cz) - hC);
  412. ao += std::max(0.0F, H(cx + 1, cz) - hC);
  413. ao += std::max(0.0F, H(cx, cz - 1) - hC);
  414. ao += std::max(0.0F, H(cx, cz + 1) - hC);
  415. ao = std::clamp(ao * 0.15F, 0.0F, 1.0F);
  416. section.aoSum += ao;
  417. section.aoCount += 1;
  418. }
  419. }
  420. }
  421. for (int i = 0; i < 3; ++i) {
  422. SectionData const &section = sections[i];
  423. if (section.indices.empty()) {
  424. continue;
  425. }
  426. auto mesh = std::make_unique<Mesh>(section.vertices, section.indices);
  427. if (!mesh) {
  428. continue;
  429. }
  430. ChunkMesh chunk;
  431. chunk.mesh = std::move(mesh);
  432. chunk.minX = chunk_x;
  433. chunk.maxX = chunk_max_x - 1;
  434. chunk.minZ = chunk_z;
  435. chunk.maxZ = chunk_max_z - 1;
  436. chunk.type = (i == 0) ? Game::Map::TerrainType::Flat
  437. : (i == 1) ? Game::Map::TerrainType::Hill
  438. : Game::Map::TerrainType::Mountain;
  439. chunk.averageHeight =
  440. (section.heightCount > 0)
  441. ? section.heightSum / float(section.heightCount)
  442. : 0.0F;
  443. const float nh_chunk = (chunk.averageHeight - min_h) / height_range;
  444. const float avg_slope =
  445. (section.statCount > 0)
  446. ? (section.slopeSum / float(section.statCount))
  447. : 0.0F;
  448. const float roughness =
  449. (section.statCount > 0)
  450. ? (section.heightVarSum / float(section.statCount))
  451. : 0.0F;
  452. const float center_gx = 0.5F * (chunk.minX + chunk.maxX);
  453. const float center_gz = 0.5F * (chunk.minZ + chunk.maxZ);
  454. auto hgrid = [&](int gx, int gz) {
  455. gx = std::clamp(gx, 0, m_width - 1);
  456. gz = std::clamp(gz, 0, m_height - 1);
  457. return m_heightData[gz * m_width + gx];
  458. };
  459. const int cxi = int(center_gx);
  460. const int czi = int(center_gz);
  461. const float hC = hgrid(cxi, czi);
  462. const float hL = hgrid(cxi - 1, czi);
  463. const float hR = hgrid(cxi + 1, czi);
  464. const float hD = hgrid(cxi, czi - 1);
  465. const float hU = hgrid(cxi, czi + 1);
  466. const float convexity = hC - 0.25F * (hL + hR + hD + hU);
  467. const float edge_factor = smooth(0.25F, 0.55F, avg_slope);
  468. const float entrance_factor =
  469. (1.0F - edge_factor) * smooth(0.00F, 0.15F, -convexity);
  470. const float plateau_flat = 1.0F - smooth(0.10F, 0.25F, avg_slope);
  471. const float plateau_height = smooth(0.60F, 0.80F, nh_chunk);
  472. const float plateau_factor = plateau_flat * plateau_height;
  473. QVector3D const base_color =
  474. getTerrainColor(chunk.type, chunk.averageHeight);
  475. QVector3D const rock_tint = m_biomeSettings.rockLow;
  476. float slope_mix = std::clamp(
  477. avg_slope * ((chunk.type == Game::Map::TerrainType::Flat) ? 0.30F
  478. : (chunk.type == Game::Map::TerrainType::Hill)
  479. ? 0.60F
  480. : 0.90F),
  481. 0.0F, 1.0F);
  482. slope_mix += 0.15F * edge_factor;
  483. slope_mix -= 0.10F * entrance_factor;
  484. slope_mix -= 0.08F * plateau_factor;
  485. slope_mix = std::clamp(slope_mix, 0.0F, 1.0F);
  486. float const center_wx = (center_gx - half_width) * m_tile_size;
  487. float const center_wz = (center_gz - half_height) * m_tile_size;
  488. float const macro = valueNoise(center_wx * 0.02F, center_wz * 0.02F,
  489. m_noiseSeed ^ 0x51C3U);
  490. float const macro_shade = 0.9F + 0.2F * macro;
  491. float const ao_avg = (section.aoCount > 0)
  492. ? (section.aoSum / float(section.aoCount))
  493. : 0.0F;
  494. float const ao_shade = 1.0F - 0.35F * ao_avg;
  495. QVector3D avgN = section.normalSum;
  496. if (avgN.lengthSquared() > 0.0F) {
  497. avgN.normalize();
  498. }
  499. QVector3D const north(0, 0, 1);
  500. float const northness = std::clamp(
  501. QVector3D::dotProduct(avgN, north) * 0.5F + 0.5F, 0.0F, 1.0F);
  502. QVector3D const cool_tint(0.96F, 1.02F, 1.04F);
  503. QVector3D const warm_tint(1.03F, 1.0F, 0.97F);
  504. QVector3D const aspect_tint =
  505. cool_tint * northness + warm_tint * (1.0F - northness);
  506. float const feature_bright =
  507. 1.0F + 0.08F * plateau_factor - 0.05F * entrance_factor;
  508. QVector3D const feature_tint =
  509. QVector3D(1.0F + 0.03F * plateau_factor - 0.03F * entrance_factor,
  510. 1.0F + 0.01F * plateau_factor - 0.01F * entrance_factor,
  511. 1.0F - 0.02F * plateau_factor + 0.03F * entrance_factor);
  512. chunk.tint = section.tint;
  513. QVector3D color =
  514. base_color * (1.0F - slope_mix) + rock_tint * slope_mix;
  515. color = applyTint(color, chunk.tint);
  516. color *= macro_shade;
  517. color.setX(color.x() * aspect_tint.x() * feature_tint.x());
  518. color.setY(color.y() * aspect_tint.y() * feature_tint.y());
  519. color.setZ(color.z() * aspect_tint.z() * feature_tint.z());
  520. color *= ao_shade * feature_bright;
  521. color = color * 0.96F + QVector3D(0.04F, 0.04F, 0.04F);
  522. chunk.color = clamp01(color);
  523. TerrainChunkParams params;
  524. auto tint_color = [&](const QVector3D &base) {
  525. return clamp01(applyTint(base, chunk.tint));
  526. };
  527. params.grassPrimary = tint_color(m_biomeSettings.grassPrimary);
  528. params.grassSecondary = tint_color(m_biomeSettings.grassSecondary);
  529. params.grassDry = tint_color(m_biomeSettings.grassDry);
  530. params.soilColor = tint_color(m_biomeSettings.soilColor);
  531. params.rockLow = tint_color(m_biomeSettings.rockLow);
  532. params.rockHigh = tint_color(m_biomeSettings.rockHigh);
  533. params.tile_size = std::max(0.001F, m_tile_size);
  534. params.macroNoiseScale = m_biomeSettings.terrainMacroNoiseScale;
  535. params.detail_noiseScale = m_biomeSettings.terrainDetailNoiseScale;
  536. float slope_threshold = m_biomeSettings.terrainRockThreshold;
  537. float sharpness_mul = 1.0F;
  538. if (chunk.type == Game::Map::TerrainType::Hill) {
  539. slope_threshold -= 0.08F;
  540. sharpness_mul = 1.25F;
  541. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  542. slope_threshold -= 0.16F;
  543. sharpness_mul = 1.60F;
  544. }
  545. slope_threshold -= 0.05F * edge_factor;
  546. slope_threshold += 0.04F * entrance_factor;
  547. slope_threshold = std::clamp(
  548. slope_threshold - std::clamp(avg_slope * 0.20F, 0.0F, 0.12F), 0.05F,
  549. 0.9F);
  550. params.slopeRockThreshold = slope_threshold;
  551. params.slopeRockSharpness = std::max(
  552. 1.0F, m_biomeSettings.terrainRockSharpness * sharpness_mul);
  553. float soil_height = m_biomeSettings.terrainSoilHeight;
  554. if (chunk.type == Game::Map::TerrainType::Hill) {
  555. soil_height -= 0.06F;
  556. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  557. soil_height -= 0.12F;
  558. }
  559. soil_height += 0.05F * entrance_factor - 0.03F * plateau_factor;
  560. params.soilBlendHeight = soil_height;
  561. params.soilBlendSharpness =
  562. std::max(0.75F, m_biomeSettings.terrainSoilSharpness *
  563. (chunk.type == Game::Map::TerrainType::Mountain
  564. ? 0.80F
  565. : 0.95F));
  566. const uint32_t noise_key_a =
  567. hash_coords(chunk.minX, chunk.minZ, m_noiseSeed ^ 0xB5297A4DU);
  568. const uint32_t noise_key_b =
  569. hash_coords(chunk.minX, chunk.minZ, m_noiseSeed ^ 0x68E31DA4U);
  570. params.noiseOffset = QVector2D(hash_to_01(noise_key_a) * 256.0F,
  571. hash_to_01(noise_key_b) * 256.0F);
  572. float base_amp =
  573. m_biomeSettings.heightNoiseAmplitude *
  574. (0.7F + 0.3F * std::clamp(roughness * 0.6F, 0.0F, 1.0F));
  575. if (chunk.type == Game::Map::TerrainType::Mountain) {
  576. base_amp *= 1.25F;
  577. }
  578. base_amp *= (1.0F + 0.10F * edge_factor - 0.08F * plateau_factor -
  579. 0.06F * entrance_factor);
  580. params.heightNoiseStrength = base_amp;
  581. params.heightNoiseFrequency = m_biomeSettings.heightNoiseFrequency;
  582. params.ambientBoost =
  583. m_biomeSettings.terrainAmbientBoost *
  584. ((chunk.type == Game::Map::TerrainType::Mountain) ? 0.90F : 0.95F);
  585. params.rockDetailStrength =
  586. m_biomeSettings.terrainRockDetailStrength *
  587. (0.75F + 0.35F * std::clamp(avg_slope * 1.2F, 0.0F, 1.0F) +
  588. 0.15F * edge_factor - 0.10F * plateau_factor -
  589. 0.08F * entrance_factor);
  590. params.tint = clamp01(QVector3D(chunk.tint, chunk.tint, chunk.tint));
  591. params.light_direction = QVector3D(0.35F, 0.8F, 0.45F);
  592. chunk.params = params;
  593. total_triangles += chunk.mesh->getIndices().size() / 3;
  594. m_chunks.push_back(std::move(chunk));
  595. }
  596. }
  597. }
  598. }
  599. auto TerrainRenderer::getTerrainColor(Game::Map::TerrainType type,
  600. float height) const -> QVector3D {
  601. switch (type) {
  602. case Game::Map::TerrainType::Mountain:
  603. if (height > 4.0F) {
  604. return m_biomeSettings.rockHigh;
  605. }
  606. return m_biomeSettings.rockLow;
  607. case Game::Map::TerrainType::Hill: {
  608. float const t = std::clamp(height / 3.0F, 0.0F, 1.0F);
  609. QVector3D const grass = m_biomeSettings.grassSecondary * (1.0F - t) +
  610. m_biomeSettings.grassDry * t;
  611. QVector3D const rock =
  612. m_biomeSettings.rockLow * (1.0F - t) + m_biomeSettings.rockHigh * t;
  613. float const rock_blend = std::clamp(0.25F + 0.5F * t, 0.0F, 0.75F);
  614. return grass * (1.0F - rock_blend) + rock * rock_blend;
  615. }
  616. case Game::Map::TerrainType::Flat:
  617. default: {
  618. float const moisture = std::clamp((height - 0.5F) * 0.2F, 0.0F, 0.4F);
  619. QVector3D const base = m_biomeSettings.grassPrimary * (1.0F - moisture) +
  620. m_biomeSettings.grassSecondary * moisture;
  621. float const dry_blend = std::clamp((height - 2.0F) * 0.12F, 0.0F, 0.3F);
  622. return base * (1.0F - dry_blend) + m_biomeSettings.grassDry * dry_blend;
  623. }
  624. }
  625. }
  626. } // namespace Render::GL