rigging and animation
This commit is contained in:
parent
b85f225789
commit
cc027a2517
11 changed files with 2120 additions and 38 deletions
108
src/3d/shaders/ps1_skinned_vertex.glsl
Normal file
108
src/3d/shaders/ps1_skinned_vertex.glsl
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// PS1-style skinned vertex shader for OpenGL 3.2+
|
||||
// Implements skeletal animation, vertex snapping, Gouraud shading, and fog
|
||||
|
||||
#version 150 core
|
||||
|
||||
// Uniforms - transform matrices
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
|
||||
// Uniforms - skeletal animation (max 64 bones)
|
||||
uniform mat4 u_bones[64];
|
||||
|
||||
// Uniforms - PS1 effects
|
||||
uniform vec2 u_resolution; // Internal render resolution for vertex snapping
|
||||
uniform bool u_enable_snap; // Enable vertex snapping to pixel grid
|
||||
uniform float u_fog_start; // Fog start distance
|
||||
uniform float u_fog_end; // Fog end distance
|
||||
|
||||
// Uniforms - lighting
|
||||
uniform vec3 u_light_dir; // Directional light direction (normalized)
|
||||
uniform vec3 u_ambient; // Ambient light color
|
||||
|
||||
// Attributes
|
||||
in vec3 a_position;
|
||||
in vec2 a_texcoord;
|
||||
in vec3 a_normal;
|
||||
in vec4 a_color;
|
||||
in vec4 a_bone_ids; // Up to 4 bone indices (as float for compatibility)
|
||||
in vec4 a_bone_weights; // Corresponding weights
|
||||
|
||||
// Varyings - passed to fragment shader
|
||||
out vec4 v_color; // Gouraud-shaded vertex color
|
||||
noperspective out vec2 v_texcoord; // Texture coordinates (affine interpolation!)
|
||||
out float v_fog; // Fog factor (0 = no fog, 1 = full fog)
|
||||
|
||||
void main() {
|
||||
// =========================================================================
|
||||
// Skeletal Animation: Vertex Skinning
|
||||
// Transform vertex and normal by weighted bone matrices
|
||||
// =========================================================================
|
||||
ivec4 bone_ids = ivec4(a_bone_ids); // Convert to integer indices
|
||||
|
||||
// Compute skinned position and normal
|
||||
mat4 skin_matrix =
|
||||
u_bones[bone_ids.x] * a_bone_weights.x +
|
||||
u_bones[bone_ids.y] * a_bone_weights.y +
|
||||
u_bones[bone_ids.z] * a_bone_weights.z +
|
||||
u_bones[bone_ids.w] * a_bone_weights.w;
|
||||
|
||||
vec4 skinned_pos = skin_matrix * vec4(a_position, 1.0);
|
||||
vec3 skinned_normal = mat3(skin_matrix) * a_normal;
|
||||
|
||||
// Transform vertex to clip space
|
||||
vec4 worldPos = u_model * skinned_pos;
|
||||
vec4 viewPos = u_view * worldPos;
|
||||
vec4 clipPos = u_projection * viewPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Vertex Snapping
|
||||
// The PS1 had limited precision for vertex positions, causing vertices
|
||||
// to "snap" to a grid, creating the characteristic jittery look.
|
||||
// =========================================================================
|
||||
if (u_enable_snap) {
|
||||
// Convert to NDC
|
||||
vec4 ndc = clipPos;
|
||||
ndc.xyz /= ndc.w;
|
||||
|
||||
// Snap to pixel grid based on render resolution
|
||||
vec2 grid = u_resolution * 0.5;
|
||||
ndc.xy = floor(ndc.xy * grid + 0.5) / grid;
|
||||
|
||||
// Convert back to clip space
|
||||
ndc.xyz *= clipPos.w;
|
||||
clipPos = ndc;
|
||||
}
|
||||
|
||||
gl_Position = clipPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Gouraud Shading
|
||||
// Per-vertex lighting was used on PS1 due to hardware limitations.
|
||||
// This creates characteristic flat-shaded polygons.
|
||||
// =========================================================================
|
||||
vec3 worldNormal = mat3(u_model) * skinned_normal;
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
// Simple directional light + ambient
|
||||
float diffuse = max(dot(worldNormal, -u_light_dir), 0.0);
|
||||
vec3 lighting = u_ambient + vec3(diffuse);
|
||||
|
||||
// Apply lighting to vertex color
|
||||
v_color = vec4(a_color.rgb * lighting, a_color.a);
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Affine Texture Mapping
|
||||
// Using 'noperspective' qualifier disables perspective-correct interpolation
|
||||
// This creates the characteristic texture warping on large polygons
|
||||
// =========================================================================
|
||||
v_texcoord = a_texcoord;
|
||||
|
||||
// =========================================================================
|
||||
// Fog Distance Calculation
|
||||
// Calculate linear fog factor based on view-space depth
|
||||
// =========================================================================
|
||||
float depth = -viewPos.z; // View space depth (positive)
|
||||
v_fog = clamp((depth - u_fog_start) / (u_fog_end - u_fog_start), 0.0, 1.0);
|
||||
}
|
||||
195
src/3d/shaders/ps1_skinned_vertex_es2.glsl
Normal file
195
src/3d/shaders/ps1_skinned_vertex_es2.glsl
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// PS1-style skinned vertex shader for OpenGL ES 2.0 / WebGL 1.0
|
||||
// Implements skeletal animation, vertex snapping, Gouraud shading, and fog
|
||||
|
||||
precision mediump float;
|
||||
|
||||
// Uniforms - transform matrices
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
|
||||
// Uniforms - skeletal animation (max 64 bones)
|
||||
// GLES2 doesn't guarantee support for arrays > 128 vec4s in vertex shaders
|
||||
// 64 bones * 4 vec4s = 256 vec4s, so we use 32 bones for safety
|
||||
uniform mat4 u_bones[32];
|
||||
|
||||
// Uniforms - PS1 effects
|
||||
uniform vec2 u_resolution; // Internal render resolution for vertex snapping
|
||||
uniform bool u_enable_snap; // Enable vertex snapping to pixel grid
|
||||
uniform float u_fog_start; // Fog start distance
|
||||
uniform float u_fog_end; // Fog end distance
|
||||
|
||||
// Uniforms - lighting
|
||||
uniform vec3 u_light_dir; // Directional light direction (normalized)
|
||||
uniform vec3 u_ambient; // Ambient light color
|
||||
|
||||
// Attributes
|
||||
attribute vec3 a_position;
|
||||
attribute vec2 a_texcoord;
|
||||
attribute vec3 a_normal;
|
||||
attribute vec4 a_color;
|
||||
attribute vec4 a_bone_ids; // Up to 4 bone indices (as floats)
|
||||
attribute vec4 a_bone_weights; // Corresponding weights
|
||||
|
||||
// Varyings - passed to fragment shader
|
||||
varying vec4 v_color; // Gouraud-shaded vertex color
|
||||
varying vec2 v_texcoord; // Texture coordinates (multiplied by w for affine trick)
|
||||
varying float v_w; // Clip space w for affine mapping restoration
|
||||
varying float v_fog; // Fog factor (0 = no fog, 1 = full fog)
|
||||
|
||||
// Helper to get bone matrix by index (GLES2 doesn't support dynamic array indexing well)
|
||||
mat4 getBoneMatrix(int index) {
|
||||
// GLES2 workaround: use if-chain for dynamic indexing
|
||||
if (index < 8) {
|
||||
if (index < 4) {
|
||||
if (index < 2) {
|
||||
if (index == 0) return u_bones[0];
|
||||
else return u_bones[1];
|
||||
} else {
|
||||
if (index == 2) return u_bones[2];
|
||||
else return u_bones[3];
|
||||
}
|
||||
} else {
|
||||
if (index < 6) {
|
||||
if (index == 4) return u_bones[4];
|
||||
else return u_bones[5];
|
||||
} else {
|
||||
if (index == 6) return u_bones[6];
|
||||
else return u_bones[7];
|
||||
}
|
||||
}
|
||||
} else if (index < 16) {
|
||||
if (index < 12) {
|
||||
if (index < 10) {
|
||||
if (index == 8) return u_bones[8];
|
||||
else return u_bones[9];
|
||||
} else {
|
||||
if (index == 10) return u_bones[10];
|
||||
else return u_bones[11];
|
||||
}
|
||||
} else {
|
||||
if (index < 14) {
|
||||
if (index == 12) return u_bones[12];
|
||||
else return u_bones[13];
|
||||
} else {
|
||||
if (index == 14) return u_bones[14];
|
||||
else return u_bones[15];
|
||||
}
|
||||
}
|
||||
} else if (index < 24) {
|
||||
if (index < 20) {
|
||||
if (index < 18) {
|
||||
if (index == 16) return u_bones[16];
|
||||
else return u_bones[17];
|
||||
} else {
|
||||
if (index == 18) return u_bones[18];
|
||||
else return u_bones[19];
|
||||
}
|
||||
} else {
|
||||
if (index < 22) {
|
||||
if (index == 20) return u_bones[20];
|
||||
else return u_bones[21];
|
||||
} else {
|
||||
if (index == 22) return u_bones[22];
|
||||
else return u_bones[23];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index < 28) {
|
||||
if (index < 26) {
|
||||
if (index == 24) return u_bones[24];
|
||||
else return u_bones[25];
|
||||
} else {
|
||||
if (index == 26) return u_bones[26];
|
||||
else return u_bones[27];
|
||||
}
|
||||
} else {
|
||||
if (index < 30) {
|
||||
if (index == 28) return u_bones[28];
|
||||
else return u_bones[29];
|
||||
} else {
|
||||
if (index == 30) return u_bones[30];
|
||||
else return u_bones[31];
|
||||
}
|
||||
}
|
||||
}
|
||||
return mat4(1.0); // Identity fallback
|
||||
}
|
||||
|
||||
void main() {
|
||||
// =========================================================================
|
||||
// Skeletal Animation: Vertex Skinning
|
||||
// Transform vertex and normal by weighted bone matrices
|
||||
// =========================================================================
|
||||
int b0 = int(a_bone_ids.x);
|
||||
int b1 = int(a_bone_ids.y);
|
||||
int b2 = int(a_bone_ids.z);
|
||||
int b3 = int(a_bone_ids.w);
|
||||
|
||||
// Compute skinned position and normal
|
||||
mat4 skin_matrix =
|
||||
getBoneMatrix(b0) * a_bone_weights.x +
|
||||
getBoneMatrix(b1) * a_bone_weights.y +
|
||||
getBoneMatrix(b2) * a_bone_weights.z +
|
||||
getBoneMatrix(b3) * a_bone_weights.w;
|
||||
|
||||
vec4 skinned_pos = skin_matrix * vec4(a_position, 1.0);
|
||||
vec3 skinned_normal = mat3(skin_matrix[0].xyz, skin_matrix[1].xyz, skin_matrix[2].xyz) * a_normal;
|
||||
|
||||
// Transform vertex to clip space
|
||||
vec4 worldPos = u_model * skinned_pos;
|
||||
vec4 viewPos = u_view * worldPos;
|
||||
vec4 clipPos = u_projection * viewPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Vertex Snapping
|
||||
// The PS1 had limited precision for vertex positions, causing vertices
|
||||
// to "snap" to a grid, creating the characteristic jittery look.
|
||||
// =========================================================================
|
||||
if (u_enable_snap) {
|
||||
// Convert to NDC
|
||||
vec4 ndc = clipPos;
|
||||
ndc.xyz /= ndc.w;
|
||||
|
||||
// Snap to pixel grid based on render resolution
|
||||
vec2 grid = u_resolution * 0.5;
|
||||
ndc.xy = floor(ndc.xy * grid + 0.5) / grid;
|
||||
|
||||
// Convert back to clip space
|
||||
ndc.xyz *= clipPos.w;
|
||||
clipPos = ndc;
|
||||
}
|
||||
|
||||
gl_Position = clipPos;
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Gouraud Shading
|
||||
// Per-vertex lighting was used on PS1 due to hardware limitations.
|
||||
// This creates characteristic flat-shaded polygons.
|
||||
// =========================================================================
|
||||
vec3 worldNormal = mat3(u_model[0].xyz, u_model[1].xyz, u_model[2].xyz) * skinned_normal;
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
// Simple directional light + ambient
|
||||
float diffuse = max(dot(worldNormal, -u_light_dir), 0.0);
|
||||
vec3 lighting = u_ambient + vec3(diffuse);
|
||||
|
||||
// Apply lighting to vertex color
|
||||
v_color = vec4(a_color.rgb * lighting, a_color.a);
|
||||
|
||||
// =========================================================================
|
||||
// PS1 Effect: Affine Texture Mapping Trick
|
||||
// GLES2 doesn't have 'noperspective' interpolation, so we manually
|
||||
// multiply texcoords by w here and divide by w in fragment shader.
|
||||
// This creates the characteristic texture warping on large polygons.
|
||||
// =========================================================================
|
||||
v_texcoord = a_texcoord * clipPos.w;
|
||||
v_w = clipPos.w;
|
||||
|
||||
// =========================================================================
|
||||
// Fog Distance Calculation
|
||||
// Calculate linear fog factor based on view-space depth
|
||||
// =========================================================================
|
||||
float depth = -viewPos.z; // View space depth (positive)
|
||||
v_fog = clamp((depth - u_fog_start) / (u_fog_end - u_fog_start), 0.0, 1.0);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue