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

@ -0,0 +1,25 @@
#version 450
// M2 ribbon emitter fragment shader.
// Samples the ribbon texture, multiplied by vertex color and alpha.
// Uses additive blending (pipeline-level) for magic/spell trails.
layout(set = 1, binding = 0) uniform sampler2D uTexture;
layout(location = 0) in vec3 vColor;
layout(location = 1) in float vAlpha;
layout(location = 2) in vec2 vUV;
layout(location = 3) in float vFogFactor;
layout(location = 0) out vec4 outColor;
void main() {
vec4 tex = texture(uTexture, vUV);
// For additive ribbons alpha comes from texture luminance; multiply by vertex alpha.
float a = tex.a * vAlpha;
if (a < 0.01) discard;
vec3 rgb = tex.rgb * vColor;
// Ribbons fade slightly with fog (additive blend attenuated toward black = invisible in fog).
rgb *= vFogFactor;
outColor = vec4(rgb, a);
}

Binary file not shown.

View file

@ -0,0 +1,43 @@
#version 450
// M2 ribbon emitter vertex shader.
// Ribbon geometry is generated CPU-side as a triangle strip.
// Vertex format: pos(3) + color(3) + alpha(1) + uv(2) = 9 floats.
layout(set = 0, binding = 0) uniform PerFrame {
mat4 view;
mat4 projection;
mat4 lightSpaceMatrix;
vec4 lightDir;
vec4 lightColor;
vec4 ambientColor;
vec4 viewPos;
vec4 fogColor;
vec4 fogParams;
vec4 shadowParams;
};
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
layout(location = 2) in float aAlpha;
layout(location = 3) in vec2 aUV;
layout(location = 0) out vec3 vColor;
layout(location = 1) out float vAlpha;
layout(location = 2) out vec2 vUV;
layout(location = 3) out float vFogFactor;
void main() {
vec4 worldPos = vec4(aPos, 1.0);
vec4 viewPos4 = view * worldPos;
gl_Position = projection * viewPos4;
float dist = length(viewPos4.xyz);
float fogStart = fogParams.x;
float fogEnd = fogParams.y;
vFogFactor = clamp((fogEnd - dist) / max(fogEnd - fogStart, 0.001), 0.0, 1.0);
vColor = aColor;
vAlpha = aAlpha;
vUV = aUV;
}

Binary file not shown.