terrain_renderer.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  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 value_noise(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(
  71. const Game::Map::TerrainHeightMap &height_map,
  72. const Game::Map::BiomeSettings &biome_settings) {
  73. m_width = height_map.getWidth();
  74. m_height = height_map.getHeight();
  75. m_tile_size = height_map.getTileSize();
  76. m_height_data = height_map.getHeightData();
  77. m_terrain_types = height_map.getTerrainTypes();
  78. m_hill_entrances = height_map.getHillEntrances();
  79. m_biome_settings = biome_settings;
  80. m_noise_seed = biome_settings.seed;
  81. build_meshes();
  82. }
  83. void TerrainRenderer::submit(Renderer &renderer, ResourceManager *resources) {
  84. if (m_chunks.empty()) {
  85. return;
  86. }
  87. Q_UNUSED(resources);
  88. auto &visibility = Game::Map::VisibilityService::instance();
  89. const bool use_visibility = visibility.is_initialized();
  90. Game::Map::VisibilityService::Snapshot visibility_snapshot;
  91. std::uint64_t visibility_version = 0;
  92. if (use_visibility) {
  93. visibility_snapshot = visibility.snapshot();
  94. visibility_version = visibility.version();
  95. if (m_chunk_visibility_cache.size() != m_chunks.size()) {
  96. m_chunk_visibility_cache.assign(m_chunks.size(), {});
  97. }
  98. }
  99. for (std::size_t chunk_index = 0; chunk_index < m_chunks.size();
  100. ++chunk_index) {
  101. const auto &chunk = m_chunks[chunk_index];
  102. if (!chunk.mesh) {
  103. continue;
  104. }
  105. if (use_visibility) {
  106. auto &cache = m_chunk_visibility_cache[chunk_index];
  107. if (cache.visibility_version != visibility_version) {
  108. bool any_visible = false;
  109. for (int gz = chunk.min_z; gz <= chunk.max_z && !any_visible; ++gz) {
  110. for (int gx = chunk.min_x; gx <= chunk.max_x; ++gx) {
  111. if (visibility_snapshot.stateAt(gx, gz) ==
  112. Game::Map::VisibilityState::Visible) {
  113. any_visible = true;
  114. break;
  115. }
  116. }
  117. }
  118. cache.any_visible = any_visible;
  119. cache.visibility_version = visibility_version;
  120. }
  121. if (!cache.any_visible) {
  122. continue;
  123. }
  124. }
  125. renderer.terrain_chunk(chunk.mesh.get(), k_identity_matrix, chunk.params,
  126. 0x0080U, true, 0.0F);
  127. }
  128. }
  129. auto TerrainRenderer::section_for(Game::Map::TerrainType type) -> int {
  130. switch (type) {
  131. case Game::Map::TerrainType::Mountain:
  132. return 2;
  133. case Game::Map::TerrainType::Hill:
  134. return 1;
  135. case Game::Map::TerrainType::Forest:
  136. case Game::Map::TerrainType::Flat:
  137. default:
  138. return 0;
  139. }
  140. }
  141. void TerrainRenderer::build_meshes() {
  142. QElapsedTimer timer;
  143. timer.start();
  144. m_chunks.clear();
  145. m_chunk_visibility_cache.clear();
  146. if (m_width < 2 || m_height < 2 || m_height_data.empty()) {
  147. return;
  148. }
  149. std::vector<float> height_data = m_height_data;
  150. std::vector<float> entry_weight;
  151. if (!m_hill_entrances.empty() &&
  152. m_hill_entrances.size() == height_data.size()) {
  153. constexpr int k_entry_radius = 4;
  154. entry_weight.assign(height_data.size(), 0.0F);
  155. for (int z = 0; z < m_height; ++z) {
  156. for (int x = 0; x < m_width; ++x) {
  157. int const idx = z * m_width + x;
  158. if (m_terrain_types[idx] != Game::Map::TerrainType::Hill) {
  159. continue;
  160. }
  161. float min_dist = float(k_entry_radius + 1);
  162. for (int dz = -k_entry_radius; dz <= k_entry_radius; ++dz) {
  163. int const nz = z + dz;
  164. if (nz < 0 || nz >= m_height) {
  165. continue;
  166. }
  167. for (int dx = -k_entry_radius; dx <= k_entry_radius; ++dx) {
  168. int const nx = x + dx;
  169. if (nx < 0 || nx >= m_width) {
  170. continue;
  171. }
  172. int const n_idx = nz * m_width + nx;
  173. if (!m_hill_entrances[n_idx]) {
  174. continue;
  175. }
  176. float const dist = std::sqrt(float(dx * dx + dz * dz));
  177. min_dist = std::min(min_dist, dist);
  178. }
  179. }
  180. if (min_dist <= k_entry_radius) {
  181. float const t = 1.0F - (min_dist / float(k_entry_radius));
  182. entry_weight[idx] = t * t;
  183. }
  184. }
  185. }
  186. }
  187. float min_h = std::numeric_limits<float>::infinity();
  188. float max_h = -std::numeric_limits<float>::infinity();
  189. for (float const h : height_data) {
  190. min_h = std::min(min_h, h);
  191. max_h = std::max(max_h, h);
  192. }
  193. const float height_range = std::max(1e-4F, max_h - min_h);
  194. const float half_width = m_width * 0.5F - 0.5F;
  195. const float half_height = m_height * 0.5F - 0.5F;
  196. const int vertex_count = m_width * m_height;
  197. std::vector<QVector3D> positions(vertex_count);
  198. std::vector<QVector3D> normals(vertex_count, QVector3D(0.0F, 0.0F, 0.0F));
  199. std::vector<QVector3D> face_accum(vertex_count, QVector3D(0, 0, 0));
  200. for (int z = 0; z < m_height; ++z) {
  201. for (int x = 0; x < m_width; ++x) {
  202. int const idx = z * m_width + x;
  203. float const world_x = (x - half_width) * m_tile_size;
  204. float const world_z = (z - half_height) * m_tile_size;
  205. positions[idx] = QVector3D(world_x, height_data[idx], world_z);
  206. }
  207. }
  208. auto sample_height_at = [&](float gx, float gz) {
  209. gx = std::clamp(gx, 0.0F, float(m_width - 1));
  210. gz = std::clamp(gz, 0.0F, float(m_height - 1));
  211. int const x0 = int(std::floor(gx));
  212. int const z0 = int(std::floor(gz));
  213. int const x1 = std::min(x0 + 1, m_width - 1);
  214. int const z1 = std::min(z0 + 1, m_height - 1);
  215. float const tx = gx - float(x0);
  216. float const tz = gz - float(z0);
  217. float const h00 = height_data[z0 * m_width + x0];
  218. float const h10 = height_data[z0 * m_width + x1];
  219. float const h01 = height_data[z1 * m_width + x0];
  220. float const h11 = height_data[z1 * m_width + x1];
  221. float const h0 = h00 * (1.0F - tx) + h10 * tx;
  222. float const h1 = h01 * (1.0F - tx) + h11 * tx;
  223. return h0 * (1.0F - tz) + h1 * tz;
  224. };
  225. auto sample_entry_at = [&](float gx, float gz) {
  226. if (entry_weight.empty()) {
  227. return 0.0F;
  228. }
  229. gx = std::clamp(gx, 0.0F, float(m_width - 1));
  230. gz = std::clamp(gz, 0.0F, float(m_height - 1));
  231. int const x0 = int(std::floor(gx));
  232. int const z0 = int(std::floor(gz));
  233. int const x1 = std::min(x0 + 1, m_width - 1);
  234. int const z1 = std::min(z0 + 1, m_height - 1);
  235. float const tx = gx - float(x0);
  236. float const tz = gz - float(z0);
  237. float const e00 = entry_weight[z0 * m_width + x0];
  238. float const e10 = entry_weight[z0 * m_width + x1];
  239. float const e01 = entry_weight[z1 * m_width + x0];
  240. float const e11 = entry_weight[z1 * m_width + x1];
  241. float const e0 = e00 * (1.0F - tx) + e10 * tx;
  242. float const e1 = e01 * (1.0F - tx) + e11 * tx;
  243. return e0 * (1.0F - tz) + e1 * tz;
  244. };
  245. auto normal_from_heights_at = [&](float gx, float gz) {
  246. float const gx0 = std::clamp(gx - 1.0F, 0.0F, float(m_width - 1));
  247. float const gx1 = std::clamp(gx + 1.0F, 0.0F, float(m_width - 1));
  248. float const gz0 = std::clamp(gz - 1.0F, 0.0F, float(m_height - 1));
  249. float const gz1 = std::clamp(gz + 1.0F, 0.0F, float(m_height - 1));
  250. float const h_l = sample_height_at(gx0, gz);
  251. float const h_r = sample_height_at(gx1, gz);
  252. float const h_d = sample_height_at(gx, gz0);
  253. float const h_u = sample_height_at(gx, gz1);
  254. QVector3D const dx(2.0F * m_tile_size, h_r - h_l, 0.0F);
  255. QVector3D const dz(0.0F, h_u - h_d, 2.0F * m_tile_size);
  256. QVector3D n = QVector3D::crossProduct(dz, dx);
  257. if (n.lengthSquared() > 0.0F) {
  258. n.normalize();
  259. }
  260. return n.isNull() ? QVector3D(0, 1, 0) : n;
  261. };
  262. for (int i = 0; i < vertex_count; ++i) {
  263. int const x = i % m_width;
  264. int const z = i / m_width;
  265. normals[i] = normal_from_heights_at(float(x), float(z));
  266. face_accum[i] = normals[i];
  267. }
  268. {
  269. std::vector<QVector3D> filtered = normals;
  270. auto get_n = [&](int x, int z) -> QVector3D & {
  271. return normals[z * m_width + x];
  272. };
  273. for (int z = 1; z < m_height - 1; ++z) {
  274. for (int x = 1; x < m_width - 1; ++x) {
  275. const int idx = z * m_width + x;
  276. const float h0 = height_data[idx];
  277. const float nh = (h0 - min_h) / height_range;
  278. const float h_l = height_data[z * m_width + (x - 1)];
  279. const float h_r = height_data[z * m_width + (x + 1)];
  280. const float h_d = height_data[(z - 1) * m_width + x];
  281. const float h_u = height_data[(z + 1) * m_width + x];
  282. const float avg_nbr = 0.25F * (h_l + h_r + h_d + h_u);
  283. const float convexity = h0 - avg_nbr;
  284. const QVector3D n0 = normals[idx];
  285. const float slope = 1.0F - std::clamp(n0.y(), 0.0F, 1.0F);
  286. const float ridge_s = smooth(0.35F, 0.70F, slope);
  287. const float ridge_c = smooth(-0.02F, 0.18F, convexity);
  288. const float ridge_factor =
  289. std::clamp(0.5F * ridge_s + 0.5F * ridge_c, 0.0F, 1.0F);
  290. const float base_boost = 0.6F * (1.0F - nh);
  291. QVector3D acc(0, 0, 0);
  292. float wsum = 0.0F;
  293. for (int dz = -1; dz <= 1; ++dz) {
  294. for (int dx = -1; dx <= 1; ++dx) {
  295. const int nx = x + dx;
  296. const int nz = z + dz;
  297. const int n_idx = nz * m_width + nx;
  298. const float dh = std::abs(height_data[n_idx] - h0);
  299. const QVector3D nn = get_n(nx, nz);
  300. const float ndot = std::max(0.0F, QVector3D::dotProduct(n0, nn));
  301. const float w_h = 1.0F / (1.0F + 2.0F * dh);
  302. const float w_n = std::pow(ndot, 8.0F);
  303. const float w_b = 1.0F + base_boost;
  304. const float w_r = 1.0F - ridge_factor * 0.85F;
  305. const float w = w_h * w_n * w_b * w_r;
  306. acc += nn * w;
  307. wsum += w;
  308. }
  309. }
  310. QVector3D n_filtered = (wsum > 0.0F) ? (acc / wsum) : n0;
  311. n_filtered.normalize();
  312. const QVector3D n_orig = face_accum[idx];
  313. const QVector3D n_final =
  314. (ridge_factor > 0.0F)
  315. ? (n_filtered * (1.0F - ridge_factor) + n_orig * ridge_factor)
  316. : n_filtered;
  317. filtered[idx] = n_final.normalized();
  318. }
  319. }
  320. normals.swap(filtered);
  321. }
  322. auto quad_section = [&](Game::Map::TerrainType a, Game::Map::TerrainType b,
  323. Game::Map::TerrainType c, Game::Map::TerrainType d) {
  324. int const priority_a = section_for(a);
  325. int const priority_b = section_for(b);
  326. int const priority_c = section_for(c);
  327. int const priority_d = section_for(d);
  328. int result = priority_a;
  329. result = std::max(result, priority_b);
  330. result = std::max(result, priority_c);
  331. result = std::max(result, priority_d);
  332. return result;
  333. };
  334. const int chunk_size = DefaultChunkSize;
  335. std::size_t total_triangles = 0;
  336. for (int chunk_z = 0; chunk_z < m_height - 1; chunk_z += chunk_size) {
  337. int const chunk_max_z = std::min(chunk_z + chunk_size, m_height - 1);
  338. for (int chunk_x = 0; chunk_x < m_width - 1; chunk_x += chunk_size) {
  339. int const chunk_max_x = std::min(chunk_x + chunk_size, m_width - 1);
  340. struct SectionData {
  341. std::vector<Vertex> vertices;
  342. std::vector<unsigned int> indices;
  343. std::unordered_map<int, unsigned int> remap;
  344. float heightSum = 0.0F;
  345. int heightCount = 0;
  346. float rotationDeg = 0.0F;
  347. bool flipU = false;
  348. float tint = 1.0F;
  349. QVector3D normalSum = QVector3D(0, 0, 0);
  350. float slopeSum = 0.0F;
  351. float heightVarSum = 0.0F;
  352. int statCount = 0;
  353. float aoSum = 0.0F;
  354. int aoCount = 0;
  355. };
  356. SectionData sections[3];
  357. uint32_t const chunk_seed = hash_coords(chunk_x, chunk_z, m_noise_seed);
  358. uint32_t const variant_seed = chunk_seed ^ k_golden_ratio;
  359. constexpr int k_rotation_shift = 5;
  360. constexpr int k_rotation_mask = 3;
  361. constexpr float k_rotation_step_degrees = 90.0F;
  362. constexpr int k_flip_shift = 7;
  363. constexpr int k_tint_shift = 12;
  364. constexpr int k_tint_variant_count = 7;
  365. float const rotation_step =
  366. static_cast<float>((variant_seed >> k_rotation_shift) &
  367. k_rotation_mask) *
  368. k_rotation_step_degrees;
  369. bool const flip = ((variant_seed >> k_flip_shift) & 1U) != 0U;
  370. static const float tint_variants[k_tint_variant_count] = {
  371. 0.9F, 0.94F, 0.97F, 1.0F, 1.03F, 1.06F, 1.1F};
  372. float const tint =
  373. tint_variants[(variant_seed >> k_tint_shift) % k_tint_variant_count];
  374. for (auto &section : sections) {
  375. section.rotationDeg = rotation_step;
  376. section.flipU = flip;
  377. section.tint = tint;
  378. }
  379. auto ensure_vertex = [&](SectionData &section,
  380. int globalIndex) -> unsigned int {
  381. auto it = section.remap.find(globalIndex);
  382. if (it != section.remap.end()) {
  383. return it->second;
  384. }
  385. Vertex v{};
  386. const QVector3D &pos = positions[globalIndex];
  387. const QVector3D &normal = normals[globalIndex];
  388. v.position[0] = pos.x();
  389. v.position[1] = pos.y();
  390. v.position[2] = pos.z();
  391. v.normal[0] = normal.x();
  392. v.normal[1] = normal.y();
  393. v.normal[2] = normal.z();
  394. float const tex_scale = 0.2F / std::max(1.0F, m_tile_size);
  395. float uu = pos.x() * tex_scale;
  396. float const vv = pos.z() * tex_scale;
  397. if (section.flipU) {
  398. uu = -uu;
  399. }
  400. float ru = uu;
  401. float rv = vv;
  402. switch (static_cast<int>(section.rotationDeg)) {
  403. case 90: {
  404. float const t = ru;
  405. ru = -rv;
  406. rv = t;
  407. } break;
  408. case 180:
  409. ru = -ru;
  410. rv = -rv;
  411. break;
  412. case 270: {
  413. float const t = ru;
  414. ru = rv;
  415. rv = -t;
  416. } break;
  417. default:
  418. break;
  419. }
  420. v.tex_coord[0] = ru;
  421. v.tex_coord[1] =
  422. entry_weight.empty() ? 0.0F : entry_weight[globalIndex];
  423. section.vertices.push_back(v);
  424. auto const local_index =
  425. static_cast<unsigned int>(section.vertices.size() - 1);
  426. section.remap.emplace(globalIndex, local_index);
  427. section.normalSum += normal;
  428. return local_index;
  429. };
  430. auto add_vertex = [&](SectionData &section, const QVector3D &pos,
  431. const QVector3D &normal,
  432. float entry_mask) -> unsigned int {
  433. Vertex v{};
  434. v.position[0] = pos.x();
  435. v.position[1] = pos.y();
  436. v.position[2] = pos.z();
  437. v.normal[0] = normal.x();
  438. v.normal[1] = normal.y();
  439. v.normal[2] = normal.z();
  440. float const tex_scale = 0.2F / std::max(1.0F, m_tile_size);
  441. float uu = pos.x() * tex_scale;
  442. float const vv = pos.z() * tex_scale;
  443. if (section.flipU) {
  444. uu = -uu;
  445. }
  446. float ru = uu;
  447. float rv = vv;
  448. switch (static_cast<int>(section.rotationDeg)) {
  449. case 90: {
  450. float const t = ru;
  451. ru = -rv;
  452. rv = t;
  453. } break;
  454. case 180:
  455. ru = -ru;
  456. rv = -rv;
  457. break;
  458. case 270: {
  459. float const t = ru;
  460. ru = rv;
  461. rv = -t;
  462. } break;
  463. default:
  464. break;
  465. }
  466. v.tex_coord[0] = ru;
  467. v.tex_coord[1] = entry_weight.empty() ? 0.0F : entry_mask;
  468. section.vertices.push_back(v);
  469. auto const local_index =
  470. static_cast<unsigned int>(section.vertices.size() - 1);
  471. section.normalSum += normal;
  472. return local_index;
  473. };
  474. auto add_vertex_at_grid = [&](SectionData &section, float gx, float gz,
  475. float entry_mask) -> unsigned int {
  476. float const world_x = (gx - half_width) * m_tile_size;
  477. float const world_z = (gz - half_height) * m_tile_size;
  478. float const h = sample_height_at(gx, gz);
  479. QVector3D const pos(world_x, h, world_z);
  480. QVector3D const normal = normal_from_heights_at(gx, gz);
  481. return add_vertex(section, pos, normal, entry_mask);
  482. };
  483. for (int z = chunk_z; z < chunk_max_z; ++z) {
  484. for (int x = chunk_x; x < chunk_max_x; ++x) {
  485. int const idx0 = z * m_width + x;
  486. int const idx1 = idx0 + 1;
  487. int const idx2 = (z + 1) * m_width + x;
  488. int const idx3 = idx2 + 1;
  489. int const section_index =
  490. quad_section(m_terrain_types[idx0], m_terrain_types[idx1],
  491. m_terrain_types[idx2], m_terrain_types[idx3]);
  492. if (section_index > 0) {
  493. SectionData &section = sections[section_index];
  494. unsigned int const v0 = ensure_vertex(section, idx0);
  495. unsigned int const v1 = ensure_vertex(section, idx1);
  496. unsigned int const v2 = ensure_vertex(section, idx2);
  497. unsigned int const v3 = ensure_vertex(section, idx3);
  498. float entry_factor = 0.0F;
  499. if (!entry_weight.empty()) {
  500. entry_factor = 0.25F * (entry_weight[idx0] + entry_weight[idx1] +
  501. entry_weight[idx2] + entry_weight[idx3]);
  502. }
  503. bool const subdivide = entry_factor > 0.25F;
  504. if (subdivide) {
  505. float const gx = float(x);
  506. float const gz = float(z);
  507. unsigned int const v00 = v0;
  508. unsigned int const v20 = v1;
  509. unsigned int const v02 = v2;
  510. unsigned int const v22 = v3;
  511. unsigned int const v10 = add_vertex_at_grid(
  512. section, gx + 0.5F, gz, sample_entry_at(gx + 0.5F, gz));
  513. unsigned int const v01 = add_vertex_at_grid(
  514. section, gx, gz + 0.5F, sample_entry_at(gx, gz + 0.5F));
  515. unsigned int const v21 =
  516. add_vertex_at_grid(section, gx + 1.0F, gz + 0.5F,
  517. sample_entry_at(gx + 1.0F, gz + 0.5F));
  518. unsigned int const v12 =
  519. add_vertex_at_grid(section, gx + 0.5F, gz + 1.0F,
  520. sample_entry_at(gx + 0.5F, gz + 1.0F));
  521. unsigned int const v11 =
  522. add_vertex_at_grid(section, gx + 0.5F, gz + 0.5F,
  523. sample_entry_at(gx + 0.5F, gz + 0.5F));
  524. section.indices.push_back(v00);
  525. section.indices.push_back(v10);
  526. section.indices.push_back(v01);
  527. section.indices.push_back(v10);
  528. section.indices.push_back(v11);
  529. section.indices.push_back(v01);
  530. section.indices.push_back(v10);
  531. section.indices.push_back(v20);
  532. section.indices.push_back(v11);
  533. section.indices.push_back(v20);
  534. section.indices.push_back(v21);
  535. section.indices.push_back(v11);
  536. section.indices.push_back(v01);
  537. section.indices.push_back(v11);
  538. section.indices.push_back(v02);
  539. section.indices.push_back(v11);
  540. section.indices.push_back(v12);
  541. section.indices.push_back(v02);
  542. section.indices.push_back(v11);
  543. section.indices.push_back(v21);
  544. section.indices.push_back(v12);
  545. section.indices.push_back(v21);
  546. section.indices.push_back(v22);
  547. section.indices.push_back(v12);
  548. } else {
  549. section.indices.push_back(v0);
  550. section.indices.push_back(v1);
  551. section.indices.push_back(v2);
  552. section.indices.push_back(v2);
  553. section.indices.push_back(v1);
  554. section.indices.push_back(v3);
  555. }
  556. float const quad_height = (height_data[idx0] + height_data[idx1] +
  557. height_data[idx2] + height_data[idx3]) *
  558. 0.25F;
  559. section.heightSum += quad_height;
  560. section.heightCount += 1;
  561. float const n_y = (normals[idx0].y() + normals[idx1].y() +
  562. normals[idx2].y() + normals[idx3].y()) *
  563. 0.25F;
  564. float const slope = 1.0F - std::clamp(n_y, 0.0F, 1.0F);
  565. section.slopeSum += slope;
  566. float const hmin =
  567. std::min(std::min(height_data[idx0], height_data[idx1]),
  568. std::min(height_data[idx2], height_data[idx3]));
  569. float const hmax =
  570. std::max(std::max(height_data[idx0], height_data[idx1]),
  571. std::max(height_data[idx2], height_data[idx3]));
  572. section.heightVarSum += (hmax - hmin);
  573. section.statCount += 1;
  574. auto h = [&](int gx, int gz) {
  575. gx = std::clamp(gx, 0, m_width - 1);
  576. gz = std::clamp(gz, 0, m_height - 1);
  577. return height_data[gz * m_width + gx];
  578. };
  579. int const cx = x;
  580. int const cz = z;
  581. float const h_c = quad_height;
  582. float ao = 0.0F;
  583. ao += std::max(0.0F, h(cx - 1, cz) - h_c);
  584. ao += std::max(0.0F, h(cx + 1, cz) - h_c);
  585. ao += std::max(0.0F, h(cx, cz - 1) - h_c);
  586. ao += std::max(0.0F, h(cx, cz + 1) - h_c);
  587. ao = std::clamp(ao * 0.15F, 0.0F, 1.0F);
  588. section.aoSum += ao;
  589. section.aoCount += 1;
  590. }
  591. }
  592. }
  593. for (int i = 0; i < 3; ++i) {
  594. SectionData const &section = sections[i];
  595. if (section.indices.empty()) {
  596. continue;
  597. }
  598. auto mesh = std::make_unique<Mesh>(section.vertices, section.indices);
  599. if (!mesh) {
  600. continue;
  601. }
  602. ChunkMesh chunk;
  603. chunk.mesh = std::move(mesh);
  604. chunk.min_x = chunk_x;
  605. chunk.max_x = chunk_max_x - 1;
  606. chunk.min_z = chunk_z;
  607. chunk.max_z = chunk_max_z - 1;
  608. chunk.type = (i == 0) ? Game::Map::TerrainType::Flat
  609. : (i == 1) ? Game::Map::TerrainType::Hill
  610. : Game::Map::TerrainType::Mountain;
  611. chunk.average_height =
  612. (section.heightCount > 0)
  613. ? section.heightSum / float(section.heightCount)
  614. : 0.0F;
  615. const float nh_chunk = (chunk.average_height - min_h) / height_range;
  616. const float avg_slope =
  617. (section.statCount > 0)
  618. ? (section.slopeSum / float(section.statCount))
  619. : 0.0F;
  620. const float roughness =
  621. (section.statCount > 0)
  622. ? (section.heightVarSum / float(section.statCount))
  623. : 0.0F;
  624. const float center_gx = 0.5F * (chunk.min_x + chunk.max_x);
  625. const float center_gz = 0.5F * (chunk.min_z + chunk.max_z);
  626. auto hgrid = [&](int gx, int gz) {
  627. gx = std::clamp(gx, 0, m_width - 1);
  628. gz = std::clamp(gz, 0, m_height - 1);
  629. return height_data[gz * m_width + gx];
  630. };
  631. const int cxi = int(center_gx);
  632. const int czi = int(center_gz);
  633. const float h_c = hgrid(cxi, czi);
  634. const float h_l = hgrid(cxi - 1, czi);
  635. const float h_r = hgrid(cxi + 1, czi);
  636. const float h_d = hgrid(cxi, czi - 1);
  637. const float h_u = hgrid(cxi, czi + 1);
  638. const float convexity = h_c - 0.25F * (h_l + h_r + h_d + h_u);
  639. const float edge_factor = smooth(0.25F, 0.55F, avg_slope);
  640. const float entrance_factor =
  641. (1.0F - edge_factor) * smooth(0.00F, 0.15F, -convexity);
  642. const float plateau_flat = 1.0F - smooth(0.10F, 0.25F, avg_slope);
  643. const float plateau_height = smooth(0.60F, 0.80F, nh_chunk);
  644. const float plateau_factor = plateau_flat * plateau_height;
  645. QVector3D const base_color =
  646. get_terrain_color(chunk.type, chunk.average_height);
  647. QVector3D const rock_tint = m_biome_settings.rock_low;
  648. float slope_mix = std::clamp(
  649. avg_slope * ((chunk.type == Game::Map::TerrainType::Flat) ? 0.30F
  650. : (chunk.type == Game::Map::TerrainType::Hill)
  651. ? 0.55F
  652. : 0.90F),
  653. 0.0F, 1.0F);
  654. slope_mix += 0.15F * edge_factor;
  655. slope_mix -= 0.10F * entrance_factor;
  656. slope_mix -= 0.08F * plateau_factor;
  657. slope_mix = std::clamp(slope_mix, 0.0F, 1.0F);
  658. float const center_wx = (center_gx - half_width) * m_tile_size;
  659. float const center_wz = (center_gz - half_height) * m_tile_size;
  660. float const macro = value_noise(center_wx * 0.02F, center_wz * 0.02F,
  661. m_noise_seed ^ 0x51C3U);
  662. float const macro_shade = 0.9F + 0.2F * macro;
  663. float const ao_avg = (section.aoCount > 0)
  664. ? (section.aoSum / float(section.aoCount))
  665. : 0.0F;
  666. float const ao_shade = 1.0F - 0.35F * ao_avg;
  667. QVector3D avg_n = section.normalSum;
  668. if (avg_n.lengthSquared() > 0.0F) {
  669. avg_n.normalize();
  670. }
  671. QVector3D const north(0, 0, 1);
  672. float const northness = std::clamp(
  673. QVector3D::dotProduct(avg_n, north) * 0.5F + 0.5F, 0.0F, 1.0F);
  674. QVector3D const cool_tint(0.96F, 1.02F, 1.04F);
  675. QVector3D const warm_tint(1.03F, 1.0F, 0.97F);
  676. QVector3D const aspect_tint =
  677. cool_tint * northness + warm_tint * (1.0F - northness);
  678. float const feature_bright =
  679. 1.0F + 0.08F * plateau_factor - 0.05F * entrance_factor;
  680. QVector3D const feature_tint =
  681. QVector3D(1.0F + 0.03F * plateau_factor - 0.03F * entrance_factor,
  682. 1.0F + 0.01F * plateau_factor - 0.01F * entrance_factor,
  683. 1.0F - 0.02F * plateau_factor + 0.03F * entrance_factor);
  684. chunk.tint = section.tint;
  685. QVector3D color =
  686. base_color * (1.0F - slope_mix) + rock_tint * slope_mix;
  687. color = applyTint(color, chunk.tint);
  688. color *= macro_shade;
  689. color.setX(color.x() * aspect_tint.x() * feature_tint.x());
  690. color.setY(color.y() * aspect_tint.y() * feature_tint.y());
  691. color.setZ(color.z() * aspect_tint.z() * feature_tint.z());
  692. color *= ao_shade * feature_bright;
  693. color = color * 0.96F + QVector3D(0.04F, 0.04F, 0.04F);
  694. chunk.color = clamp01(color);
  695. TerrainChunkParams params;
  696. auto tint_color = [&](const QVector3D &base) {
  697. return clamp01(applyTint(base, chunk.tint));
  698. };
  699. params.grass_primary = tint_color(m_biome_settings.grass_primary);
  700. params.grass_secondary = tint_color(m_biome_settings.grass_secondary);
  701. params.grass_dry = tint_color(m_biome_settings.grass_dry);
  702. params.soil_color = tint_color(m_biome_settings.soil_color);
  703. params.rock_low = tint_color(m_biome_settings.rock_low);
  704. params.rock_high = tint_color(m_biome_settings.rock_high);
  705. params.tile_size = std::max(0.001F, m_tile_size);
  706. params.macro_noise_scale = m_biome_settings.terrain_macro_noise_scale;
  707. params.detail_noise_scale = m_biome_settings.terrain_detail_noise_scale;
  708. float slope_threshold = m_biome_settings.terrain_rock_threshold;
  709. float sharpness_mul = 1.0F;
  710. if (chunk.type == Game::Map::TerrainType::Hill) {
  711. slope_threshold -= 0.06F;
  712. sharpness_mul = 1.15F;
  713. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  714. slope_threshold -= 0.16F;
  715. sharpness_mul = 1.60F;
  716. }
  717. slope_threshold -= 0.05F * edge_factor;
  718. slope_threshold += 0.04F * entrance_factor;
  719. slope_threshold = std::clamp(
  720. slope_threshold - std::clamp(avg_slope * 0.20F, 0.0F, 0.12F), 0.05F,
  721. 0.9F);
  722. params.slope_rock_threshold = slope_threshold;
  723. params.slope_rock_sharpness = std::max(
  724. 1.0F, m_biome_settings.terrain_rock_sharpness * sharpness_mul);
  725. float soil_height = m_biome_settings.terrain_soil_height;
  726. if (chunk.type == Game::Map::TerrainType::Hill) {
  727. soil_height -= 0.04F;
  728. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  729. soil_height -= 0.12F;
  730. }
  731. soil_height += 0.05F * entrance_factor - 0.03F * plateau_factor;
  732. params.soil_blend_height = soil_height;
  733. params.soil_blend_sharpness =
  734. std::max(0.75F, m_biome_settings.terrain_soil_sharpness *
  735. (chunk.type == Game::Map::TerrainType::Mountain
  736. ? 0.80F
  737. : 0.95F));
  738. const uint32_t noise_key_a =
  739. hash_coords(chunk.min_x, chunk.min_z, m_noise_seed ^ 0xB5297A4DU);
  740. const uint32_t noise_key_b =
  741. hash_coords(chunk.min_x, chunk.min_z, m_noise_seed ^ 0x68E31DA4U);
  742. constexpr float k_noise_offset_scale = 256.0F;
  743. params.noise_offset =
  744. QVector2D(hash_to_01(noise_key_a) * k_noise_offset_scale,
  745. hash_to_01(noise_key_b) * k_noise_offset_scale);
  746. float base_amp =
  747. m_biome_settings.height_noise_amplitude *
  748. (0.7F + 0.3F * std::clamp(roughness * 0.6F, 0.0F, 1.0F));
  749. if (chunk.type == Game::Map::TerrainType::Hill) {
  750. base_amp *= 1.12F;
  751. } else if (chunk.type == Game::Map::TerrainType::Mountain) {
  752. base_amp *= 1.25F;
  753. }
  754. base_amp *= (1.0F + 0.10F * edge_factor - 0.08F * plateau_factor -
  755. 0.06F * entrance_factor);
  756. params.height_noise_strength = base_amp;
  757. params.height_noise_frequency = m_biome_settings.height_noise_frequency;
  758. params.ambient_boost =
  759. m_biome_settings.terrain_ambient_boost *
  760. ((chunk.type == Game::Map::TerrainType::Hill) ? 0.97F
  761. : (chunk.type == Game::Map::TerrainType::Mountain) ? 0.90F
  762. : 0.95F);
  763. params.rock_detail_strength =
  764. m_biome_settings.terrain_rock_detail_strength *
  765. (0.75F + 0.35F * std::clamp(avg_slope * 1.2F, 0.0F, 1.0F) +
  766. 0.15F * edge_factor - 0.10F * plateau_factor -
  767. 0.08F * entrance_factor);
  768. params.tint = clamp01(QVector3D(chunk.tint, chunk.tint, chunk.tint));
  769. params.light_direction = QVector3D(0.35F, 0.8F, 0.45F);
  770. chunk.params = params;
  771. total_triangles += chunk.mesh->get_indices().size() / 3;
  772. m_chunks.push_back(std::move(chunk));
  773. }
  774. }
  775. }
  776. m_chunk_visibility_cache.assign(m_chunks.size(), {});
  777. }
  778. auto TerrainRenderer::get_terrain_color(Game::Map::TerrainType type,
  779. float height) const -> QVector3D {
  780. switch (type) {
  781. case Game::Map::TerrainType::Mountain:
  782. if (height > 4.0F) {
  783. return m_biome_settings.rock_high;
  784. }
  785. return m_biome_settings.rock_low;
  786. case Game::Map::TerrainType::Hill: {
  787. float const t = std::clamp(height / 3.0F, 0.0F, 1.0F);
  788. float const t_smooth = t * t * (3.0F - 2.0F * t);
  789. QVector3D const grass_low = m_biome_settings.grass_primary * 0.3F +
  790. m_biome_settings.grass_secondary * 0.7F;
  791. QVector3D const grass_high = m_biome_settings.grass_secondary * 0.6F +
  792. m_biome_settings.grass_dry * 0.4F;
  793. QVector3D const grass =
  794. grass_low * (1.0F - t_smooth) + grass_high * t_smooth;
  795. QVector3D const rock = m_biome_settings.rock_low * (1.0F - t_smooth) +
  796. m_biome_settings.rock_high * t_smooth;
  797. float const rock_blend_base = 0.15F + 0.45F * t_smooth;
  798. float const height_factor = std::clamp((height - 0.5F) * 0.3F, 0.0F, 0.25F);
  799. float const rock_blend =
  800. std::clamp(rock_blend_base + height_factor, 0.0F, 0.70F);
  801. return grass * (1.0F - rock_blend) + rock * rock_blend;
  802. }
  803. case Game::Map::TerrainType::Forest: {
  804. float const moisture = std::clamp((height - 0.5F) * 0.2F, 0.0F, 0.4F);
  805. QVector3D const base = m_biome_settings.grass_primary * (1.0F - moisture) +
  806. m_biome_settings.grass_secondary * moisture;
  807. float const dry_blend = std::clamp((height - 2.0F) * 0.12F, 0.0F, 0.3F);
  808. QVector3D const with_dry =
  809. base * (1.0F - dry_blend) + m_biome_settings.grass_dry * dry_blend;
  810. return QVector3D(with_dry.x() * 0.7F, with_dry.y() * 0.9F,
  811. with_dry.z() * 0.6F);
  812. }
  813. case Game::Map::TerrainType::Flat:
  814. default: {
  815. float const moisture = std::clamp((height - 0.5F) * 0.2F, 0.0F, 0.4F);
  816. QVector3D const base = m_biome_settings.grass_primary * (1.0F - moisture) +
  817. m_biome_settings.grass_secondary * moisture;
  818. float const dry_blend = std::clamp((height - 2.0F) * 0.12F, 0.0F, 0.3F);
  819. return base * (1.0F - dry_blend) + m_biome_settings.grass_dry * dry_blend;
  820. }
  821. }
  822. }
  823. } // namespace Render::GL