commit ecb5bc420bd8e7ce8addf2b8d4024e06c210653d
parent b106bb82d1660f5fa7f805abf70fad0cd0520a3e
Author: Michael Savage <mikejsavage@gmail.com>
Date: Wed, 29 May 2019 13:03:57 +0300
GLTF WIP
Diffstat:
9 files changed, 391 insertions(+), 72 deletions(-)
diff --git a/RiggedFigure.glb b/RiggedFigure.glb
Binary files differ.
diff --git a/gltf.cc b/gltf.cc
@@ -6,6 +6,10 @@
#include "renderer.h"
#include "shaders.h"
+#include <map>
+
+#include "platform_time.h"
+
#define CGLTF_IMPLEMENTATION
#include "libs/cgltf/cgltf.h"
@@ -14,11 +18,7 @@ typedef v3 Vec3;
typedef m4 Mat4;
#define Lerp lerp
-
-Quaternion NLerp( Quaternion a, float t, Quaternion b ) {
- return Quaternion();
- // return a * ( 1.0f - t ) + b * t;
-}
+#define NLerp nlerp
struct SQT {
Quaternion rotation;
@@ -26,70 +26,131 @@ struct SQT {
float scale;
};
-constexpr size_t MaxJoints = 256;
-
-struct Skeleton {
- struct Joint {
- Mat4 joint_to_bind;
- u8 parent;
- };
+SQT Lerp( const SQT & from, float t, const SQT & to ) {
+ SQT res;
+ res.rotation = NLerp( from.rotation, t, to.rotation );
+ res.translation = Lerp( from.translation, t, to.translation );
+ res.scale = Lerp( from.scale, t, to.scale );
+ return res;
+}
- Joint joints[ 256 ];
- u8 num_joints;
-};
+static Mat4 SQTToMat4( const SQT & sqt ) {
+ quat q = sqt.rotation;
+ v3 t = sqt.translation;
+ float s = sqt.scale;
+
+ // return t * q * s;
+ return Mat4(
+ ( 1.0f - 2 * q.y * q.y - 2.0f * q.z * q.z ) * s,
+ ( 2.0f * q.x * q.y - 2.0f * q.z * q.w ) * s,
+ ( 2.0f * q.x * q.z + 2.0f * q.y * q.w ) * s,
+ t.x,
+
+ ( 2.0f * q.x * q.y + 2.0f * q.z * q.w ) * s,
+ ( 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z ) * s,
+ ( 2.0f * q.y * q.z - 2.0f * q.x * q.w ) * s,
+ t.y,
+
+ ( 2.0f * q.x * q.z - 2.0f * q.y * q.w ) * s,
+ ( 2.0f * q.y * q.z + 2.0f * q.x * q.w ) * s,
+ ( 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y ) * s,
+ t.z,
+
+ 0.0f, 0.0f, 0.0f, 1.0f
+ );
+}
struct AnimationSample {
SQT * joint_poses;
};
-struct AnimationClip {
- const Skeleton * skeleton;
+struct AnimationReel {
+ struct Joint {
+ Mat4 node_transform;
+ Mat4 joint_to_bind;
+ u8 parent;
+ };
struct Sample {
SQT * joint_poses;
- s64 time;
};
+ struct Clip {
+ u32 first_sample;
+ u32 num_frames;
+ float fps;
+ bool loop;
+ };
+
+ Joint * joints;
+ u8 num_joints;
+
Sample * samples;
-};
+ u32 num_samples;
-struct AnimationGlobalPose {
- Mat4 * joint_transforms;
+ Clip * clips;
+ u32 num_clips;
};
-struct AnimatedModel {
- // ...
+struct MatrixPalette {
+ Mat4 * joint_transforms;
};
-static Mat4 SQTToMat4( const SQT & from ) {
- return Mat4();
+float PositiveMod( float x, float y ) {
+ float res = fmodf( x, y );
+ if( res < 0 )
+ res += y;
+ return res;
}
-static void LerpAnimationSamples( const SQT * from, const SQT * to, u32 num_joints, float t, SQT * out ) {
- for( u32 i = 0; i < num_joints; i++ ) {
- out[ i ].rotation = NLerp( from[ i ].rotation, t, to[ i ].rotation );
- out[ i ].translation = Lerp( from[ i ].translation, t, to[ i ].translation );
- out[ i ].scale = Lerp( from[ i ].scale, t, to[ i ].scale );
+void SampleAnimationClip( const AnimationReel & reel, u32 clip_idx, float t, float start_time, SQT * joint_poses ) {
+ AnimationReel::Clip clip = reel.clips[ clip_idx ];
+
+ float clip_length = clip.num_frames / clip.fps;
+ float local_time;
+ if( clip.loop ) {
+ local_time = PositiveMod( t - start_time, clip_length );
+ }
+ else {
+ local_time = clamp( 0.0f, t - start_time, clip_length );
+ }
+
+ float local_frame = local_time * clip.fps;
+ float lerp_frac = local_frame - floorf( local_frame );
+
+ u32 prev_sample = u32( local_frame );
+ u32 next_sample = prev_sample + 1;
+ if( clip.loop )
+ next_sample %= clip.num_frames;
+
+ for( u32 i = 0; i < reel.num_joints; i++ ) {
+ joint_poses[ i ] = Lerp( reel.samples[ prev_sample ].joint_poses[ i ], lerp_frac, reel.samples[ next_sample ].joint_poses[ i ] );
}
}
-/*
-void SampleAnimationClip( const AnimationClip & clip, float t, float start_time, AnimationSample * sample ) {
- // TODO: t clamping/looping
- float local_time = t - start_time;
- float frac = local_time * clip.fps;
+void ComputeMatrixPalette( const AnimationReel & animation, const SQT * sqts, Mat4 * matrices ) {
+ todo2;
+ // skip the animation matrices to check i'm loading node hierarchy correctly
+ for( u32 i = 0; i < animation.num_joints; i++ ) {
+ matrices[ i ] = SQTToMat4( sqts[ i ] );
+ }
- AnimationSample prev_sample = clip.samples[ u32( frac ) ];
- AnimationSample next_sample = clip.samples[ u32( frac ) + 1 ];
- float lerp_frac = frac - floorf( frac );
+ matrices[ 0 ] *= animation.joints[ 0 ].node_transform;
+ for( u32 i = 1; i < animation.num_joints; i++ ) {
+ u8 parent = animation.joints[ i ].parent;
+ matrices[ i ] *= matrices[ parent ] * animation.joints[ parent ].node_transform;
+ }
- LerpAnimationSamples( prev_sample.joint_poses, next_sample.joint_poses, clip.skeleton->num_joints, lerp_frac, sample->joint_poses );
+ for( u32 i = 0; i < animation.num_joints; i++ ) {
+ matrices[ i ] *= animation.joints[ i ].joint_to_bind;
+ }
}
-*/
struct GLTFModel {
Mesh meshes[ 16 ];
u32 num_meshes;
+
+ AnimationReel animation;
};
// like cgltf_load_buffers, but doesn't try to load URIs
@@ -108,11 +169,18 @@ static bool LoadBinaryBuffers( cgltf_data * data ) {
return true;
}
+static AnimationReel::Clip CLIP;
static GLTFModel model;
-static Skeleton skeleton;
-static void LoadSkin( const cgltf_skin * skin ) {
+static void LoadSkin( const cgltf_skin * skin, std::map< const cgltf_node *, size_t > & node_to_joint ) {
printf( "skeleton %p\n", skin->skeleton );
+
+ model.animation.joints = ( AnimationReel::Joint * ) malloc( sizeof( AnimationReel::Joint ) * skin->joints_count );
+
+ todo1;
+ // find root of skeleton, not necessarily root of hierarchy
+ // bake transforms from root of skeleton to root of hierarchy into root joint
+ // depth first fill in joints from node hierarchy to get rid of the ordering assert
for( size_t i = 0; i < skin->joints_count; i++ ) {
printf( "joint %zu: %p\n", i, skin->joints[ i ] );
@@ -120,34 +188,91 @@ static void LoadSkin( const cgltf_skin * skin ) {
ASSERT( skin->joints[ i ]->children[ j ] > skin->joints[ i ] );
}
+ node_to_joint[ skin->joints[ i ] ] = i;
+
if( i == 0 ) {
- skeleton.joints[ i ].parent = 0;
+ model.animation.joints[ i ].parent = 0;
}
else {
- skeleton.joints[ i ].parent = checked_cast< u8 >( skin->joints[ i ]->parent - skin->joints[ 0 ] );
+ model.animation.joints[ i ].parent = checked_cast< u8 >( skin->joints[ i ]->parent - skin->joints[ 0 ] );
}
- cgltf_bool ok = cgltf_accessor_read_float( skin->inverse_bind_matrices, i, &skeleton.joints[ i ].joint_to_bind.col0.x, 16 );
+ cgltf_node_transform_local( skin->joints[ i ], &model.animation.joints[ i ].node_transform.col0.x );
+ cgltf_bool ok = cgltf_accessor_read_float( skin->inverse_bind_matrices, i, &model.animation.joints[ i ].joint_to_bind.col0.x, 16 );
ASSERT( ok != 0 );
}
- skeleton.num_joints = skin->joints_count;
- for( u8 i = 0; i < skeleton.num_joints; i++ ) {
- ggprint( "{}: {} {}\n", i, skeleton.joints[ i ].parent, skeleton.joints[ i ].joint_to_bind );
+ model.animation.num_joints = skin->joints_count;
+ for( u8 i = 0; i < model.animation.num_joints; i++ ) {
+ ggprint( "{}: {} {}\n", i, model.animation.joints[ i ].parent, model.animation.joints[ i ].joint_to_bind );
}
}
-static void LoadAnimation( MemoryArena * arena, const cgltf_animation * animation ) {
- ggprint( "LoadAnimation\n" );
- for( size_t i = 0; i < animation->samplers_count; i++ ) {
- ggprint( "{}\n", animation->samplers[ i ].interpolation );
+const char * pathtype( cgltf_animation_path_type type ) {
+ if( type == cgltf_animation_path_type_translation ) return "translation";
+ if( type == cgltf_animation_path_type_rotation ) return "rotation";
+ if( type == cgltf_animation_path_type_scale ) return "scale";
+ if( type == cgltf_animation_path_type_weights ) return "weights";
+ return "invalid";
+}
+
+static void LoadAnimationReel( MemoryArena * arena, const cgltf_animation * animation, const std::map< const cgltf_node *, size_t > & node_to_joint ) {
+ ggprint( "LoadAnimationReel\n" );
+ for( size_t i = 0; i < animation->channels_count; i++ ) {
+ const cgltf_animation_channel * chan = &animation->channels[ i ];
+ const cgltf_animation_sampler * sampler = chan->sampler;
- const cgltf_accessor * input = animation->samplers[ i ].input;
- for( size_t j = 0; j < input->count; j++ ) {
- float t;
- cgltf_accessor_read_float( input, j, &t, 1 );
- ggprint( "{}: {}\n", j, t );
+ ggprint( "channel {} {}\n", i, pathtype( chan->target_path ) );
+
+ ASSERT( node_to_joint.find( chan->target_node ) != node_to_joint.end() );
+ size_t joint_idx = node_to_joint.find( chan->target_node )->second;
+
+ ggprint( "interpolation {}\n", sampler->interpolation );
+ const cgltf_accessor * samples = sampler->output;
+
+ if( model.animation.samples == NULL ) {
+ model.animation.num_samples = samples->count;
+ model.animation.samples = ( AnimationReel::Sample * ) malloc( sizeof( AnimationReel::Sample * ) * model.animation.num_samples );
+
+ for( u32 j = 0; j < model.animation.num_samples; j++ ) {
+ model.animation.samples[ j ].joint_poses = ( SQT * ) malloc( sizeof( SQT ) * model.animation.num_joints );
+ }
+ }
+ else {
+ ASSERT( samples->count == model.animation.num_samples );
+ }
+
+ for( size_t j = 0; j < samples->count; j++ ) {
+ if( chan->target_path == cgltf_animation_path_type_translation ) {
+ cgltf_bool ok = cgltf_accessor_read_float( samples, j, &model.animation.samples[ j ].joint_poses[ joint_idx ].translation.x, 3 );
+ ASSERT( ok != 0 );
+ }
+ else if( chan->target_path == cgltf_animation_path_type_rotation ) {
+ cgltf_bool ok = cgltf_accessor_read_float( samples, j, &model.animation.samples[ j ].joint_poses[ joint_idx ].rotation.x, 4 );
+ ASSERT( ok != 0 );
+ }
+ else if( chan->target_path == cgltf_animation_path_type_scale ) {
+ float scale[ 3 ];
+ cgltf_bool ok = cgltf_accessor_read_float( samples, j, scale, 3 );
+ ASSERT( ok != 0 );
+
+ ASSERT( fabsf( scale[ 0 ] / scale[ 1 ] - 1.0f ) < 0.001 );
+ ASSERT( fabsf( scale[ 0 ] / scale[ 2 ] - 1.0f ) < 0.001 );
+
+ model.animation.samples[ j ].joint_poses[ joint_idx ].scale = scale[ 0 ];
+ }
+ else {
+ FATAL( "bad path type" );
+ }
}
}
+
+ CLIP.first_sample = 0;
+ CLIP.num_frames = 2;
+ CLIP.fps = 1;
+ CLIP.loop = true;
+
+ model.animation.clips = &CLIP;
+ model.animation.num_clips = 1;
}
array< const u8 > AccessorToSpan( const cgltf_accessor * accessor ) {
@@ -155,7 +280,7 @@ array< const u8 > AccessorToSpan( const cgltf_accessor * accessor ) {
return array< const u8 >( ( const u8 * ) accessor->buffer_view->buffer->data + offset, accessor->count * accessor->stride );
}
-static void LoadNode( MemoryArena * arena, const cgltf_node * node, int depth ) {
+static void LoadNode( MemoryArena * arena, const cgltf_node * node, std::map< const cgltf_node *, size_t > & node_to_joint, int depth ) {
MEMARENA_SCOPED_CHECKPOINT( arena );
Mat4 transform;
@@ -188,7 +313,7 @@ static void LoadNode( MemoryArena * arena, const cgltf_node * node, int depth )
}
printf( "- skin\n" );
- LoadSkin( node->skin );
+ LoadSkin( node->skin, node_to_joint );
}
ASSERT( node->mesh->primitives_count == 1 );
@@ -234,7 +359,7 @@ static void LoadNode( MemoryArena * arena, const cgltf_node * node, int depth )
}
mesh_config.indices = renderer_new_ib( AccessorToSpan( prim.indices ) );
- mesh_config.indices_format = INDEXFMT_U16;
+ mesh_config.indices_format = prim.indices->component_type == cgltf_component_type_r_16u ? INDEXFMT_U16 : INDEXFMT_U32;
mesh_config.num_vertices = prim.indices->count;
model.meshes[ model.num_meshes ] = renderer_new_mesh( mesh_config );
@@ -242,16 +367,19 @@ static void LoadNode( MemoryArena * arena, const cgltf_node * node, int depth )
}
for( size_t i = 0; i < node->children_count; i++ ) {
- LoadNode( arena, node->children[ i ], depth + 1 );
+ LoadNode( arena, node->children[ i ], node_to_joint, depth + 1 );
}
}
GAME_INIT( game_init ) {
+ memset( &model, 0, sizeof( model ) );
+
+ double before = get_time();
+
cgltf_options options = { };
- options.type = cgltf_file_type_glb;
cgltf_data * data;
- if( cgltf_parse_file( &options, "RiggedSimple.glb", &data ) != cgltf_result_success )
+ if( cgltf_parse_file( &options, "RiggedFigure.glb", &data ) != cgltf_result_success )
FATAL( "cgltf_parse_file" );
if( cgltf_load_buffers( &options, data, "./" ) != cgltf_result_success )
FATAL( "cgltf_load_buffers" );
@@ -262,16 +390,21 @@ GAME_INIT( game_init ) {
model.num_meshes = 0;
+ std::map< const cgltf_node *, size_t > node_to_joint;
+
for( size_t i = 0; i < data->scene->nodes_count; i++ ) {
- LoadNode( &mem->persistent_arena, data->scene->nodes[ i ], 0 );
+ LoadNode( &mem->persistent_arena, data->scene->nodes[ i ], node_to_joint, 0 );
}
- for( size_t i = 0; i < data->animations_count; i++ ) {
- LoadAnimation( &mem->persistent_arena, &data->animations[ i ] );
+ ASSERT( data->animations_count <= 1 );
+ if( data->animations_count > 0 ) {
+ LoadAnimationReel( &mem->persistent_arena, &data->animations[ 0 ], node_to_joint );
}
cgltf_free( data );
+ ggprint( "loading model took {.2}ms\n", ( get_time() - before ) * 1000.0 );
+
game->pos = v3( -10, -10, 5 );
game->pitch = 0;
game->yaw = 45;
@@ -303,18 +436,28 @@ GAME_FRAME( game_frame ) {
game->pos += right * dt * lr * speed;
game->pos.z += dt * dz * speed;
- const m4 P = m4_perspective( VERTICAL_FOV, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
+ const m4 P = m4_perspective( 90, get_aspect_ratio(), NEAR_PLANE_DEPTH, FAR_PLANE_DEPTH );
m4 V = m4_view( forward, right, up, game->pos );
+ SQT sample_sqt[ 100 ];
+ SampleAnimationClip( model.animation, 0, current_time, 0.0f, sample_sqt );
+
+ Mat4 sample_matrices[ 100 ];
+ ComputeMatrixPalette( model.animation, sample_sqt, sample_matrices );
+
renderer_begin_frame();
renderer_begin_pass( RENDERER_CLEAR_COLOUR_DO, RENDERER_CLEAR_DEPTH_DO );
UniformBinding view_uniforms = renderer_uniforms( V, P, game->pos );
+ UniformBinding joints_uniforms = renderer_upload_uniforms( sample_matrices, model.animation.num_joints * sizeof( Mat4 ), 16 );
+ todo3;
+ // render bones without the mesh attached to make sure transforms are correct
{
RenderState render_state;
- render_state.shader = get_shader( SHADER_FLAT_VERTEX_COLOURS );
+ render_state.shader = get_shader( SHADER_SKINNED_FLAT_VERTEX_COLOURS );
render_state.uniforms[ UNIFORMS_VIEW ] = view_uniforms;
+ render_state.uniforms[ UNIFORMS_JOINT_POSES ] = joints_uniforms;
render_state.textures[ 0 ] = renderer_blue_noise();
for( u32 i = 0; i < model.num_meshes; i++ ) {
diff --git a/libs/cgltf/cgltf.h b/libs/cgltf/cgltf.h
@@ -608,11 +608,13 @@ static const uint32_t GlbMagicBinChunk = 0x004E4942;
static void* cgltf_default_alloc(void* user, cgltf_size size)
{
+ (void)user;
return malloc(size);
}
static void cgltf_default_free(void* user, void* ptr)
{
+ (void)user;
free(ptr);
}
@@ -1659,6 +1661,7 @@ static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* toke
static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size)
{
+ (void)json_chunk;
CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY);
if (*out_array)
{
@@ -2420,6 +2423,7 @@ static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* token
static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler)
{
+ (void)options;
CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
out_sampler->wrap_s = 10497;
@@ -3478,6 +3482,7 @@ static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* toke
static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler)
{
+ (void)options;
CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i].size;
@@ -3532,6 +3537,7 @@ static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t
static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel)
{
+ (void)options;
CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
int size = tokens[i].size;
@@ -3932,7 +3938,7 @@ static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens
cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data)
{
- jsmn_parser parser = { 0 };
+ jsmn_parser parser = { 0, 0, 0 };
if (options->json_token_count == 0)
{
diff --git a/linear_algebra.h b/linear_algebra.h
@@ -273,6 +273,15 @@ forceinline m3::m3( const m4 & m ) {
// AUTOVISITOR
struct quat {
float x, y, z, w;
+
+ quat() { }
+
+ explicit quat( float a, float b, float c, float d ) {
+ x = a;
+ y = b;
+ z = c;
+ w = d;
+ }
};
// TODO: v3x4, v4x4, simd matrices?
@@ -301,6 +310,10 @@ forceinline v3 rad_to_deg( const v3 & v ) {
* v2
*/
+forceinline v2 operator+( v2 v, float x ) {
+ return v2( v.x + x, v.y + x );
+}
+
forceinline v2 operator+( v2 lhs, v2 rhs ) {
return v2( lhs.x + rhs.x, lhs.y + rhs.y );
}
@@ -325,6 +338,10 @@ forceinline v2 operator*( v2 lhs, v2 rhs ) {
return v2( lhs.x * rhs.x, lhs.y * rhs.y );
}
+forceinline void operator*=( v2 & lhs, v2 rhs ) {
+ lhs = lhs * rhs;
+}
+
forceinline v2 operator/( v2 v, float scale ) {
float inv_scale = 1.0f / scale;
return v * inv_scale;
@@ -879,6 +896,10 @@ forceinline m4 operator*( const m4 & lhs, const m4 & rhs ) {
);
}
+forceinline void operator*=( m4 & lhs, const m4 & rhs ) {
+ lhs = lhs * rhs;
+}
+
forceinline v4 operator*( const m4 & m, v4 v ) {
return v4(
dot( m.row0(), v ),
@@ -913,6 +934,72 @@ forceinline m4 m4_lookat( const v3 & position, const v3 & target, const v3 & wor
* quat
*/
+forceinline quat quat_identity() {
+ return quat( 0, 0, 0, 1 );
+}
+
+forceinline quat operator+( quat lhs, quat rhs ) {
+ return quat(
+ lhs.x + rhs.x,
+ lhs.y + rhs.y,
+ lhs.z + rhs.z,
+ lhs.w + rhs.w
+ );
+}
+
+forceinline quat operator-( quat lhs, quat rhs ) {
+ return quat(
+ lhs.x - rhs.x,
+ lhs.y - rhs.y,
+ lhs.z - rhs.z,
+ lhs.w - rhs.w
+ );
+}
+
+forceinline quat operator*( quat q, float scale ) {
+ return quat(
+ q.x * scale,
+ q.y * scale,
+ q.z * scale,
+ q.w * scale
+ );
+}
+
+forceinline quat operator*( float scale, quat q ) {
+ return q * scale;
+}
+
+forceinline quat operator/( quat q, float scale ) {
+ float inv_scale = 1.0f / scale;
+ return q * inv_scale;
+}
+
+forceinline void operator/=( quat & q, float scale ) {
+ q = q / scale;
+}
+
+forceinline quat operator-( quat q ) {
+ return quat( -q.x, -q.y, -q.z, -q.w );
+}
+
+forceinline float dot( quat lhs, quat rhs ) {
+ return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w;
+}
+
+forceinline float length( quat q ) {
+ return sqrtf( q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w );
+}
+
+forceinline quat normalize( quat q ) {
+ return q / length( q );
+}
+
+forceinline quat nlerp( quat from, float t, quat to ) {
+ float lt = 1.0f - t;
+ float rt = dot( from, to ) > 0 ? t : -t;
+ return normalize( from * lt + to * rt );
+}
+
/*
* misc. TODO: put this stuff somewhere else
*/
diff --git a/renderer.cc b/renderer.cc
@@ -22,7 +22,9 @@ static const GLuint ATTR_NORMAL = 1;
static const GLuint ATTR_TEX_COORD0 = 2;
static const GLuint ATTR_TEX_COORD1 = 3;
static const GLuint ATTR_COLOUR = 4;
-static const GLuint ATTR_MODEL_TO_WORLD_COL0 = 5;
+static const GLuint ATTR_JOINTS = 5;
+static const GLuint ATTR_WEIGHTS = 6;
+static const GLuint ATTR_MODEL_TO_WORLD_COL0 = 7;
static const u32 UNIFORM_BUFFER_SIZE = kilobytes( 64 );
@@ -383,6 +385,10 @@ void renderer_end_frame() {
renderer_delete_vb( del.mesh.colours );
if( del.mesh.indices != 0 )
renderer_delete_ib( del.mesh.indices );
+ if( del.mesh.joints != 0 )
+ renderer_delete_vb( del.mesh.joints );
+ if( del.mesh.weights != 0 )
+ renderer_delete_vb( del.mesh.weights );
glDeleteVertexArrays( 1, &del.mesh.vao );
break;
@@ -760,6 +766,8 @@ Shader renderer_new_shader( ShaderConfig config ) {
glBindAttribLocation( program, ATTR_TEX_COORD0, "tex_coord0" );
glBindAttribLocation( program, ATTR_TEX_COORD1, "tex_coord1" );
glBindAttribLocation( program, ATTR_COLOUR, "colour" );
+ glBindAttribLocation( program, ATTR_JOINTS, "joints" );
+ glBindAttribLocation( program, ATTR_WEIGHTS, "weights" );
glBindAttribLocation( program, ATTR_MODEL_TO_WORLD_COL0, "model_to_world" );
glBindFragDataLocation( program, 0, "output_albedo" );
@@ -780,7 +788,7 @@ Shader renderer_new_shader( ShaderConfig config ) {
return INVALID_SHADER;
}
- const char * ubo_names[] = { "debug", "view", "model", "light_view", "window", "sun", "sky", "clipmap", "clipmap_skirt" };
+ const char * ubo_names[] = { "debug", "view", "model", "light_view", "window", "sun", "sky", "clipmap", "clipmap_skirt", "animation" };
STATIC_ASSERT( ARRAY_COUNT( ubo_names ) == UNIFORMS_COUNT );
for( GLuint i = 0; i < ARRAY_COUNT( ubo_names ); i++ ) {
GLuint idx = glGetUniformBlockIndex( program, ubo_names[ i ] );
@@ -995,6 +1003,11 @@ static void vertexfmt_to_glenum( VertexFormat format, GLenum * type, int * compo
*components = 4;
break;
+ case VERTEXFMT_U16x4:
+ *type = GL_UNSIGNED_SHORT;
+ *components = 4;
+ break;
+
case VERTEXFMT_HALFx2:
*type = GL_HALF_FLOAT;
*components = 2;
@@ -1079,6 +1092,16 @@ Mesh renderer_new_mesh( MeshConfig config ) {
glBindBuffer( GL_ARRAY_BUFFER, config.colours );
setup_vertex_attrib( ATTR_COLOUR, config.colours_format );
}
+
+ if( config.joints != 0 ) {
+ glBindBuffer( GL_ARRAY_BUFFER, config.joints );
+ setup_vertex_attrib( ATTR_JOINTS, config.joints_format );
+ }
+
+ if( config.weights != 0 ) {
+ glBindBuffer( GL_ARRAY_BUFFER, config.weights );
+ setup_vertex_attrib( ATTR_WEIGHTS, config.weights_format );
+ }
}
else {
ASSERT( config.stride != 0 );
@@ -1102,6 +1125,14 @@ Mesh renderer_new_mesh( MeshConfig config ) {
if( config.colours_offset != 0 ) {
setup_vertex_attrib( ATTR_COLOUR, config.colours_format, config.stride, config.colours_offset );
}
+
+ if( config.joints_offset != 0 ) {
+ setup_vertex_attrib( ATTR_JOINTS, config.joints_format, config.stride, config.joints_offset );
+ }
+
+ if( config.weights_offset != 0 ) {
+ setup_vertex_attrib( ATTR_WEIGHTS, config.weights_format, config.stride, config.weights_offset );
+ }
}
if( config.indices != 0 ) {
@@ -1120,6 +1151,8 @@ Mesh renderer_new_mesh( MeshConfig config ) {
mesh.tex_coords0 = config.tex_coords0;
mesh.tex_coords1 = config.tex_coords1;
mesh.colours = config.colours;
+ mesh.joints = config.joints;
+ mesh.weights = config.weights;
}
else {
mesh.positions = config.unified_buffer;
diff --git a/renderer.h b/renderer.h
@@ -25,6 +25,7 @@ enum UniformSlots {
UNIFORMS_SKY,
UNIFORMS_CLIPMAP,
UNIFORMS_CLIPMAP_SKIRT,
+ UNIFORMS_JOINT_POSES,
UNIFORMS_COUNT,
};
@@ -146,6 +147,8 @@ enum VertexFormat {
VERTEXFMT_U8x3,
VERTEXFMT_U8x4,
+ VERTEXFMT_U16x4,
+
VERTEXFMT_HALFx2,
VERTEXFMT_HALFx3,
VERTEXFMT_HALFx4,
@@ -169,6 +172,8 @@ struct Mesh {
VB tex_coords0;
VB tex_coords1;
VB colours;
+ VB joints;
+ VB weights;
IB indices;
IndexFormat indices_format;
};
@@ -213,7 +218,7 @@ struct MeshConfig {
VertexFormat tex_coords0_format = VERTEXFMT_FLOATx2;
VertexFormat tex_coords1_format = VERTEXFMT_FLOATx2;
VertexFormat colours_format = VERTEXFMT_FLOATx3;
- VertexFormat joints_format = VERTEXFMT_U8x4;
+ VertexFormat joints_format = VERTEXFMT_U16x4;
VertexFormat weights_format = VERTEXFMT_FLOATx4;
IndexFormat indices_format = INDEXFMT_U32;
diff --git a/shaders.cc b/shaders.cc
@@ -87,6 +87,8 @@ void shaders_init() {
shaders[ SHADER_DEPTH_EDGE ].path = "shaders/depth_edge.glsl";
shaders[ SHADER_DEPTH_EDGE ].texture_uniform_names[ 0 ] = "model_depth";
+ shaders[ SHADER_SKINNED_FLAT_VERTEX_COLOURS ].path = "shaders/skinned_flat_vertex_colours.glsl";
+
int failed = hotload_shaders();
if( failed != 0 ) {
FATAL( "failed to load shaders" );
diff --git a/shaders.h b/shaders.h
@@ -21,6 +21,8 @@ enum ShaderID {
SHADER_GBUFFER,
SHADER_DEPTH_EDGE,
+ SHADER_SKINNED_FLAT_VERTEX_COLOURS,
+
SHADER_COUNT,
};
diff --git a/shaders/skinned_flat_vertex_colours.glsl b/shaders/skinned_flat_vertex_colours.glsl
@@ -0,0 +1,41 @@
+#ifdef VERTEX_SHADER
+
+in vec4 position;
+in vec3 colour;
+
+in vec4 joints;
+in vec4 weights;
+
+out vec3 frag_colour;
+
+layout( std140 ) uniform view {
+ mat4 V;
+ mat4 P;
+};
+
+layout( std140 ) uniform animation {
+ mat4 joint_poses[ 100 ];
+};
+
+void main() {
+ mat4 skin =
+ weights.x * joint_poses[ int( joints.x ) ] +
+ weights.y * joint_poses[ int( joints.y ) ] +
+ weights.z * joint_poses[ int( joints.z ) ] +
+ weights.w * joint_poses[ int( joints.w ) ];
+
+ gl_Position = P * V * skin * position;
+ frag_colour = colour;
+}
+
+#else
+
+in vec3 frag_colour;
+
+out vec4 screen_colour;
+
+void main() {
+ screen_colour = vec4( linear_to_srgb( frag_colour ), 1.0 );
+}
+
+#endif