feat: implement M2 ribbon emitter rendering for spell trail effects

Parse M2RibbonEmitter data (WotLK format) from M2 files — bone index,
position, color/alpha/height tracks, edgesPerSecond, edgeLifetime,
gravity. Add CPU-side trail simulation per instance (edge birth at bone
world position, lifetime expiry, gravity droop). New m2_ribbon.vert/frag
shaders render a triangle-strip quad per emitter using the existing
particleTexLayout_ descriptor set. Supports both alpha-blend and additive
pipeline variants based on material blend mode. Fixes invisible spell
trail effects (~5-10%% of spell visuals) that were silently skipped.
This commit is contained in:
Kelsi 2026-03-13 01:17:30 -07:00
parent 022d387d95
commit 1108aa9ae6
9 changed files with 604 additions and 1 deletions

View file

@ -9,6 +9,7 @@
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <deque>
#include <string>
#include <optional>
#include <random>
@ -130,6 +131,11 @@ struct M2ModelGPU {
std::vector<VkTexture*> particleTextures; // Resolved Vulkan textures per emitter
std::vector<VkDescriptorSet> particleTexSets; // Pre-allocated descriptor sets per emitter (stable, avoids per-frame alloc)
// Ribbon emitter data (kept from M2Model)
std::vector<pipeline::M2RibbonEmitter> ribbonEmitters;
std::vector<VkTexture*> ribbonTextures; // Resolved texture per ribbon emitter
std::vector<VkDescriptorSet> ribbonTexSets; // Descriptor sets per ribbon emitter
// Texture transform data for UV animation
std::vector<pipeline::M2TextureTransform> textureTransforms;
std::vector<uint16_t> textureTransformLookup;
@ -180,6 +186,19 @@ struct M2Instance {
std::vector<float> emitterAccumulators; // fractional particle counter per emitter
std::vector<M2Particle> particles;
// Ribbon emitter state
struct RibbonEdge {
glm::vec3 worldPos; // Spine world position when this edge was born
glm::vec3 color; // Interpolated color at birth
float alpha; // Interpolated alpha at birth
float heightAbove;// Half-width above spine
float heightBelow;// Half-width below spine
float age; // Seconds since spawned
};
// One deque of edges per ribbon emitter on this instance
std::vector<std::deque<RibbonEdge>> ribbonEdges;
std::vector<float> ribbonEdgeAccumulators; // fractional edge counter per emitter
// Cached model flags (set at creation to avoid per-frame hash lookups)
bool cachedHasAnimation = false;
bool cachedDisableAnimation = false;
@ -295,6 +314,11 @@ public:
*/
void renderSmokeParticles(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
/**
* Render M2 ribbon emitters (spell trails / wing effects)
*/
void renderM2Ribbons(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
void setInstancePosition(uint32_t instanceId, const glm::vec3& position);
void setInstanceTransform(uint32_t instanceId, const glm::mat4& transform);
void setInstanceAnimationFrozen(uint32_t instanceId, bool frozen);
@ -374,6 +398,11 @@ private:
VkPipeline smokePipeline_ = VK_NULL_HANDLE; // Smoke particles
VkPipelineLayout smokePipelineLayout_ = VK_NULL_HANDLE;
// Ribbon pipelines (additive + alpha-blend)
VkPipeline ribbonPipeline_ = VK_NULL_HANDLE; // Alpha-blend ribbons
VkPipeline ribbonAdditivePipeline_ = VK_NULL_HANDLE; // Additive ribbons
VkPipelineLayout ribbonPipelineLayout_ = VK_NULL_HANDLE;
// Descriptor set layouts
VkDescriptorSetLayout materialSetLayout_ = VK_NULL_HANDLE; // set 1
VkDescriptorSetLayout boneSetLayout_ = VK_NULL_HANDLE; // set 2
@ -385,6 +414,12 @@ private:
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
static constexpr uint32_t MAX_BONE_SETS = 8192;
// Dynamic ribbon vertex buffer (CPU-written triangle strip)
static constexpr size_t MAX_RIBBON_VERTS = 2048; // 9 floats each
::VkBuffer ribbonVB_ = VK_NULL_HANDLE;
VmaAllocation ribbonVBAlloc_ = VK_NULL_HANDLE;
void* ribbonVBMapped_ = nullptr;
// Dynamic particle buffers
::VkBuffer smokeVB_ = VK_NULL_HANDLE;
VmaAllocation smokeVBAlloc_ = VK_NULL_HANDLE;
@ -535,6 +570,7 @@ private:
glm::vec3 interpFBlockVec3(const pipeline::M2FBlock& fb, float lifeRatio);
void emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt);
void updateParticles(M2Instance& inst, float dt);
void updateRibbons(M2Instance& inst, const M2ModelGPU& gpu, float dt);
// Helper to allocate descriptor sets
VkDescriptorSet allocateMaterialSet();