combat_dust_pipeline.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. #include "combat_dust_pipeline.h"
  2. #include "../../../game/core/component.h"
  3. #include "../../../game/core/world.h"
  4. #include "../backend.h"
  5. #include "../camera.h"
  6. #include "../render_constants.h"
  7. #include "../shader_cache.h"
  8. #include <QDebug>
  9. #include <QMatrix4x4>
  10. #include <QOpenGLContext>
  11. #include <cmath>
  12. #include <numbers>
  13. namespace Render::GL::BackendPipelines {
  14. using namespace Render::GL::VertexAttrib;
  15. using namespace Render::GL::ComponentCount;
  16. namespace {
  17. constexpr float kMinDustIntensity = 0.01F;
  18. constexpr float kDefaultDustRadius = 2.0F;
  19. constexpr float kDefaultDustIntensity = 0.6F;
  20. constexpr float kDustColorR = 0.6F;
  21. constexpr float kDustColorG = 0.55F;
  22. constexpr float kDustColorB = 0.45F;
  23. constexpr float kDustYOffset = 0.05F;
  24. constexpr float kDefaultFlameRadius = 3.0F;
  25. constexpr float kDefaultFlameIntensity = 0.8F;
  26. constexpr float kFlameColorR = 1.0F;
  27. constexpr float kFlameColorG = 0.4F;
  28. constexpr float kFlameColorB = 0.1F;
  29. constexpr float kFlameYOffset = 0.5F;
  30. constexpr float kBuildingHealthThreshold = 0.5F;
  31. void clear_gl_errors() {
  32. #ifndef NDEBUG
  33. while (glGetError() != GL_NO_ERROR) {
  34. }
  35. #endif
  36. }
  37. auto check_gl_error(const char *operation) -> bool {
  38. #ifndef NDEBUG
  39. GLenum err = glGetError();
  40. if (err != GL_NO_ERROR) {
  41. qWarning() << "CombatDustPipeline GL error in" << operation << ":" << err;
  42. return false;
  43. }
  44. #else
  45. Q_UNUSED(operation);
  46. #endif
  47. return true;
  48. }
  49. } // namespace
  50. auto CombatDustPipeline::initialize() -> bool {
  51. if (m_shader_cache == nullptr) {
  52. qWarning() << "CombatDustPipeline::initialize: null ShaderCache";
  53. return false;
  54. }
  55. initializeOpenGLFunctions();
  56. clear_gl_errors();
  57. m_dust_shader = m_shader_cache->get("combat_dust");
  58. if (m_dust_shader == nullptr) {
  59. m_dust_shader = m_shader_cache->load(
  60. "combat_dust", QStringLiteral(":/assets/shaders/combat_dust.vert"),
  61. QStringLiteral(":/assets/shaders/combat_dust.frag"));
  62. }
  63. if (m_dust_shader == nullptr) {
  64. qWarning() << "CombatDustPipeline: Failed to get combat_dust shader";
  65. return false;
  66. }
  67. cache_uniforms();
  68. if (!create_dust_geometry()) {
  69. qWarning() << "CombatDustPipeline: Failed to create dust geometry";
  70. return false;
  71. }
  72. qInfo() << "CombatDustPipeline initialized successfully";
  73. return is_initialized();
  74. }
  75. void CombatDustPipeline::shutdown() {
  76. shutdown_geometry();
  77. m_dust_shader = nullptr;
  78. }
  79. void CombatDustPipeline::shutdown_geometry() {
  80. if (QOpenGLContext::currentContext() == nullptr) {
  81. m_vao = 0;
  82. m_vertex_buffer = 0;
  83. m_index_buffer = 0;
  84. m_index_count = 0;
  85. return;
  86. }
  87. initializeOpenGLFunctions();
  88. clear_gl_errors();
  89. if (m_vao != 0) {
  90. glDeleteVertexArrays(1, &m_vao);
  91. m_vao = 0;
  92. }
  93. if (m_vertex_buffer != 0) {
  94. glDeleteBuffers(1, &m_vertex_buffer);
  95. m_vertex_buffer = 0;
  96. }
  97. if (m_index_buffer != 0) {
  98. glDeleteBuffers(1, &m_index_buffer);
  99. m_index_buffer = 0;
  100. }
  101. m_index_count = 0;
  102. }
  103. void CombatDustPipeline::cache_uniforms() {
  104. if (m_dust_shader == nullptr) {
  105. return;
  106. }
  107. m_uniforms.mvp = m_dust_shader->uniform_handle("u_mvp");
  108. m_uniforms.model = m_dust_shader->uniform_handle("u_model");
  109. m_uniforms.time = m_dust_shader->uniform_handle("u_time");
  110. m_uniforms.center = m_dust_shader->uniform_handle("u_center");
  111. m_uniforms.radius = m_dust_shader->uniform_handle("u_radius");
  112. m_uniforms.intensity = m_dust_shader->uniform_handle("u_intensity");
  113. m_uniforms.dust_color = m_dust_shader->uniform_handle("u_dust_color");
  114. m_uniforms.effect_type = m_dust_shader->uniform_handle("u_effect_type");
  115. }
  116. auto CombatDustPipeline::is_initialized() const -> bool {
  117. return m_dust_shader != nullptr && m_vao != 0 && m_index_count > 0;
  118. }
  119. struct DustVertex {
  120. float position[3];
  121. float normal[3];
  122. float tex_coord[2];
  123. };
  124. auto CombatDustPipeline::create_dust_geometry() -> bool {
  125. initializeOpenGLFunctions();
  126. shutdown_geometry();
  127. clear_gl_errors();
  128. std::vector<DustVertex> vertices;
  129. std::vector<unsigned int> indices;
  130. constexpr int rings = 6;
  131. constexpr int segments = 16;
  132. constexpr float pi = std::numbers::pi_v<float>;
  133. constexpr int height_levels = 8;
  134. constexpr int angle_segments = 12;
  135. constexpr float max_height = 1.0F;
  136. vertices.reserve(
  137. static_cast<size_t>((height_levels + 1) * (angle_segments + 1)));
  138. for (int h = 0; h <= height_levels; ++h) {
  139. float height_t = static_cast<float>(h) / static_cast<float>(height_levels);
  140. float y = height_t * max_height;
  141. float radius_at_height = 1.0F - height_t * 0.3F;
  142. for (int a = 0; a <= angle_segments; ++a) {
  143. float angle_t =
  144. static_cast<float>(a) / static_cast<float>(angle_segments);
  145. float theta = angle_t * pi * 2.0F;
  146. float x = radius_at_height * std::cos(theta);
  147. float z = radius_at_height * std::sin(theta);
  148. DustVertex v;
  149. v.position[0] = x;
  150. v.position[1] = y;
  151. v.position[2] = z;
  152. v.normal[0] = std::cos(theta);
  153. v.normal[1] = 0.3F;
  154. v.normal[2] = std::sin(theta);
  155. v.tex_coord[0] = angle_t;
  156. v.tex_coord[1] = height_t;
  157. vertices.push_back(v);
  158. }
  159. }
  160. indices.reserve(static_cast<size_t>(height_levels * angle_segments * 6));
  161. for (int h = 0; h < height_levels; ++h) {
  162. for (int a = 0; a < angle_segments; ++a) {
  163. unsigned int curr =
  164. static_cast<unsigned int>(h * (angle_segments + 1) + a);
  165. unsigned int next = curr + static_cast<unsigned int>(angle_segments + 1);
  166. indices.push_back(curr);
  167. indices.push_back(next);
  168. indices.push_back(curr + 1);
  169. indices.push_back(curr + 1);
  170. indices.push_back(next);
  171. indices.push_back(next + 1);
  172. }
  173. }
  174. glGenVertexArrays(1, &m_vao);
  175. if (!check_gl_error("glGenVertexArrays") || m_vao == 0) {
  176. return false;
  177. }
  178. glBindVertexArray(m_vao);
  179. if (!check_gl_error("glBindVertexArray")) {
  180. glDeleteVertexArrays(1, &m_vao);
  181. m_vao = 0;
  182. return false;
  183. }
  184. glGenBuffers(1, &m_vertex_buffer);
  185. glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
  186. glBufferData(GL_ARRAY_BUFFER,
  187. static_cast<GLsizeiptr>(vertices.size() * sizeof(DustVertex)),
  188. vertices.data(), GL_STATIC_DRAW);
  189. if (!check_gl_error("vertex buffer")) {
  190. shutdown_geometry();
  191. return false;
  192. }
  193. glGenBuffers(1, &m_index_buffer);
  194. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
  195. glBufferData(GL_ELEMENT_ARRAY_BUFFER,
  196. static_cast<GLsizeiptr>(indices.size() * sizeof(unsigned int)),
  197. indices.data(), GL_STATIC_DRAW);
  198. if (!check_gl_error("index buffer")) {
  199. shutdown_geometry();
  200. return false;
  201. }
  202. m_index_count = static_cast<GLsizei>(indices.size());
  203. glEnableVertexAttribArray(VertexAttrib::Position);
  204. glVertexAttribPointer(
  205. VertexAttrib::Position, ComponentCount::Vec3, GL_FLOAT, GL_FALSE,
  206. sizeof(DustVertex),
  207. reinterpret_cast<void *>(offsetof(DustVertex, position)));
  208. glEnableVertexAttribArray(VertexAttrib::Normal);
  209. glVertexAttribPointer(VertexAttrib::Normal, ComponentCount::Vec3, GL_FLOAT,
  210. GL_FALSE, sizeof(DustVertex),
  211. reinterpret_cast<void *>(offsetof(DustVertex, normal)));
  212. glEnableVertexAttribArray(VertexAttrib::TexCoord);
  213. glVertexAttribPointer(
  214. VertexAttrib::TexCoord, ComponentCount::Vec2, GL_FLOAT, GL_FALSE,
  215. sizeof(DustVertex),
  216. reinterpret_cast<void *>(offsetof(DustVertex, tex_coord)));
  217. glBindVertexArray(0);
  218. if (!check_gl_error("vertex attributes")) {
  219. shutdown_geometry();
  220. return false;
  221. }
  222. return true;
  223. }
  224. void CombatDustPipeline::collect_combat_zones(Engine::Core::World *world,
  225. float animation_time) {
  226. if (world == nullptr) {
  227. return;
  228. }
  229. auto units = world->get_entities_with<Engine::Core::UnitComponent>();
  230. for (auto *unit : units) {
  231. if (unit->has_component<Engine::Core::PendingRemovalComponent>()) {
  232. continue;
  233. }
  234. auto *unit_comp = unit->get_component<Engine::Core::UnitComponent>();
  235. auto *transform = unit->get_component<Engine::Core::TransformComponent>();
  236. auto *attack = unit->get_component<Engine::Core::AttackComponent>();
  237. if (transform == nullptr || unit_comp == nullptr) {
  238. continue;
  239. }
  240. if (unit_comp->health <= 0) {
  241. continue;
  242. }
  243. if (attack == nullptr || !attack->in_melee_lock) {
  244. continue;
  245. }
  246. CombatDustData data;
  247. data.position =
  248. QVector3D(transform->position.x, kDustYOffset, transform->position.z);
  249. data.radius = kDefaultDustRadius;
  250. data.intensity = kDefaultDustIntensity;
  251. data.color = QVector3D(kDustColorR, kDustColorG, kDustColorB);
  252. data.time = animation_time;
  253. data.effect_type = EffectType::Dust;
  254. m_dust_data.push_back(data);
  255. }
  256. }
  257. void CombatDustPipeline::collect_building_flames(Engine::Core::World *world,
  258. float animation_time) {
  259. if (world == nullptr) {
  260. return;
  261. }
  262. auto buildings = world->get_entities_with<Engine::Core::BuildingComponent>();
  263. for (auto *building : buildings) {
  264. if (building->has_component<Engine::Core::PendingRemovalComponent>()) {
  265. continue;
  266. }
  267. auto *unit_comp = building->get_component<Engine::Core::UnitComponent>();
  268. auto *transform =
  269. building->get_component<Engine::Core::TransformComponent>();
  270. if (transform == nullptr || unit_comp == nullptr) {
  271. continue;
  272. }
  273. if (unit_comp->health <= 0) {
  274. continue;
  275. }
  276. float health_ratio = static_cast<float>(unit_comp->health) /
  277. static_cast<float>(unit_comp->max_health);
  278. if (health_ratio > kBuildingHealthThreshold) {
  279. continue;
  280. }
  281. float base_intensity = kDefaultFlameIntensity * (1.0F - health_ratio);
  282. float cx = transform->position.x;
  283. float cz = transform->position.z;
  284. constexpr float kBuildingHalfWidth = 1.5F;
  285. constexpr float kBuildingHalfDepth = 1.2F;
  286. constexpr float kFlameSpacing = 0.8F;
  287. struct FlamePoint {
  288. float dx, dz, height_offset, intensity_mult, radius_mult;
  289. };
  290. FlamePoint flame_points[] = {
  291. {-kBuildingHalfWidth * 0.7F, -kBuildingHalfDepth * 0.7F, 0.8F, 1.0F,
  292. 0.9F},
  293. {kBuildingHalfWidth * 0.7F, -kBuildingHalfDepth * 0.7F, 0.7F, 0.95F,
  294. 0.85F},
  295. {-kBuildingHalfWidth * 0.7F, kBuildingHalfDepth * 0.7F, 0.6F, 0.9F,
  296. 0.8F},
  297. {kBuildingHalfWidth * 0.7F, kBuildingHalfDepth * 0.7F, 0.75F, 1.0F,
  298. 0.9F},
  299. {0.0F, -kBuildingHalfDepth * 0.8F, 0.9F, 0.85F, 0.7F},
  300. {0.0F, kBuildingHalfDepth * 0.8F, 0.7F, 0.8F, 0.65F},
  301. {-kBuildingHalfWidth * 0.8F, 0.0F, 0.65F, 0.75F, 0.7F},
  302. {kBuildingHalfWidth * 0.8F, 0.0F, 0.8F, 0.85F, 0.75F},
  303. {0.0F, 0.0F, 1.0F, 1.1F, 1.0F},
  304. };
  305. for (const auto &fp : flame_points) {
  306. CombatDustData data;
  307. data.position =
  308. QVector3D(cx + fp.dx, kFlameYOffset + fp.height_offset, cz + fp.dz);
  309. data.radius = kDefaultFlameRadius * fp.radius_mult;
  310. data.intensity = base_intensity * fp.intensity_mult;
  311. data.color = QVector3D(kFlameColorR, kFlameColorG, kFlameColorB);
  312. data.time = animation_time;
  313. data.effect_type = EffectType::Flame;
  314. m_dust_data.push_back(data);
  315. }
  316. }
  317. }
  318. void CombatDustPipeline::collect_all_effects(Engine::Core::World *world,
  319. float animation_time) {
  320. m_dust_data.clear();
  321. collect_combat_zones(world, animation_time);
  322. collect_building_flames(world, animation_time);
  323. }
  324. void CombatDustPipeline::add_dust_zone(const QVector3D &position, float radius,
  325. float intensity, const QVector3D &color,
  326. float time) {
  327. CombatDustData data;
  328. data.position = position;
  329. data.radius = radius;
  330. data.intensity = intensity;
  331. data.color = color;
  332. data.time = time;
  333. data.effect_type = EffectType::Dust;
  334. m_dust_data.push_back(data);
  335. }
  336. void CombatDustPipeline::add_flame_zone(const QVector3D &position, float radius,
  337. float intensity, const QVector3D &color,
  338. float time) {
  339. CombatDustData data;
  340. data.position = position;
  341. data.radius = radius;
  342. data.intensity = intensity;
  343. data.color = color;
  344. data.time = time;
  345. data.effect_type = EffectType::Flame;
  346. m_dust_data.push_back(data);
  347. }
  348. void CombatDustPipeline::render(const Camera &cam, float animation_time) {
  349. if (!is_initialized() || m_dust_data.empty()) {
  350. return;
  351. }
  352. clear_gl_errors();
  353. GLboolean cull_enabled = glIsEnabled(GL_CULL_FACE);
  354. GLboolean depth_test_enabled = glIsEnabled(GL_DEPTH_TEST);
  355. GLboolean blend_enabled = glIsEnabled(GL_BLEND);
  356. GLboolean depth_mask_enabled = GL_TRUE;
  357. glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_mask_enabled);
  358. glDisable(GL_CULL_FACE);
  359. glEnable(GL_DEPTH_TEST);
  360. glDepthMask(GL_FALSE);
  361. glEnable(GL_BLEND);
  362. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  363. m_dust_shader->use();
  364. glBindVertexArray(m_vao);
  365. for (const auto &data : m_dust_data) {
  366. CombatDustData render_data = data;
  367. render_data.time = animation_time;
  368. render_dust(render_data, cam);
  369. }
  370. glBindVertexArray(0);
  371. glDepthMask(depth_mask_enabled);
  372. if (!blend_enabled) {
  373. glDisable(GL_BLEND);
  374. }
  375. if (depth_test_enabled) {
  376. glEnable(GL_DEPTH_TEST);
  377. } else {
  378. glDisable(GL_DEPTH_TEST);
  379. }
  380. if (cull_enabled) {
  381. glEnable(GL_CULL_FACE);
  382. }
  383. }
  384. void CombatDustPipeline::render_dust(const CombatDustData &data,
  385. const Camera &cam) {
  386. QMatrix4x4 model;
  387. model.setToIdentity();
  388. model.translate(data.position);
  389. model.scale(data.radius);
  390. QMatrix4x4 vp = cam.get_projection_matrix() * cam.get_view_matrix();
  391. QMatrix4x4 mvp = vp * model;
  392. m_dust_shader->set_uniform(m_uniforms.mvp, mvp);
  393. m_dust_shader->set_uniform(m_uniforms.model, model);
  394. m_dust_shader->set_uniform(m_uniforms.time, data.time);
  395. m_dust_shader->set_uniform(m_uniforms.center, data.position);
  396. m_dust_shader->set_uniform(m_uniforms.radius, data.radius);
  397. m_dust_shader->set_uniform(m_uniforms.intensity, data.intensity);
  398. m_dust_shader->set_uniform(m_uniforms.dust_color, data.color);
  399. m_dust_shader->set_uniform(m_uniforms.effect_type,
  400. static_cast<int>(data.effect_type));
  401. glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
  402. }
  403. void CombatDustPipeline::render_single_dust(const QVector3D &position,
  404. const QVector3D &color,
  405. float radius, float intensity,
  406. float time,
  407. const QMatrix4x4 &view_proj) {
  408. if (!is_initialized()) {
  409. return;
  410. }
  411. if (intensity < kMinDustIntensity) {
  412. return;
  413. }
  414. GLboolean cull_enabled = glIsEnabled(GL_CULL_FACE);
  415. GLboolean depth_mask_enabled = GL_TRUE;
  416. glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_mask_enabled);
  417. glDisable(GL_CULL_FACE);
  418. glEnable(GL_DEPTH_TEST);
  419. glDepthMask(GL_FALSE);
  420. glEnable(GL_BLEND);
  421. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  422. m_dust_shader->use();
  423. glBindVertexArray(m_vao);
  424. QMatrix4x4 model;
  425. model.setToIdentity();
  426. model.translate(position);
  427. model.scale(radius);
  428. QMatrix4x4 mvp = view_proj * model;
  429. m_dust_shader->set_uniform(m_uniforms.mvp, mvp);
  430. m_dust_shader->set_uniform(m_uniforms.model, model);
  431. m_dust_shader->set_uniform(m_uniforms.time, time);
  432. m_dust_shader->set_uniform(m_uniforms.center, position);
  433. m_dust_shader->set_uniform(m_uniforms.radius, radius);
  434. m_dust_shader->set_uniform(m_uniforms.intensity, intensity);
  435. m_dust_shader->set_uniform(m_uniforms.dust_color, color);
  436. m_dust_shader->set_uniform(m_uniforms.effect_type,
  437. static_cast<int>(EffectType::Dust));
  438. glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
  439. glBindVertexArray(0);
  440. glDepthMask(depth_mask_enabled);
  441. if (cull_enabled) {
  442. glEnable(GL_CULL_FACE);
  443. }
  444. }
  445. void CombatDustPipeline::render_single_flame(const QVector3D &position,
  446. const QVector3D &color,
  447. float radius, float intensity,
  448. float time,
  449. const QMatrix4x4 &view_proj) {
  450. if (!is_initialized()) {
  451. return;
  452. }
  453. if (intensity < kMinDustIntensity) {
  454. return;
  455. }
  456. GLboolean cull_enabled = glIsEnabled(GL_CULL_FACE);
  457. GLboolean depth_mask_enabled = GL_TRUE;
  458. glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_mask_enabled);
  459. glDisable(GL_CULL_FACE);
  460. glEnable(GL_DEPTH_TEST);
  461. glDepthMask(GL_FALSE);
  462. glEnable(GL_BLEND);
  463. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  464. m_dust_shader->use();
  465. glBindVertexArray(m_vao);
  466. QMatrix4x4 model;
  467. model.setToIdentity();
  468. model.translate(position);
  469. model.scale(radius);
  470. QMatrix4x4 mvp = view_proj * model;
  471. m_dust_shader->set_uniform(m_uniforms.mvp, mvp);
  472. m_dust_shader->set_uniform(m_uniforms.model, model);
  473. m_dust_shader->set_uniform(m_uniforms.time, time);
  474. m_dust_shader->set_uniform(m_uniforms.center, position);
  475. m_dust_shader->set_uniform(m_uniforms.radius, radius);
  476. m_dust_shader->set_uniform(m_uniforms.intensity, intensity);
  477. m_dust_shader->set_uniform(m_uniforms.dust_color, color);
  478. m_dust_shader->set_uniform(m_uniforms.effect_type,
  479. static_cast<int>(EffectType::Flame));
  480. glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
  481. glBindVertexArray(0);
  482. glDepthMask(depth_mask_enabled);
  483. if (cull_enabled) {
  484. glEnable(GL_CULL_FACE);
  485. }
  486. }
  487. void CombatDustPipeline::render_single_stone_impact(
  488. const QVector3D &position, const QVector3D &color, float radius,
  489. float intensity, float time, const QMatrix4x4 &view_proj) {
  490. if (!is_initialized()) {
  491. return;
  492. }
  493. if (intensity < kMinDustIntensity) {
  494. return;
  495. }
  496. GLboolean cull_enabled = glIsEnabled(GL_CULL_FACE);
  497. GLboolean depth_mask_enabled = GL_TRUE;
  498. glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_mask_enabled);
  499. glDisable(GL_CULL_FACE);
  500. glEnable(GL_DEPTH_TEST);
  501. glDepthMask(GL_FALSE);
  502. glEnable(GL_BLEND);
  503. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  504. m_dust_shader->use();
  505. glBindVertexArray(m_vao);
  506. QMatrix4x4 model;
  507. model.setToIdentity();
  508. model.translate(position);
  509. model.scale(radius);
  510. QMatrix4x4 mvp = view_proj * model;
  511. m_dust_shader->set_uniform(m_uniforms.mvp, mvp);
  512. m_dust_shader->set_uniform(m_uniforms.model, model);
  513. m_dust_shader->set_uniform(m_uniforms.time, time);
  514. m_dust_shader->set_uniform(m_uniforms.center, position);
  515. m_dust_shader->set_uniform(m_uniforms.radius, radius);
  516. m_dust_shader->set_uniform(m_uniforms.intensity, intensity);
  517. m_dust_shader->set_uniform(m_uniforms.dust_color, color);
  518. m_dust_shader->set_uniform(m_uniforms.effect_type,
  519. static_cast<int>(EffectType::StoneImpact));
  520. glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
  521. glBindVertexArray(0);
  522. glDepthMask(depth_mask_enabled);
  523. if (cull_enabled) {
  524. glEnable(GL_CULL_FACE);
  525. }
  526. }
  527. } // namespace Render::GL::BackendPipelines