mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
415 lines (338 loc) • 11.1 kB
JavaScript
import shaders from '../../shaders';
export default {
vs: `
${shaders.instanceId}
${shaders.boneTexture}
uniform mat4 u_mvp;
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_uv;
attribute vec4 a_bones;
attribute float a_boneNumber;
attribute float a_teamColor;
attribute vec4 a_vertexColor;
attribute vec4 a_geosetColor;
attribute float a_layerAlpha;
attribute vec4 a_uvTransRot;
attribute vec3 a_uvScaleSprite;
varying vec3 v_normal;
varying vec2 v_uv;
varying float v_teamColor;
varying vec4 v_color;
varying vec4 v_uvTransRot;
varying vec3 v_uvScaleSprite;
void transform(inout vec3 position, inout vec3 normal, float boneNumber, vec4 bones) {
// For the broken models out there, since the game supports this.
if (boneNumber > 0.0) {
mat4 b0 = fetchMatrix(bones[0], a_InstanceID);
mat4 b1 = fetchMatrix(bones[1], a_InstanceID);
mat4 b2 = fetchMatrix(bones[2], a_InstanceID);
mat4 b3 = fetchMatrix(bones[3], a_InstanceID);
vec4 p = vec4(position, 1.0);
vec4 n = vec4(normal, 0.0);
position = vec3(b0 * p + b1 * p + b2 * p + b3 * p) / boneNumber;
normal = normalize(vec3(b0 * n + b1 * n + b2 * n + b3 * n));
}
}
void main() {
vec2 uv = a_uv;
vec3 position = a_position;
vec3 normal = a_normal;
transform(position, normal, a_boneNumber, a_bones);
v_uv = a_uv;
v_uvTransRot = a_uvTransRot;
v_uvScaleSprite = a_uvScaleSprite;
v_normal = normal;
v_teamColor = a_teamColor;
v_color = a_vertexColor * a_geosetColor.bgra;
v_color.a *= a_layerAlpha;
if (a_geosetColor.a > 0.0 && a_layerAlpha > 0.0) {
gl_Position = u_mvp * vec4(position, 1.0);
} else {
gl_Position = vec4(0.0);
}
}
`,
fs: `
${shaders.quat_transform}
uniform sampler2D u_texture;
uniform float u_filterMode;
// uniform bool u_unshaded;
uniform bool u_isTeamColor;
uniform vec2 u_uvScale;
uniform bool u_hasSlotAnim;
uniform bool u_hasTranslationAnim;
uniform bool u_hasRotationAnim;
uniform bool u_hasScaleAnim;
varying vec3 v_normal;
varying vec2 v_uv;
varying float v_teamColor;
varying vec4 v_color;
varying vec4 v_uvTransRot;
varying vec3 v_uvScaleSprite;
// const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));
void main() {
vec2 uv;
if (u_isTeamColor) {
uv = vec2(v_teamColor / 14.0, 0.0) + vec2(v_uv.x / 14.0, v_uv.y);
} else {
uv = v_uv;
// Translation animation
if (u_hasTranslationAnim) {
uv += v_uvTransRot.xy;
}
// Rotation animation
if (u_hasRotationAnim) {
uv = quat_transform(v_uvTransRot.zw, uv - 0.5) + 0.5;
}
// Scale animation
if (u_hasScaleAnim) {
uv = v_uvScaleSprite.x * (uv - 0.5) + 0.5;
}
// Sprite animation
if (u_hasSlotAnim) {
uv = (v_uvScaleSprite.yz + fract(uv)) * u_uvScale;
}
}
vec4 texel = texture2D(u_texture, uv);
vec4 color = texel * v_color;
// 1bit Alpha
if (u_filterMode == 1.0 && color.a < 0.75) {
discard;
}
// "Close to 0 alpha"
if (u_filterMode >= 5.0 && color.a < 0.02) {
discard;
}
// if (!u_unshaded) {
// color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);
// }
gl_FragColor = color;
}
`,
vsParticles: `
uniform mat4 u_mvp;
uniform mediump float u_emitter;
// Shared
uniform vec4 u_colors[3];
uniform vec3 u_vertices[4];
uniform vec3 u_intervals[4];
uniform float u_lifeSpan;
uniform float u_columns;
uniform float u_rows;
// Particle2
uniform vec3 u_scaling;
uniform vec3 u_cameraZ;
uniform float u_timeMiddle;
uniform bool u_teamColored;
// Splat and Uber.
uniform vec3 u_intervalTimes;
// Vertices
attribute float a_position;
// Instances
attribute vec3 a_p0;
attribute vec3 a_p1;
attribute vec3 a_p2;
attribute vec3 a_p3;
attribute float a_health;
attribute vec4 a_color;
attribute float a_tail;
attribute vec3 a_leftRightTop;
varying vec2 v_texcoord;
varying vec4 v_color;
float getCell(vec3 interval, float factor) {
float start = interval[0];
float end = interval[1];
float repeat = interval[2];
float spriteCount = end - start;
if (spriteCount > 0.0) {
// Repeating speeds up the sprite animation, which makes it effectively run N times in its interval.
// E.g. if repeat is 4, the sprite animation will be seen 4 times, and thus also run 4 times as fast.
// The sprite index is limited to the number of actual sprites.
return min(start + mod(floor(spriteCount * repeat * factor), spriteCount), u_columns * u_rows - 1.0);
}
return 0.0;
}
void particle2() {
float factor = (u_lifeSpan - a_health) / u_lifeSpan;
int index = 0;
if (factor < u_timeMiddle) {
factor = factor / u_timeMiddle;
index = 0;
} else {
factor = (factor - u_timeMiddle) / (1.0 - u_timeMiddle);
index = 1;
}
factor = min(factor, 1.0);
float scale = mix(u_scaling[index], u_scaling[index + 1], factor);
vec4 color = mix(u_colors[index], u_colors[index + 1], factor);
float cell = 0.0;
if (u_teamColored) {
cell = a_leftRightTop[0];
} else {
vec3 interval;
if (a_tail == HEAD) {
interval = u_intervals[index];
} else {
interval = u_intervals[index + 2];
}
cell = getCell(interval, factor);
}
float left = floor(mod(cell, u_columns));
float top = floor(cell / u_columns);
float right = left + 1.0;
float bottom = top + 1.0;
left /= u_columns;
right /= u_columns;
top /= u_rows;
bottom /= u_rows;
if (a_position == 0.0) {
v_texcoord = vec2(right, top);
} else if (a_position == 1.0) {
v_texcoord = vec2(left, top);
} else if (a_position == 2.0) {
v_texcoord = vec2(left, bottom);
} else if (a_position == 3.0) {
v_texcoord = vec2(right, bottom);
}
v_color = color;
if (a_tail == HEAD) {
gl_Position = u_mvp * vec4(a_p0 + (u_vertices[int(a_position)] * scale), 1.0);
} else {
// Get the normal to the tail in camera space.
// This allows to build a 2D rectangle around the 3D tail.
vec3 normal = cross(u_cameraZ, normalize(a_p1 - a_p0));
vec3 boundary = normal * scale * a_p2[0];
vec3 position;
if (a_position == 0.0) {
position = a_p0 - boundary;
} else if (a_position == 1.0) {
position = a_p1 - boundary;
} else if (a_position == 2.0) {
position = a_p1 + boundary;
} else if (a_position == 3.0) {
position = a_p0 + boundary;
}
gl_Position = u_mvp * vec4(position, 1.0);
}
}
void ribbon() {
vec3 position;
float left = a_leftRightTop[0] / 255.0;
float right = a_leftRightTop[1] / 255.0;
float top = a_leftRightTop[2] / 255.0;
float bottom = top + 1.0;
if (a_position == 0.0) {
v_texcoord = vec2(left, top);
position = a_p0;
} else if (a_position == 1.0) {
v_texcoord = vec2(left, bottom);
position = a_p1;
} else if (a_position == 2.0) {
v_texcoord = vec2(right, bottom);
position = a_p2;
} else if (a_position == 3.0) {
v_texcoord = vec2(right, top);
position = a_p3;
}
v_texcoord[0] /= u_columns;
v_texcoord[1] /= u_rows;
v_color = a_color;
gl_Position = u_mvp * vec4(position, 1.0);
}
void splat() {
float factor = u_lifeSpan - a_health;
int index;
if (factor < u_intervalTimes[0]) {
factor = factor / u_intervalTimes[0];
index = 0;
} else {
factor = (factor - u_intervalTimes[0]) / u_intervalTimes[1];
index = 1;
}
float cell = getCell(u_intervals[index], factor);
float left = floor(mod(cell, u_columns));
float top = floor(cell / u_columns);
float right = left + 1.0;
float bottom = top + 1.0;
vec3 position;
if (a_position == 0.0) {
v_texcoord = vec2(left, top);
position = a_p0;
} else if (a_position == 1.0) {
v_texcoord = vec2(left, bottom);
position = a_p1;
} else if (a_position == 2.0) {
v_texcoord = vec2(right, bottom);
position = a_p2;
} else if (a_position == 3.0) {
v_texcoord = vec2(right, top);
position = a_p3;
}
v_texcoord[0] /= u_columns;
v_texcoord[1] /= u_rows;
v_color = mix(u_colors[index], u_colors[index + 1], factor) / 255.0;
gl_Position = u_mvp * vec4(position, 1.0);
}
void uber() {
float factor = u_lifeSpan - a_health;
vec4 color;
if (factor < u_intervalTimes[0]) {
color = mix(u_colors[0], u_colors[1], factor / u_intervalTimes[0]);
} else if (factor < u_intervalTimes[0] + u_intervalTimes[1]) {
color = u_colors[1];
} else {
color = mix(u_colors[1], u_colors[2], (factor - u_intervalTimes[0] - u_intervalTimes[1]) / u_intervalTimes[2]);
}
vec3 position;
if (a_position == 0.0) {
v_texcoord = vec2(0.0, 0.0);
position = a_p0;
} else if (a_position == 1.0) {
v_texcoord = vec2(0.0, 1.0);
position = a_p1;
} else if (a_position == 2.0) {
v_texcoord = vec2(1.0, 1.0);
position = a_p2;
} else if (a_position == 3.0) {
v_texcoord = vec2(1.0, 0.0);
position = a_p3;
}
v_color = color / 255.0;
gl_Position = u_mvp * vec4(position, 1.0);
}
void main() {
if (u_emitter == EMITTER_PARTICLE2) {
particle2();
} else if (u_emitter == EMITTER_RIBBON) {
ribbon();
} else if (u_emitter == EMITTER_SPLAT) {
splat();
} else if (u_emitter == EMITTER_UBER) {
uber();
}
}
`,
fsParticles: `
uniform sampler2D u_texture;
uniform mediump float u_emitter;
uniform float u_filterMode;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
vec4 texel = texture2D(u_texture, v_texcoord);
vec4 color = texel * v_color;
// 1bit Alpha, used by ribbon emitters.
if (u_emitter == EMITTER_RIBBON && u_filterMode == 1.0 && color.a < 0.75) {
discard;
}
gl_FragColor = color;
}
`,
};