/// Loads scenes from memory and files. /// /// Only the GLTF scene format is current supported. /// /// ---------------------------------------------------------------------------- /// glTF File Format Documentation /// ---------------------------------------------------------------------------- /// /// cgltf: /// https://github.com/jkuhlmann/cgltf /// /// gltf overview: /// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0b.png /// /// gltf spec: /// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md /// /// Sample models: /// https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 /// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Sponza/glTF/Sponza.gltf /// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AlphaBlendModeTest/glTF/AlphaBlendModeTest.gltf /// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Buggy/glTF/Buggy.gltf /// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera/glTF/AntiqueCamera.gltf /// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf /// /// ---------------------------------------------------------------------------- /// Implementation Notes /// ---------------------------------------------------------------------------- /// /// # glTF and the gfx library /// /// glTF has concepts that are similar to those in the gfx library, but there /// isn't an exact 1-1 mapping. Concepts map as follows: /// /// glTF gfx /// ---- --- /// buffer Buffer /// accessor + buffer view BufferView /// mesh primitive (geom + mat) Mesh (also geom + mat) /// mesh SceneObject /// node SceneNode /// /// glTF buffers map 1-1 with gfx Buffers. glTF scenes make heavy re-use of /// buffers across views/accessors/meshes, so it is important to make that same /// re-use in the gfx library to use the data effectively and without /// duplication. The Sponza scene, for example, has all of its data in one giant /// buffer. /// /// glTF accessors and buffer views are combined and mapped to gfx BufferViews. /// The glTF buffer view's offset/length/stride are combined with the accessor's /// offset, and together with the remaining information of both data structures /// baked into a BufferView. Internally, this information is fed into /// glVertexAttribPointer() calls, wrapped in a VAO (index view/accessor /// information is fed into glDrawElements()). This baking should not hurt /// re-use, at least in the OpenGL world. /// /// A glTF mesh primitive contains a piece of geometry and a material. This maps /// directly to a gfx Mesh. /// /// A glTF mesh is a list of mesh primitives. This maps nicely to a gfx /// SceneObject, with the only inconvenience that terminology gets a little /// confusing. /// /// Finally, glTF nodes map directly to gfx SceneNodes. Both enforce a strict /// tree hierarchy; DAGs are not supported. /// /// # Materials /// /// glTF uses the metallic-roughness material model. However, an extension /// allows a scene to use the specular-glossiness model as well and cgltf /// supports it: /// /// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/ /// /// From the docs, the specular-glossiness model can represent more materials /// than the metallic-roughness model, but it is also more computationally /// expensive. Furthermore, a material in glTF can specify parameters for both /// models, leaving it up to the implementation to decide which one to use. /// In our case, we use the specular-glosiness model if parameters for it are /// provided, otherwise we use the metallic-roughness model. #include "asset/model.h" #include "asset/texture.h" #include "gfx/core.h" #include "gfx/gfx.h" #include "gfx/scene/animation.h" #include "gfx/scene/camera.h" #include "gfx/scene/material.h" #include "gfx/scene/mesh.h" #include "gfx/scene/node.h" #include "gfx/scene/object.h" #include "gfx/scene/scene.h" #include "gfx/sizes.h" #include "gfx/util/shader.h" #include "gfx_assert.h" #include "scene/model_impl.h" #include "cstring.h" #include "error.h" #include "log/log.h" #include "math/camera.h" #include "math/defs.h" #include "math/mat4.h" #include "math/quat.h" #include "math/vec2.h" #include "math/vec3.h" #include "cgltf_tangents.h" #define CGLTF_IMPLEMENTATION #include "cgltf.h" #include #include // Taken from the GL header file. #define GL_NEAREST 0x2600 #define GL_LINEAR 0x2601 #define GL_NEAREST_MIPMAP_NEAREST 0x2700 #define GL_LINEAR_MIPMAP_NEAREST 0x2701 #define GL_NEAREST_MIPMAP_LINEAR 0x2702 #define GL_LINEAR_MIPMAP_LINEAR 0x2703 // Uniforms names. Must match the names in shaders. #define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor" #define UNIFORM_METALLIC_FACTOR "MetallicFactor" #define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor" #define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor" #define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture" #define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture" #define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture" #define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture" #define UNIFORM_NORMAL_MAP "NormalMap" // Shader compiler defines. Must match the names in shaders. #define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS" #define DEFINE_HAS_NORMALS "HAS_NORMALS" #define DEFINE_HAS_TANGENTS "HAS_TANGENTS" #define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP" #define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP" #define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" #define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" #define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" #define DEFINE_HAS_JOINTS "HAS_JOINTS" #define DEFINE_MAX_JOINTS "MAX_JOINTS" typedef enum TextureType { BaseColorTexture, MetallicRoughnessTexture, EmissiveTexture, AmbientOcclusionTexture, NormalMap, } TextureType; /// Describes the properties of a mesh. /// This is used to create shader permutations. typedef struct MeshPermutation { union { struct { // Vertex attributes. bool has_texcoords : 1; bool has_normals : 1; bool has_tangents : 1; bool has_joints : 1; bool has_weights : 1; // Textures. bool has_albedo_map : 1; bool has_metallic_roughness_map : 1; bool has_normal_map : 1; bool has_occlusion_map : 1; bool has_emissive_map : 1; }; int32_t all; }; } MeshPermutation; /// Build shader compiler defines from a mesh permutation. static size_t make_defines( MeshPermutation perm, ShaderCompilerDefine* defines) { static const char* str_true = "1"; size_t next = 0; #define check(field, define) \ if (perm.field) { \ defines[next].name = sstring_make(define); \ defines[next].value = sstring_make(str_true); \ next++; \ } check(has_texcoords, DEFINE_HAS_TEXCOORDS); check(has_normals, DEFINE_HAS_NORMALS); check(has_tangents, DEFINE_HAS_TANGENTS); check(has_joints, DEFINE_HAS_JOINTS); check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP); check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP); check(has_normal_map, DEFINE_HAS_NORMAL_MAP); check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); if (perm.has_joints) { defines[next].name = sstring_make(DEFINE_MAX_JOINTS); defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS); next++; } return next; } /// Compile a shader permutation. static ShaderProgram* make_shader_permutation( GfxCore* gfxcore, MeshPermutation perm) { LOGD( "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " "metallic-roughness map: " "%d, normal " "map: %d, AO map: %d, emissive map: %d", perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; const size_t num_defines = make_defines(perm, defines); return gfx_make_cook_torrance_shader_perm(gfxcore, defines, num_defines); } /// Map a texture type to the name of the shader uniform used to access the /// texture. static const char* get_texture_uniform_name(TextureType type) { switch (type) { case BaseColorTexture: return UNIFORM_BASE_COLOR_TEXTURE; case MetallicRoughnessTexture: return UNIFORM_METALLIC_ROUGHNESS_TEXTURE; case EmissiveTexture: return UNIFORM_EMISSIVE_TEXTURE; case AmbientOcclusionTexture: return UNIFORM_AMBIENT_OCCLUSION_TEXTURE; case NormalMap: return UNIFORM_NORMAL_MAP; } assert(false); return 0; } /// Map a glTF primitive type to a gfx primitive type. static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) { switch (type) { case cgltf_primitive_type_triangles: return Triangles; case cgltf_primitive_type_triangle_fan: return TriangleFan; case cgltf_primitive_type_triangle_strip: return TriangleStrip; // Not yet implemented. case cgltf_primitive_type_lines: case cgltf_primitive_type_line_loop: case cgltf_primitive_type_line_strip: case cgltf_primitive_type_points: break; } LOGE("Unsupported primitive type: %d", type); assert(false); return 0; } /// Map a glTF animation path type to its Gfx equivalent. static ChannelType from_gltf_animation_path_type( cgltf_animation_path_type type) { switch (type) { case cgltf_animation_path_type_translation: return TranslationChannel; case cgltf_animation_path_type_rotation: return RotationChannel; case cgltf_animation_path_type_scale: return ScaleChannel; case cgltf_animation_path_type_weights: return WeightsChannel; case cgltf_animation_path_type_invalid: assert(false); break; } assert(false); return 0; } /// Map a glTF interpolation to its Gfx equivalent. static AnimationInterpolation from_gltf_interpolation_type( cgltf_interpolation_type type) { switch (type) { case cgltf_interpolation_type_linear: return LinearInterpolation; case cgltf_interpolation_type_step: return StepInterpolation; case cgltf_interpolation_type_cubic_spline: return CubicSplineInterpolation; } assert(false); return 0; } /// Return the component's size in bytes. static cgltf_size get_component_size(cgltf_component_type type) { switch (type) { case cgltf_component_type_r_8: return 1; case cgltf_component_type_r_8u: return 1; case cgltf_component_type_r_16: return 2; case cgltf_component_type_r_16u: return 2; case cgltf_component_type_r_32u: return 4; case cgltf_component_type_r_32f: return 4; case cgltf_component_type_invalid: assert(false); break; } assert(false); return 0; } /// Return the number dimensionality of the given data type. int get_num_dimensions(cgltf_type type) { switch (type) { case cgltf_type_scalar: return 1; case cgltf_type_vec2: return 2; case cgltf_type_vec3: return 3; case cgltf_type_vec4: return 4; case cgltf_type_mat2: return 4; // 2x2 case cgltf_type_mat3: return 9; // 3x3 case cgltf_type_mat4: return 16; // 4x4 case cgltf_type_invalid: FAIL(); break; } FAIL(); return 0; } /// Read an int64 from the given data pointer and accessor. /// The largest integer in glTF is u32, so we can fit all integers in an int64. static int64_t read_int(const void* component, const cgltf_accessor* accessor) { assert(component); assert(accessor); switch (accessor->component_type) { case cgltf_component_type_r_8: { const int8_t c = *((int8_t*)component); return c; } case cgltf_component_type_r_8u: { const uint8_t c = *((uint8_t*)component); return c; } case cgltf_component_type_r_16: { const int16_t c = *((int16_t*)component); return c; } case cgltf_component_type_r_16u: { const uint16_t c = *((uint16_t*)component); return c; } case cgltf_component_type_r_32u: { const uint32_t c = *((uint32_t*)component); return c; } case cgltf_component_type_r_32f: { const float c = *((float*)component); return (int64_t)c; } case cgltf_component_type_invalid: FAIL(); break; } FAIL(); return 0; } /// Read a float from the given data pointer and accessor. /// /// This function uses the normalization equations from the spec. See the /// animation section: /// /// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations static float read_float(const void* component, const cgltf_accessor* accessor) { assert(component); assert(accessor); switch (accessor->component_type) { case cgltf_component_type_r_8: { // assert(accessor->normalized); const int8_t c = *((int8_t*)component); return max((float)c / 127.0, -1.0); } case cgltf_component_type_r_8u: { // assert(accessor->normalized); const uint8_t c = *((uint8_t*)component); return (float)c / 255.0; } case cgltf_component_type_r_16: { // assert(accessor->normalized); const int16_t c = *((int16_t*)component); return max((float)c / 32767.0, -1.0); } case cgltf_component_type_r_16u: { // assert(accessor->normalized); const uint16_t c = *((uint16_t*)component); return (float)c / 65535.0; } case cgltf_component_type_r_32u: { // assert(accessor->normalized); const uint32_t c = *((uint32_t*)component); return (float)c / 4294967295.0; } case cgltf_component_type_r_32f: { const float c = *((float*)component); return c; } case cgltf_component_type_invalid: FAIL(); break; } FAIL(); return 0; } typedef struct AccessorIter { const cgltf_accessor* accessor; const uint8_t* next_element; cgltf_size comp_size; // Component size in bytes. cgltf_size stride; // ELement stride in bytes. cgltf_size index; // Index of the next element. bool is_matrix; } AccessorIter; typedef struct AccessorData { union { struct { float x, y, z, w; // Possibly normalized. int64_t xi, yi, zi, wi; // Always unnormalized. }; const float* floats; }; } AccessorData; bool accessor_iter_next(AccessorIter* iter, AccessorData* data) { assert(iter); assert(data); if (iter->index < iter->accessor->count) { const int dimensions = get_num_dimensions(iter->accessor->type); const uint8_t* component = iter->next_element; // So that the caller can access the element's components as an array. data->floats = (const float*)component; if (!iter->is_matrix) { // Scalar or vector. // x data->x = read_float(component, iter->accessor); data->xi = read_int(component, iter->accessor); component += iter->comp_size; // y if (dimensions > 1) { data->y = read_float(component, iter->accessor); data->yi = read_int(component, iter->accessor); component += iter->comp_size; } // z if (dimensions > 2) { data->z = read_float(component, iter->accessor); data->zi = read_int(component, iter->accessor); component += iter->comp_size; } // w if (dimensions > 3) { data->w = read_float(component, iter->accessor); data->wi = read_int(component, iter->accessor); component += iter->comp_size; } } iter->next_element += iter->stride; iter->index++; return true; } return false; } AccessorIter make_accessor_iter(const cgltf_accessor* accessor) { assert(accessor); const bool is_matrix = (accessor->type == cgltf_type_mat2) || (accessor->type == cgltf_type_mat3) || (accessor->type == cgltf_type_mat4); const int dimensions = get_num_dimensions(accessor->type); assert( ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) || ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) || ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) || ((dimensions == 4) && (accessor->type == cgltf_type_vec4)) || ((dimensions == 4) && (accessor->type == cgltf_type_mat2)) || ((dimensions == 9) && (accessor->type == cgltf_type_mat3)) || ((dimensions == 16) && (accessor->type == cgltf_type_mat4))); const cgltf_buffer_view* view = accessor->buffer_view; const cgltf_buffer* buffer = view->buffer; const cgltf_size offset = accessor->offset + view->offset; const uint8_t* bytes = (const uint8_t*)buffer->data + offset; // Component size in bytes. const cgltf_size comp_size = get_component_size(accessor->component_type); // Element size in bytes. const cgltf_size elem_size = dimensions * comp_size; // Stride in bytes. If the view stride is 0, then the elements are tightly // packed. const cgltf_size stride = view->stride != 0 ? view->stride : elem_size; // There isn't an accessor stride in the spec, but cgltf still specifies one. assert(accessor->stride == elem_size); // Accessor data must fit inside the view. assert(accessor->offset + (accessor->count * accessor->stride) <= view->size); // Accessor data must fit inside the buffer. assert( (offset + (accessor->count * elem_size) + ((accessor->count - 1) * view->stride)) <= buffer->size); return (AccessorIter){ .accessor = accessor, .next_element = bytes, .comp_size = comp_size, .stride = stride, .index = 0, .is_matrix = is_matrix, }; } /// Return the total number of primitives in the scene. Each mesh may contain /// multiple primitives. /// /// Note that this function scans all of the scenes in the glTF data. static size_t get_total_primitives(const cgltf_data* data) { size_t total = 0; for (cgltf_size i = 0; i < data->meshes_count; ++i) { total += data->meshes[i].primitives_count; } return total; } /// Load all buffers from the glTF scene. /// /// If buffer data is loaded from memory, set filepath = null. /// /// Return an array of Buffers such that the index of each glTF buffer in the /// original array matches the same Buffer in the resulting array. /// /// TODO: There is no need to load the inverse bind matrices buffer into the /// GPU. Might need to lazily load buffers. static bool load_buffers( const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers) { assert(data); assert(gfxcore); assert(buffers); for (cgltf_size i = 0; i < data->buffers_count; ++i) { const cgltf_buffer* buffer = &data->buffers[i]; assert(buffer->data); buffers[i] = gfx_make_buffer( gfxcore, &(BufferDesc){ .usage = BufferStatic, .type = BufferUntyped, .data.data = buffer->data, .data.count = buffer->size}); if (!buffers[i]) { return false; } } return true; } /// Load tangent buffers. static bool load_tangent_buffers( const cgltfTangentBuffer* cgltf_tangent_buffers, cgltf_size num_tangent_buffers, GfxCore* gfxcore, Buffer** tangent_buffers) { assert(cgltf_tangent_buffers); assert(gfxcore); assert(tangent_buffers); for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i]; assert(buffer->data); tangent_buffers[i] = gfx_make_buffer( gfxcore, &(BufferDesc){ .usage = BufferStatic, .type = BufferUntyped, .data.data = buffer->data, .data.count = buffer->size_bytes}); if (!tangent_buffers[i]) { return false; } } return true; } /// Lazily load all textures from the glTF scene. /// /// Colour textures like albedo are in sRGB colour space. Non-colour textures /// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we /// don't know how the texture is going to be used at this point, we can't tell /// what colour space it should be loaded in (ideally this would be part of the /// image file format, but not all formats specify colour space.) Therefore, we /// load the textures lazily and don't actually commit them to GPU memory until /// we know their colour space when loading glTF materials. /// /// Return an array of LoadTextureCmds such that the index of each cmd matches /// the index of each glTF texture in the scene. static void load_textures_lazy( const cgltf_data* data, GfxCore* gfxcore, const char* directory, LoadTextureCmd* load_texture_cmds) { assert(data); assert(gfxcore); assert(load_texture_cmds); for (cgltf_size i = 0; i < data->textures_count; ++i) { const cgltf_texture* texture = &data->textures[i]; const cgltf_image* image = texture->image; const cgltf_sampler* sampler = texture->sampler; // glTF models might not specify a sampler. In such case, the client can // pick its own defaults. // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers bool mipmaps = true; TextureFiltering filtering = LinearFiltering; TextureWrapping wrap = Repeat; if (sampler) { // The gfx library does not distinguish between sampling the texture and // combining the mipmap levels. const cgltf_int filter = sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter; switch (filter) { case GL_NEAREST_MIPMAP_NEAREST: mipmaps = true; filtering = NearestFiltering; break; case GL_NEAREST_MIPMAP_LINEAR: case GL_LINEAR_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_LINEAR: mipmaps = true; filtering = LinearFiltering; break; case GL_NEAREST: filtering = NearestFiltering; break; case GL_LINEAR: filtering = LinearFiltering; break; default: break; } } // Currently only supporting loading textures from files. assert(image->uri); assert(directory); mstring fullpath = mstring_concat_path(mstring_make(directory), mstring_make(image->uri)); load_texture_cmds[i] = (LoadTextureCmd){ .origin = AssetFromFile, .type = LoadTexture, .colour_space = sRGB, .filtering = filtering, .wrap = wrap, .mipmaps = mipmaps, .data.texture.filepath = fullpath}; } } /// Load a texture uniform. /// /// This determines a texture's colour space based on its intended use, loads /// the texture, and then defines the sampler shader uniform. static bool load_texture_and_uniform( const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view, TextureType texture_type, const Texture** textures, LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) { assert(data); assert(gfx); assert(texture_view); assert(textures); assert(next_uniform); assert(desc); assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); const size_t texture_index = texture_view->texture - data->textures; assert(texture_index < data->textures_count); // Here we are assuming that if a texture is re-used, it is re-used with the // same texture view. This should be fine because, e.g., a normal map would // not be used as albedo and vice versa. if (!textures[texture_index]) { LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; // TODO: Check for colour textures and default to LinearColourSpace instead. if (texture_type == NormalMap) { cmd->colour_space = LinearColourSpace; } LOGD( "Load texture: %s (mipmaps: %d, filtering: %d)", mstring_cstr(&cmd->data.texture.filepath), cmd->mipmaps, cmd->filtering); textures[texture_index] = gfx_load_texture(gfx, cmd); if (!textures[texture_index]) { log_error( "Failed to load texture: %s", mstring_cstr(&cmd->data.texture.filepath)); return false; } } assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc->uniforms[(*next_uniform)++] = (ShaderUniform){ .name = sstring_make(get_texture_uniform_name(texture_type)), .type = UniformTexture, .value.texture = textures[texture_index]}; return true; } /// Load all materials from the glTF scene. /// /// Return an array of Materials such that the index of each descriptor matches /// the index of each glTF material in the scene. Also return the number of /// materials and the textures used by them. static bool load_materials( const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds, const Texture** textures, Material** materials) { assert(data); assert(gfx); assert(materials); if (data->textures_count > 0) { assert(load_texture_cmds); assert(textures); } for (cgltf_size i = 0; i < data->materials_count; ++i) { const cgltf_material* mat = &data->materials[i]; int next_uniform = 0; MaterialDesc desc = {0}; // TODO: specular/glossiness and other material parameters. if (mat->has_pbr_metallic_roughness) { const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), .type = UniformVec4, .value.vec4 = vec4_from_array(pbr->base_color_factor)}; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_METALLIC_FACTOR), .type = UniformFloat, .value.scalar = pbr->metallic_factor}; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), .type = UniformFloat, .value.scalar = pbr->roughness_factor}; assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), .type = UniformVec3, .value.vec3 = vec3_from_array(mat->emissive_factor)}; if (pbr->base_color_texture.texture) { if (!load_texture_and_uniform( data, gfx, &pbr->base_color_texture, BaseColorTexture, textures, load_texture_cmds, &next_uniform, &desc)) { return false; } } if (pbr->metallic_roughness_texture.texture) { if (!load_texture_and_uniform( data, gfx, &pbr->metallic_roughness_texture, MetallicRoughnessTexture, textures, load_texture_cmds, &next_uniform, &desc)) { return false; } } } if (mat->emissive_texture.texture) { if (!load_texture_and_uniform( data, gfx, &mat->emissive_texture, EmissiveTexture, textures, load_texture_cmds, &next_uniform, &desc)) { return false; } } if (mat->occlusion_texture.texture) { if (!load_texture_and_uniform( data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture, textures, load_texture_cmds, &next_uniform, &desc)) { return false; } } if (mat->normal_texture.texture) { if (!load_texture_and_uniform( data, gfx, &mat->normal_texture, NormalMap, textures, load_texture_cmds, &next_uniform, &desc)) { return false; } } assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.num_uniforms = next_uniform; materials[i] = gfx_make_material(&desc); if (!materials[i]) { return false; } } return true; } /// Create a default material for meshes that do not have a material. static Material* make_default_material() { MaterialDesc desc = (MaterialDesc){0}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), .type = UniformVec4, .value.vec4 = vec4_make(1, 1, 1, 1)}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ .name = sstring_make(UNIFORM_METALLIC_FACTOR), .type = UniformFloat, .value.scalar = 0}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), .type = UniformFloat, .value.scalar = 1}; assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), .type = UniformVec3, .value.vec3 = vec3_make(0, 0, 0)}; return gfx_make_material(&desc); } /// Compute the bounding box of the vertices pointed to by the accessor. /// 'dim' is the dimension of the vertices (2D or 3D). aabb3 compute_aabb(const cgltf_accessor* accessor) { aabb3 box = {0}; if (accessor->has_min && accessor->has_max) { box = aabb3_make( vec3_from_array(accessor->min), vec3_from_array(accessor->max)); } else { AccessorIter iter = make_accessor_iter(accessor); AccessorData vertex = {0}; cgltf_size i = 0; while (accessor_iter_next(&iter, &vertex)) { const vec3 p = vec3_make(vertex.x, vertex.y, vertex.z); if (i == 0) { box = aabb3_make(p, p); } else { box = aabb3_add(box, p); } ++i; } } return box; } /// Load all meshes from the glTF scene. static bool load_meshes( const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers, Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers, cgltf_size num_tangent_buffers, Material** materials, ShaderProgram* const shader, size_t primitive_count, Geometry** geometries, Mesh** meshes, SceneObject** scene_objects) { // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to // a SceneObject. // // glTF gfx // ---- --- // Mesh SceneObject // Mesh primitive Mesh / Geometry // Accessor + buffer view BufferView // Buffer Buffer assert(data); assert(gfxcore); assert(buffers); assert(materials); assert(geometries); assert(meshes); assert(scene_objects); if (num_tangent_buffers > 0) { assert(tangent_buffers); assert(cgltf_tangent_buffers); } // Points to the next available Mesh and also the next available Geometry. // There is one (Mesh, Geometry) pair per glTF mesh primitive. size_t next_mesh = 0; for (cgltf_size m = 0; m < data->meshes_count; ++m) { const cgltf_mesh* mesh = &data->meshes[m]; ObjectDesc object_desc = {0}; for (cgltf_size p = 0; p < mesh->primitives_count; ++p) { assert(next_mesh < primitive_count); const cgltf_primitive* prim = &mesh->primitives[p]; const cgltf_material* mat = prim->material; MeshPermutation perm = {0}; if (mat) { perm.has_normal_map = mat->normal_texture.texture != 0; perm.has_occlusion_map = mat->occlusion_texture.texture != 0; perm.has_emissive_map = mat->emissive_texture.texture != 0; if (mat->has_pbr_metallic_roughness) { const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; perm.has_albedo_map = pbr->base_color_texture.texture != 0; perm.has_metallic_roughness_map = pbr->metallic_roughness_texture.texture != 0; } else { // TODO: specular/glossiness and other material parameters. } } GeometryDesc geometry_desc = { .type = from_gltf_primitive_type(prim->type), .buffer_usage = BufferStatic}; // Vertex indices. if (prim->indices) { const cgltf_accessor* accessor = prim->indices; const cgltf_buffer_view* view = prim->indices->buffer_view; const cgltf_size buffer_index = view->buffer - data->buffers; assert(buffer_index < data->buffers_count); Buffer* buffer = buffers[buffer_index]; const cgltf_size component_size = get_component_size(accessor->component_type); switch (component_size) { case 1: { BufferViewIdx8* indices = &geometry_desc.indices8; // TODO: discards const qualifier. indices->buffer = buffer; indices->offset_bytes = accessor->offset + view->offset; indices->size_bytes = view->size; indices->stride_bytes = view->stride; geometry_desc.num_indices = prim->indices->count; break; } case 2: { BufferViewIdx16* indices = &geometry_desc.indices16; indices->buffer = buffer; indices->offset_bytes = accessor->offset + view->offset; indices->size_bytes = view->size; indices->stride_bytes = view->stride; geometry_desc.num_indices = prim->indices->count; break; } default: // TODO: Handle 32-bit indices. assert(false); break; } } // Vertex attributes. for (cgltf_size a = 0; a < prim->attributes_count; ++a) { const cgltf_attribute* attrib = &prim->attributes[a]; const cgltf_accessor* accessor = attrib->data; const cgltf_buffer_view* view = accessor->buffer_view; const cgltf_size offset = accessor->offset + view->offset; const cgltf_size buffer_index = view->buffer - data->buffers; assert(buffer_index < data->buffers_count); Buffer* buffer = buffers[buffer_index]; BufferView2d* buffer_view_2d = 0; BufferView3d* buffer_view_3d = 0; BufferView4d* buffer_view_4d = 0; BufferViewFloat* buffer_view_float = 0; BufferViewU8* buffer_view_u8 = 0; BufferViewU16* buffer_view_u16 = 0; switch (attrib->type) { case cgltf_attribute_type_position: { switch (accessor->type) { case cgltf_type_vec2: assert(geometry_desc.positions3d.buffer == 0); buffer_view_2d = &geometry_desc.positions2d; geometry_desc.aabb = compute_aabb(accessor); break; case cgltf_type_vec3: assert(geometry_desc.positions2d.buffer == 0); buffer_view_3d = &geometry_desc.positions3d; geometry_desc.aabb = compute_aabb(accessor); break; default: FAIL( "Unhandled accessor type %d in vertex positions", accessor->type); assert(false); return false; } // It is assumed that meshes have positions, so there is nothing to // do for the mesh permutation in this case. break; } case cgltf_attribute_type_normal: buffer_view_3d = &geometry_desc.normals; perm.has_normals = true; break; case cgltf_attribute_type_tangent: buffer_view_4d = &geometry_desc.tangents; perm.has_tangents = true; break; case cgltf_attribute_type_texcoord: buffer_view_2d = &geometry_desc.texcoords; perm.has_texcoords = true; break; case cgltf_attribute_type_color: // TODO: Add support for color. break; case cgltf_attribute_type_joints: // Joints can be either u8 or u16. switch (accessor->component_type) { case cgltf_component_type_r_8u: buffer_view_u8 = &geometry_desc.joints.u8; break; case cgltf_component_type_r_16u: buffer_view_u16 = &geometry_desc.joints.u16; break; default: assert(false); return false; } perm.has_joints = true; break; case cgltf_attribute_type_weights: // Weights can be either u8, u16, or float. switch (accessor->component_type) { case cgltf_component_type_r_8u: buffer_view_u8 = &geometry_desc.weights.u8; break; case cgltf_component_type_r_16u: buffer_view_u16 = &geometry_desc.weights.u16; break; case cgltf_component_type_r_32f: buffer_view_float = &geometry_desc.weights.floats; break; default: assert(false); return false; } perm.has_weights = true; break; case cgltf_attribute_type_invalid: assert(false); break; } #define CONFIGURE_BUFFER(buf) \ if (buf) { \ buf->buffer = buffer; \ buf->offset_bytes = offset; \ buf->size_bytes = view->size; \ buf->stride_bytes = view->stride; \ } CONFIGURE_BUFFER(buffer_view_2d); CONFIGURE_BUFFER(buffer_view_3d); CONFIGURE_BUFFER(buffer_view_4d); CONFIGURE_BUFFER(buffer_view_u8); CONFIGURE_BUFFER(buffer_view_u16); CONFIGURE_BUFFER(buffer_view_float); } // Vertex attributes. assert( (perm.has_joints && perm.has_weights) || (!perm.has_joints && !perm.has_weights)); // If the mesh primitive has no tangents, see if they were computed // separately. if (!geometry_desc.tangents.buffer) { for (cgltf_size t = 0; t < num_tangent_buffers; ++t) { const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t]; if (cgltf_buffer->primitive == prim) { BufferView4d* view = &geometry_desc.tangents; view->buffer = tangent_buffers[t]; view->offset_bytes = 0; view->size_bytes = cgltf_buffer->size_bytes; view->stride_bytes = 0; // Tightly packed. break; } } } // Set the number of vertices in the geometry. Since a geometry can have // either 2d or 3d positions but not both, here we can perform addition // to compute the total number of vertices. geometry_desc.num_verts = (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + (geometry_desc.positions3d.size_bytes / sizeof(vec3)); #define CHECK_COUNT(buffer_view, type, num_components) \ if (geometry_desc.buffer_view.buffer) { \ assert( \ (geometry_desc.buffer_view.size_bytes / \ (num_components * sizeof(type))) == geometry_desc.num_verts); \ } // Check that the number of vertices is consistent across all vertex // attributes. CHECK_COUNT(normals, vec3, 1); CHECK_COUNT(tangents, vec4, 1); CHECK_COUNT(texcoords, vec2, 1); CHECK_COUNT(joints.u8, uint8_t, 4); CHECK_COUNT(joints.u16, uint16_t, 4); CHECK_COUNT(weights.u8, uint8_t, 4); CHECK_COUNT(weights.u16, uint16_t, 4); CHECK_COUNT(weights.floats, float, 4); Material* material = 0; if (mat) { const cgltf_size material_index = mat - data->materials; assert(material_index < data->materials_count); material = materials[material_index]; } else { // Create a default material for meshes that do not specify one. material = make_default_material(); } assert(material); geometries[next_mesh] = gfx_make_geometry(gfxcore, &geometry_desc); if (!geometries[next_mesh]) { return false; } // If the user specifies a custom shader, use that instead. Otherwise // compile a shader based on the mesh's permutation. // // Note that Gfx takes care of caching shaders and shader programs. // // Caching materials could be useful, but, provided they can share // shaders, the renderer can check later whether uniforms have the same // values. Also, changing uniforms is much faster than swapping shaders, // so shader caching is the most important thing here. ShaderProgram* mesh_shader = shader ? shader : make_shader_permutation(gfxcore, perm); assert(mesh_shader); meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ .geometry = geometries[next_mesh], .material = material, .shader = mesh_shader}); if (!meshes[next_mesh]) { return false; } assert(object_desc.num_meshes < GFX_MAX_NUM_MESHES); object_desc.meshes[object_desc.num_meshes] = meshes[next_mesh]; object_desc.num_meshes++; ++next_mesh; } // glTF mesh primitive / gfx Mesh. scene_objects[m] = gfx_make_object(&object_desc); if (!scene_objects[m]) { return false; } } // glTF mesh / gfx SceneObject. return true; } /// Compute bounding boxes for the joints in the model. static void compute_joint_bounding_boxes( const cgltf_data* data, size_t num_joints, JointDesc* joint_descs) { assert(data); assert(joint_descs); assert(num_joints <= GFX_MAX_NUM_JOINTS); // Initialize bounding boxes so that we can compute unions below. for (size_t i = 0; i < num_joints; ++i) { joint_descs[i].box = aabb3_make_empty(); } // Iterate over the meshes -> primitives -> vertices -> joint indices, and add // the vertex to the joint's bounding box. for (cgltf_size n = 0; n < data->nodes_count; ++n) { const cgltf_node* node = &data->nodes[n]; if (node->skin) { if (node->mesh) { const cgltf_mesh* mesh = node->mesh; for (cgltf_size pr = 0; pr < mesh->primitives_count; ++pr) { const cgltf_primitive* prim = &mesh->primitives[pr]; // Find the indices of the positions and joints arrays in the // primitive's attributes. int positions_index = -1; int joints_index = -1; for (int a = 0; a < (int)prim->attributes_count; ++a) { const cgltf_attribute* attrib = &prim->attributes[a]; if (attrib->type == cgltf_attribute_type_position) { positions_index = a; } else if (attrib->type == cgltf_attribute_type_joints) { joints_index = a; } } if ((positions_index != -1) && (joints_index != -1)) { const cgltf_accessor* positions = prim->attributes[positions_index].data; const cgltf_accessor* joints = prim->attributes[joints_index].data; assert(positions->count == joints->count); AccessorIter positions_iter = make_accessor_iter(positions); AccessorIter joints_iter = make_accessor_iter(joints); AccessorData position = {0}, joint = {0}; while (accessor_iter_next(&positions_iter, &position)) { const bool advance = accessor_iter_next(&joints_iter, &joint); assert(advance); // Counts should match. const vec3 p = vec3_make(position.x, position.y, position.z); const int64_t j[4] = {joint.xi, joint.yi, joint.wi, joint.zi}; for (int i = 0; i < 4; ++i) { const size_t joint_index = j[i]; assert((size_t)joint_index < num_joints); joint_descs[joint_index].box = aabb3_add(joint_descs[joint_index].box, p); } } } } } } } } /// Find the joint node with the smallest index across all skeletons. /// /// The channels in glTF may target arbitrary nodes in the scene (those nodes /// are the joints). However, we want to map the "base joint" (the joint/node /// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this /// by subtracting the "base node index" from every joint index or channel /// target. /// /// There is an assumption in the animation library that joints are contiguous /// anyway, so this "base joint index" works provided the joint nodes are also /// contiguous in the glTF. The glTF does not guarantee this, but I think it's /// a reasonable assumption that exporters write glTF files in such a way, and /// Blender does appear to do so. cgltf_size find_base_joint_index(const cgltf_data* data) { assert(data); cgltf_size base_joint_index = (cgltf_size)-1; for (cgltf_size s = 0; s < data->skins_count; ++s) { const cgltf_skin* skin = &data->skins[s]; for (cgltf_size j = 0; j < skin->joints_count; ++j) { // Joint is an index/pointer into the nodes array. const cgltf_size node_index = skin->joints[j] - data->nodes; assert(node_index < data->nodes_count); // Min. if (node_index < base_joint_index) { base_joint_index = node_index; } } } return base_joint_index; } /// Load all skins (Gfx skeletons) from the glTF scene. /// Return the total number of joints. static size_t load_skins( const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index, AnimaDesc* anima_desc) { assert(data); assert(buffers); assert(anima_desc); assert(base_joint_index < data->nodes_count); // Determines whether the ith joint in the node hierarchy is a joint node. // This is then used to determine whether a joint is a root of the joint // hierarchy. bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false}; size_t num_joints = 0; for (cgltf_size s = 0; s < data->skins_count; ++s) { const cgltf_skin* skin = &data->skins[s]; const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices; assert(matrices_accessor->count == skin->joints_count); num_joints += skin->joints_count; assert(num_joints < GFX_MAX_NUM_JOINTS); SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s]; *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count}; // for (cgltf_size j = 0; j < skin->joints_count; ++j) { AccessorIter iter = make_accessor_iter(matrices_accessor); AccessorData matrix = {0}; for (cgltf_size i = 0; accessor_iter_next(&iter, &matrix); ++i) { const mat4 inv_bind_matrix = mat4_from_array(matrix.floats); // Joint is an index/pointer into the nodes array. const cgltf_size node_index = skin->joints[i] - data->nodes; assert(node_index < data->nodes_count); const cgltf_size parent_node_index = skin->joints[i]->parent - data->nodes; assert(parent_node_index < data->nodes_count); // Subtract the base index to pack the joints as tightly as possible in // the AnimaDesc. assert(node_index >= base_joint_index); const cgltf_size joint_index = node_index - base_joint_index; assert(parent_node_index >= base_joint_index); const cgltf_size parent_index = parent_node_index - base_joint_index; skeleton_desc->joints[i] = joint_index; JointDesc* joint_desc = &anima_desc->joints[joint_index]; joint_desc->parent = parent_index; joint_desc->inv_bind_matrix = inv_bind_matrix; is_joint_node[joint_index] = true; }; // glTF may specify a "skeleton", which is the root of the skin's // (skeleton's) node hierarchy. // if (skin->skeleton) { // // cgltf_size root_index = skin->skeleton - data->nodes; // // assert(root_index <= data->nodes_count); // // root_node = nodes[root_index]; // assert(false); //} } // Animation library assumes that joints are contiguous. for (size_t i = 0; i < num_joints; ++i) { assert(is_joint_node[i]); } // Insert the root joint. // This is the root of all skeletons. It is, specifically, the root of all // joints that do not have a parent; skins (skeletons) in glTF are not // guaranteed to have a common parent, but are generally a set of disjoint // trees. const size_t root_index = num_joints; assert(root_index < GFX_MAX_NUM_JOINTS); anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE}; num_joints++; // Make root joints point to the root joint at index N. // The root joints are the ones that have a non-joint node in the glTF as a // parent. for (size_t i = 0; i < root_index; ++i) { JointDesc* joint = &anima_desc->joints[i]; if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) { joint->parent = root_index; } } return num_joints; } /// Load all animations from the glTF scene. static void load_animations( const cgltf_data* data, cgltf_size base_joint_index, AnimaDesc* anima_desc) { assert(data); assert(anima_desc); assert(base_joint_index < data->nodes_count); assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS); for (cgltf_size a = 0; a < data->animations_count; ++a) { const cgltf_animation* animation = &data->animations[a]; AnimationDesc* animation_desc = &anima_desc->animations[a]; *animation_desc = (AnimationDesc){ .name = sstring_make(animation->name), .num_channels = animation->channels_count}; assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); for (cgltf_size c = 0; c < animation->channels_count; ++c) { const cgltf_animation_channel* channel = &animation->channels[c]; ChannelDesc* channel_desc = &animation_desc->channels[c]; const cgltf_animation_sampler* sampler = channel->sampler; const size_t target_index = channel->target_node - data->nodes; assert(target_index < data->nodes_count); assert(target_index >= base_joint_index); const size_t tight_target_index = target_index - base_joint_index; assert(tight_target_index < anima_desc->num_joints); *channel_desc = (ChannelDesc){ .target = tight_target_index, .type = from_gltf_animation_path_type(channel->target_path), .interpolation = from_gltf_interpolation_type(sampler->interpolation), .num_keyframes = 0}; // Read time inputs. AccessorIter iter = make_accessor_iter(sampler->input); AccessorData input = {0}; for (cgltf_size i = 0; accessor_iter_next(&iter, &input); ++i) { channel_desc->keyframes[i].time = input.x; channel_desc->num_keyframes++; } // Read transform outputs. AccessorData output = {0}; switch (channel->target_path) { case cgltf_animation_path_type_translation: { iter = make_accessor_iter(sampler->output); for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) { channel_desc->keyframes[i].translation = vec3_make(output.x, output.y, output.z); } break; } case cgltf_animation_path_type_rotation: { iter = make_accessor_iter(sampler->output); for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) { channel_desc->keyframes[i].rotation = qmake(output.x, output.y, output.z, output.w); } break; } default: // TODO: Handle other channel transformations. break; } } } } /// Load all nodes from the glTF scene. /// /// This function ignores the many scenes and default scene of the glTF spec /// and instead just loads all nodes into a single gfx Scene. static void load_nodes( const cgltf_data* data, SceneNode* root_node, SceneObject** objects, SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a // disjount union of strict trees: // // "For Version 2.0 conformance, the glTF node hierarchy is not a directed // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. // That is, no node may be a direct descendant of more than one node. This // restriction is meant to simplify implementation and facilitate // conformance." // // This matches the gfx library implementation, where every node can have at // most one parent. assert(data); assert(root_node); assert(objects); assert(cameras); assert(nodes); cgltf_size next_camera = 0; for (cgltf_size n = 0; n < data->nodes_count; ++n) { const cgltf_node* node = &data->nodes[n]; // Add SceneObject, SceneCamera or Lights. // TODO: Handle lights once they are implemented in the gfx library. if (node->mesh) { const cgltf_size mesh_index = node->mesh - data->meshes; assert(mesh_index < data->meshes_count); SceneObject* object = objects[mesh_index]; gfx_construct_object_node(nodes[n], object); if (node->skin) { assert(anima); const cgltf_size skin_index = node->skin - data->skins; assert(skin_index < data->skins_count); const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); gfx_set_object_skeleton(object, skeleton); } } else if (node->camera) { assert(next_camera < data->cameras_count); Camera camera; const cgltf_camera* cam = node->camera; // TODO: We could define a function load_cameras() the same way we load // every mesh and then remove this ad-hoc loading of cameras here, as well // as remove 'next_camera'. switch (cam->type) { case cgltf_camera_type_orthographic: camera = camera_orthographic( 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag, cam->data.orthographic.znear, cam->data.orthographic.zfar); break; case cgltf_camera_type_perspective: camera = camera_perspective( cam->data.perspective.yfov, cam->data.perspective.aspect_ratio, cam->data.perspective.znear, cam->data.perspective.zfar); break; case cgltf_camera_type_invalid: break; } gfx_set_camera_camera(cameras[next_camera], &camera); gfx_construct_camera_node(nodes[n], cameras[next_camera]); ++next_camera; } else { // TODO: implementation for missing node types. // These nodes currently default to logical nodes. } assert(nodes[n]); // Set transform. mat4 transform; if (node->has_matrix) { transform = mat4_from_array(node->matrix); } else { transform = mat4_id(); if (node->has_scale) { const mat4 scale = mat4_scale(vec3_from_array(node->scale)); transform = mat4_mul(transform, scale); } if (node->has_rotation) { const quat q = quat_from_array(node->rotation); const mat4 rotate = mat4_from_quat(q); transform = mat4_mul(transform, rotate); } if (node->has_translation) { const mat4 translate = mat4_translate(vec3_from_array(node->translation)); transform = mat4_mul(translate, transform); } } gfx_set_node_transform(nodes[n], &transform); // If this is a top-level node in the glTF scene, set its parent to the // given root node. if (!node->parent) { gfx_set_node_parent(nodes[n], root_node); } else { const cgltf_size parent_index = node->parent - data->nodes; assert(parent_index < data->nodes_count); SceneNode* parent = nodes[parent_index]; assert(parent); gfx_set_node_parent(nodes[n], parent); } } // SceneNode. } /// Remove joint nodes from the Gfx Scene. /// /// Joint nodes are not needed because joints are packed into the Anima. static void remove_joint_nodes( const cgltf_data* data, SceneNode** scene_nodes) { assert(data); assert(scene_nodes); // This works assuming the joint nodes are contiguous. Contiguity is checked // when loading skins. See load_skins(). size_t min_joint_index = (size_t)-1; size_t max_joint_index = 0; // First get the minimum and maximum indices of all joint nodes. for (cgltf_size s = 0; s < data->skins_count; ++s) { const cgltf_skin* skin = &data->skins[s]; for (cgltf_size j = 0; j < skin->joints_count; ++j) { // Joint is an index/pointer into the nodes array. const cgltf_size joint_index = skin->joints[j] - data->nodes; assert(joint_index < data->nodes_count); if (joint_index < min_joint_index) { min_joint_index = joint_index; } if (joint_index > max_joint_index) { max_joint_index = joint_index; } } } assert(min_joint_index < data->nodes_count); assert(max_joint_index < data->nodes_count); // Now walk over the joint nodes. If a joint's parent is itself not a joint // node, then that joint is a root of a joint hierarchy (skins in glTF may // have multiple roots). In such case, delete the root joint recursively. for (cgltf_size s = 0; s < data->skins_count; ++s) { const cgltf_skin* skin = &data->skins[s]; for (cgltf_size j = 0; j < skin->joints_count; ++j) { // Joint is an index/pointer into the nodes array. const cgltf_size joint_index = skin->joints[j] - data->nodes; assert(joint_index < data->nodes_count); const cgltf_node* joint = &data->nodes[joint_index]; // Parent node index. const cgltf_size parent_index = joint->parent - data->nodes; assert(parent_index < data->nodes_count); // If the parent is not a joint node, recursively delete this joint node. if ((parent_index < min_joint_index) || (parent_index > max_joint_index)) { gfx_destroy_node(&scene_nodes[joint_index]); } } } } /// Load all scenes from the glTF file. /// /// If the scene is loaded from memory, set filepath = null. /// /// This function ignores the many scenes and default scene of the glTF spec /// and instead just loads all scenes into a single Gfx Scene. static Model* load_scene( cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader, const cgltfTangentBuffer* cgltf_tangent_buffers, cgltf_size num_tangent_buffers) { // In a GLTF scene, buffers can be shared among meshes, meshes among nodes, // etc. Each object is referenced by its index in the relevant array. Here we // do a button-up construction, first allocating our own graphics objects in // the same quantities and then re-using the GLTF indices to index these // arrays. // // For simplicity, this function also handles all of the cleanup. Arrays are // allocated up front, and the helper functions construct their elements. If // an error is encountered, the helper functions can simply return and this // function cleans up any intermediate objects that had been created up until // the point of failure. // // Loading animation data: // - Buffers with animation sampler data need to stay on the CPU, not // uploaded to the GPU. We could try to implement GPU animation at a later // stage. assert(data); assert(gfx); assert(filepath); assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0)); bool success = false; GfxCore* gfxcore = gfx_get_core(gfx); const size_t primitive_count = get_total_primitives(data); const mstring directory = mstring_dirname(*filepath); LOGD("Filepath: %s", mstring_cstr(filepath)); LOGD("Directory: %s", mstring_cstr(&directory)); Buffer** tangent_buffers = 0; Buffer** buffers = 0; LoadTextureCmd* load_texture_cmds = 0; const Texture** textures = 0; // Textures are owned by asset cache. Material** materials = 0; Geometry** geometries = 0; Mesh** meshes = 0; AnimaDesc* anima_desc = 0; SceneObject** scene_objects = 0; SceneCamera** scene_cameras = 0; SceneNode** scene_nodes = 0; Anima* anima = 0; SceneNode* root_node = 0; Model* model = 0; tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*)); buffers = calloc(data->buffers_count, sizeof(Buffer*)); textures = calloc(data->textures_count, sizeof(Texture*)); materials = calloc(data->materials_count, sizeof(Material*)); geometries = calloc(primitive_count, sizeof(Geometry*)); meshes = calloc(primitive_count, sizeof(Mesh*)); scene_objects = calloc(data->meshes_count, sizeof(SceneObject*)); scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**)); scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**)); // A glTF scene does not necessarily have textures. Materials can be given // as constants, for example. if (data->textures_count > 0) { load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); } if (!buffers || !tangent_buffers || ((data->textures_count > 0) && !load_texture_cmds) || !textures || !materials || !geometries || !meshes || !scene_objects || !scene_cameras || !scene_nodes) { goto cleanup; } if ((num_tangent_buffers > 0) && !load_tangent_buffers( cgltf_tangent_buffers, num_tangent_buffers, gfxcore, tangent_buffers)) { goto cleanup; } if (!load_buffers(data, gfxcore, buffers)) { goto cleanup; } if (data->textures_count > 0) { load_textures_lazy( data, gfxcore, mstring_cstr(&directory), load_texture_cmds); } if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) { goto cleanup; } if (!load_meshes( data, gfxcore, buffers, tangent_buffers, cgltf_tangent_buffers, num_tangent_buffers, materials, shader, primitive_count, geometries, meshes, scene_objects)) { goto cleanup; } // Skins refer to nodes, and nodes may refer to skins. To break this circular // dependency, glTF defines skins in terms of node indices. We could do the // same if Gfx allowed allocating nodes contiguously in memory. For now, // create the nodes up front and use the indices of the array to map to the // node_idx. for (cgltf_size i = 0; i < data->nodes_count; ++i) { scene_nodes[i] = gfx_make_node(); } // Create the scene's root node. // This is an anima node if the scene has skins; otherwise it is a logical // node. root_node = gfx_make_node(); if (data->skins_count > 0) { anima_desc = calloc(1, sizeof(AnimaDesc)); if (!anima_desc) { goto cleanup; } const cgltf_size base = find_base_joint_index(data); anima_desc->num_skeletons = data->skins_count; anima_desc->num_animations = data->animations_count; anima_desc->num_joints = load_skins(data, buffers, base, anima_desc); load_animations(data, base, anima_desc); compute_joint_bounding_boxes( data, anima_desc->num_joints, anima_desc->joints); anima = gfx_make_anima(anima_desc); gfx_construct_anima_node(root_node, anima); } // The root node becomes the root of all scene nodes. load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); // Clean up scene nodes that correspond to joints in the glTF. These are // not needed anymore. if (data->skins_count > 0) { remove_joint_nodes(data, scene_nodes); } model = gfx_make_model(root_node); success = true; cleanup: // The arrays of resources are no longer needed. The resources themselves are // destroyed only if this function fails. if (tangent_buffers) { if (!success) { for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { if (tangent_buffers[i]) { gfx_destroy_buffer(gfxcore, &tangent_buffers[i]); } } } free(tangent_buffers); } if (buffers) { if (!success) { for (cgltf_size i = 0; i < data->buffers_count; ++i) { if (buffers[i]) { gfx_destroy_buffer(gfxcore, &buffers[i]); } } } free(buffers); } if (load_texture_cmds) { free(load_texture_cmds); } if (textures) { free(textures); } if (materials) { if (!success) { for (cgltf_size i = 0; i < data->materials_count; ++i) { if (materials[i]) { gfx_destroy_material(&materials[i]); } } } free(materials); } if (geometries) { if (!success) { for (size_t i = 0; i < primitive_count; ++i) { if (geometries[i]) { gfx_destroy_geometry(gfxcore, &geometries[i]); } } } free(geometries); } if (meshes) { if (!success) { for (size_t i = 0; i < primitive_count; ++i) { if (meshes[i]) { gfx_destroy_mesh(&meshes[i]); } } } free(meshes); } if (anima_desc) { free(anima_desc); } if (scene_objects) { if (!success) { for (cgltf_size i = 0; i < data->meshes_count; ++i) { if (scene_objects[i]) { gfx_destroy_object(&scene_objects[i]); } } } free(scene_objects); } if (scene_cameras) { if (!success) { for (cgltf_size i = 0; i < data->cameras_count; ++i) { if (scene_cameras[i]) { gfx_destroy_camera(&scene_cameras[i]); } } } free(scene_cameras); } if (scene_nodes) { if (!success) { for (cgltf_size i = 0; i < data->nodes_count; ++i) { if (scene_nodes[i]) { gfx_destroy_node(&scene_nodes[i]); } } } free(scene_nodes); } if (!success) { if (root_node) { gfx_destroy_node(&root_node); // Node owns the anima. } else if (anima) { gfx_destroy_anima(&anima); } } return model; } Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) { assert(gfx); assert(cmd); Model* model = 0; cgltf_options options = {0}; cgltf_data* data = NULL; cgltfTangentBuffer* tangent_buffers = 0; cgltf_result result; switch (cmd->origin) { case AssetFromFile: result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data); break; case AssetFromMemory: result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data); break; } if (result != cgltf_result_success) { goto cleanup; } if (cmd->origin == AssetFromFile) { // Must call cgltf_load_buffers() to load buffer data. result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath)); if (result != cgltf_result_success) { goto cleanup; } } // Compute tangents for normal-mapped models that are missing them. cgltf_size num_tangent_buffers = 0; cgltf_compute_tangents( &options, data, &tangent_buffers, &num_tangent_buffers); model = load_scene( data, gfx, &cmd->filepath, cmd->shader, tangent_buffers, num_tangent_buffers); cleanup: if (data) { cgltf_free(data); } if (tangent_buffers) { free(tangent_buffers); } return model; }