medfall

A super great game engine
Log | Files | Refs

commit ecb5bc420bd8e7ce8addf2b8d4024e06c210653d
parent b106bb82d1660f5fa7f805abf70fad0cd0520a3e
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Wed, 29 May 2019 13:03:57 +0300

GLTF WIP

Diffstat:
ARiggedFigure.glb | 0
Mgltf.cc | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mlibs/cgltf/cgltf.h | 8+++++++-
Mlinear_algebra.h | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mrenderer.cc | 37+++++++++++++++++++++++++++++++++++--
Mrenderer.h | 7++++++-
Mshaders.cc | 2++
Mshaders.h | 2++
Ashaders/skinned_flat_vertex_colours.glsl | 41+++++++++++++++++++++++++++++++++++++++++
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