virtualx-engine/drivers/gles3/shaders/skeleton.glsl
clayjohn f33ffd9ab4 Add Skeletons and Blend Shapes to the OpenGL renderer
This uses a similar multipass approach to blend shapes
as Godot 3.x, the major difference here is that we
need to convert the normals and tangents to octahedral
for rendering.

Skeletons work the same as the Vulkan renderer except the bones
are stored in a texture as they were in 3.x.
2022-11-29 09:45:03 -08:00

269 lines
7.5 KiB
GLSL

/* clang-format off */
#[modes]
mode_base_pass =
mode_blend_pass = #define MODE_BLEND_PASS
#[specializations]
MODE_2D = true
USE_BLEND_SHAPES = false
USE_SKELETON = false
USE_NORMAL = false
USE_TANGENT = false
FINAL_PASS = false
USE_EIGHT_WEIGHTS = false
#[vertex]
#include "stdlib_inc.glsl"
#ifdef MODE_2D
#define VFORMAT vec2
#else
#define VFORMAT vec3
#endif
#ifdef FINAL_PASS
#define OFORMAT vec2
#else
#define OFORMAT uvec2
#endif
// These come from the source mesh and the output from previous passes.
layout(location = 0) in highp VFORMAT in_vertex;
#ifdef MODE_BLEND_PASS
#ifdef USE_NORMAL
layout(location = 1) in highp uvec2 in_normal;
#endif
#ifdef USE_TANGENT
layout(location = 2) in highp uvec2 in_tangent;
#endif
#else // MODE_BLEND_PASS
#ifdef USE_NORMAL
layout(location = 1) in highp vec2 in_normal;
#endif
#ifdef USE_TANGENT
layout(location = 2) in highp vec2 in_tangent;
#endif
#endif // MODE_BLEND_PASS
#ifdef USE_SKELETON
#ifdef USE_EIGHT_WEIGHTS
layout(location = 10) in highp uvec4 in_bone_attrib;
layout(location = 11) in highp uvec4 in_bone_attrib2;
layout(location = 12) in mediump vec4 in_weight_attrib;
layout(location = 13) in mediump vec4 in_weight_attrib2;
#else
layout(location = 10) in highp uvec4 in_bone_attrib;
layout(location = 11) in mediump vec4 in_weight_attrib;
#endif
uniform mediump sampler2D skeleton_texture; // texunit:0
#endif
/* clang-format on */
#ifdef MODE_BLEND_PASS
layout(location = 3) in highp VFORMAT blend_vertex;
#ifdef USE_NORMAL
layout(location = 4) in highp vec2 blend_normal;
#endif
#ifdef USE_TANGENT
layout(location = 5) in highp vec2 blend_tangent;
#endif
#endif // MODE_BLEND_PASS
out highp VFORMAT out_vertex; //tfb:
#ifdef USE_NORMAL
flat out highp OFORMAT out_normal; //tfb:USE_NORMAL
#endif
#ifdef USE_TANGENT
flat out highp OFORMAT out_tangent; //tfb:USE_TANGENT
#endif
#ifdef USE_BLEND_SHAPES
uniform highp float blend_weight;
uniform lowp float blend_shape_count;
#endif
vec2 signNotZero(vec2 v) {
return mix(vec2(-1.0), vec2(1.0), greaterThanEqual(v.xy, vec2(0.0)));
}
vec3 oct_to_vec3(vec2 oct) {
oct = oct * 2.0 - 1.0;
vec3 v = vec3(oct.xy, 1.0 - abs(oct.x) - abs(oct.y));
if (v.z < 0.0) {
v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy);
}
return normalize(v);
}
vec2 vec3_to_oct(vec3 e) {
e /= abs(e.x) + abs(e.y) + abs(e.z);
vec2 oct = e.z >= 0.0f ? e.xy : (vec2(1.0f) - abs(e.yx)) * signNotZero(e.xy);
return oct * 0.5f + 0.5f;
}
vec4 oct_to_tang(vec2 oct_sign_encoded) {
// Binormal sign encoded in y component
vec2 oct = vec2(oct_sign_encoded.x, abs(oct_sign_encoded.y) * 2.0 - 1.0);
return vec4(oct_to_vec3(oct), sign(oct_sign_encoded.y));
}
vec2 tang_to_oct(vec4 base) {
vec2 oct = vec3_to_oct(base.xyz);
// Encode binormal sign in y component
oct.y = oct.y * 0.5f + 0.5f;
oct.y = base.w >= 0.0f ? oct.y : 1.0 - oct.y;
return oct;
}
// Our original input for normals and tangents is 2 16-bit floats.
// Transform Feedback has to write out 32-bits per channel.
// Octahedral compression requires normalized vectors, but we need to store
// non-normalized vectors until the very end.
// Therefore, we will compress our normals into 16 bits using signed-normalized
// fixed point precision. This works well, because we know that each normal
// is no larger than |1| so we can normalize by dividing by the number of blend
// shapes.
uvec2 vec4_to_vec2(vec4 p_vec) {
return uvec2(packSnorm2x16(p_vec.xy), packSnorm2x16(p_vec.zw));
}
vec4 vec2_to_vec4(uvec2 p_vec) {
return vec4(unpackSnorm2x16(p_vec.x), unpackSnorm2x16(p_vec.y));
}
void main() {
#ifdef MODE_2D
out_vertex = in_vertex;
#ifdef USE_BLEND_SHAPES
#ifdef MODE_BLEND_PASS
out_vertex = in_vertex + blend_vertex * blend_weight;
#else
out_vertex = in_vertex * blend_weight;
#endif
#ifdef FINAL_PASS
out_vertex = normalize(out_vertex);
#endif
#endif // USE_BLEND_SHAPES
#ifdef USE_SKELETON
#define TEX(m) texelFetch(skeleton_texture, ivec2(m % 256u, m / 256u), 0)
#define GET_BONE_MATRIX(a, b, w) mat2x4(TEX(a), TEX(b)) * w
uvec4 bones = in_bone_attrib * uvec4(2u);
uvec4 bones_a = bones + uvec4(1u);
highp mat2x4 m = GET_BONE_MATRIX(bones.x, bones_a.x, in_weight_attrib.x);
m += GET_BONE_MATRIX(bones.y, bones_a.y, in_weight_attrib.y);
m += GET_BONE_MATRIX(bones.z, bones_a.z, in_weight_attrib.z);
m += GET_BONE_MATRIX(bones.w, bones_a.w, in_weight_attrib.w);
mat4 bone_matrix = mat4(m[0], m[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
//reverse order because its transposed
out_vertex = (vec4(out_vertex, 0.0, 1.0) * bone_matrix).xy;
#endif // USE_SKELETON
#else // MODE_2D
#ifdef USE_BLEND_SHAPES
#ifdef MODE_BLEND_PASS
out_vertex = in_vertex + blend_vertex * blend_weight;
#ifdef USE_NORMAL
vec3 normal = vec2_to_vec4(in_normal).xyz * blend_shape_count;
vec3 normal_blend = oct_to_vec3(blend_normal) * blend_weight;
#ifdef FINAL_PASS
out_normal = vec3_to_oct(normalize(normal + normal_blend));
#else
out_normal = vec4_to_vec2(vec4(normal + normal_blend, 0.0) / blend_shape_count);
#endif
#endif // USE_NORMAL
#ifdef USE_TANGENT
vec4 tangent = vec2_to_vec4(in_tangent) * blend_shape_count;
vec4 tangent_blend = oct_to_tang(blend_tangent) * blend_weight;
#ifdef FINAL_PASS
out_tangent = tang_to_oct(vec4(normalize(tangent.xyz + tangent_blend.xyz), tangent.w));
#else
out_tangent = vec4_to_vec2(vec4((tangent.xyz + tangent_blend.xyz) / blend_shape_count, tangent.w));
#endif
#endif // USE_TANGENT
#else // MODE_BLEND_PASS
out_vertex = in_vertex * blend_weight;
#ifdef USE_NORMAL
vec3 normal = oct_to_vec3(in_normal);
out_normal = vec4_to_vec2(vec4(normal * blend_weight / blend_shape_count, 0.0));
#endif
#ifdef USE_TANGENT
vec4 tangent = oct_to_tang(in_tangent);
out_tangent = vec4_to_vec2(vec4(tangent.rgb * blend_weight / blend_shape_count, tangent.w));
#endif
#endif // MODE_BLEND_PASS
#else // USE_BLEND_SHAPES
// Make attributes available to the skeleton shader if not written by blend shapes.
out_vertex = in_vertex;
#ifdef USE_NORMAL
out_normal = in_normal;
#endif
#ifdef USE_TANGENT
out_tangent = in_tangent;
#endif
#endif // USE_BLEND_SHAPES
#ifdef USE_SKELETON
#define TEX(m) texelFetch(skeleton_texture, ivec2(m % 256u, m / 256u), 0)
#define GET_BONE_MATRIX(a, b, c, w) mat4(TEX(a), TEX(b), TEX(c), vec4(0.0, 0.0, 0.0, 1.0)) * w
uvec4 bones = in_bone_attrib * uvec4(3);
uvec4 bones_a = bones + uvec4(1);
uvec4 bones_b = bones + uvec4(2);
highp mat4 m;
m = GET_BONE_MATRIX(bones.x, bones_a.x, bones_b.x, in_weight_attrib.x);
m += GET_BONE_MATRIX(bones.y, bones_a.y, bones_b.y, in_weight_attrib.y);
m += GET_BONE_MATRIX(bones.z, bones_a.z, bones_b.z, in_weight_attrib.z);
m += GET_BONE_MATRIX(bones.w, bones_a.w, bones_b.w, in_weight_attrib.w);
#ifdef USE_EIGHT_WEIGHTS
bones = in_bone_attrib2 * uvec4(3);
bones_a = bones + uvec4(1);
bones_b = bones + uvec4(2);
m += GET_BONE_MATRIX(bones.x, bones_a.x, bones_b.x, in_weight_attrib2.x);
m += GET_BONE_MATRIX(bones.y, bones_a.y, bones_b.y, in_weight_attrib2.y);
m += GET_BONE_MATRIX(bones.z, bones_a.z, bones_b.z, in_weight_attrib2.z);
m += GET_BONE_MATRIX(bones.w, bones_a.w, bones_b.w, in_weight_attrib2.w);
#endif
// Reverse order because its transposed.
out_vertex = (vec4(out_vertex, 1.0) * m).xyz;
#ifdef USE_NORMAL
vec3 vertex_normal = oct_to_vec3(out_normal);
out_normal = vec3_to_oct(normalize((vec4(vertex_normal, 0.0) * m).xyz));
#endif // USE_NORMAL
#ifdef USE_TANGENT
vec4 vertex_tangent = oct_to_tang(out_tangent);
out_tangent = tang_to_oct(vec4(normalize((vec4(vertex_tangent.xyz, 0.0) * m).xyz), vertex_tangent.w));
#endif // USE_TANGENT
#endif // USE_SKELETON
#endif // MODE_2D
}
/* clang-format off */
#[fragment]
void main() {
}
/* clang-format on */