Giter Site home page Giter Site logo

ufbx's Introduction

ufbx CI codecov

Single source file FBX file loader.

ufbx_load_opts opts = { 0 }; // Optional, pass NULL for defaults
ufbx_error error; // Optional, pass NULL if you don't care about errors
ufbx_scene *scene = ufbx_load_file("thing.fbx", &opts, &error);
if (!scene) {
    fprintf(stderr, "Failed to load: %s\n", error.description.data);
    exit(1);
}

// Use and inspect `scene`, it's just plain data!

// Let's just list all objects within the scene for example:
for (size_t i = 0; i < scene->nodes.count; i++) {
    ufbx_node *node = scene->nodes.data[i];
    if (node->is_root) continue;

    printf("Object: %s\n", node->name.data);
    if (node->mesh) {
        printf("-> mesh with %zu faces\n", node->mesh->faces.count);
    }
}

ufbx_free_scene(scene);

Documentation

Online documentation

Setup

Copy ufbx.h and ufbx.c to your project, ufbx.c needs to be compiled as C99/C++11 or more recent. You can also add misc/ufbx.natvis to get debug formatting for the types.

Features

The goal is to be at feature parity with the official FBX SDK.

  • Supports binary and ASCII FBX files starting from version 3000
  • Safe
    • Invalid files and out-of-memory conditions are handled gracefully
    • Loaded scenes are sanitized by default, no out-of-bounds indices or non-UTF-8 strings
    • Extensively tested
  • Various object types
    • Meshes, skinning, blend shapes
    • Lights and cameras
    • Embedded textures
    • NURBS curves and surfaces
    • Geometry caches
    • LOD groups
    • Display/selection sets
    • Rigging constraints
  • Unified PBR material from known vendor-specific materials
  • Various utilities for evaluating the scene (can be compiled out if not needed)
    • Polygon triangulation
    • Index generation
    • Animation curve evaluation / layer blending
    • CPU skinning evaluation
    • Subdivision surface evaluation
    • NURBS curve/surface tessellation
  • Progress reporting and cancellation
  • Support for Wavefront .obj files as well

Platforms

The library is written in portable C (also compiles as C++) and should work on almost any platform without modification. If compiled as pre-C11/C++11 on an unknown compiler (not MSVC/Clang/GCC/TCC), some functions will not be thread-safe as C99 does not have support for atomics.

The following platforms are tested on CI and produce bit-exact results:

  • Windows: MSVC x64/x86, Clang x64/x86, GCC MinGW x64
  • macOS: Clang x64, GCC x64
  • Linux: Clang x64/x86/ARM64/ARM32/PowerPC, GCC x64/x86/ARM64/ARM32, TCC x64/x86
  • WASI: Clang WASM

Testing

  • Internal tests run on all platforms listed above
    • 592 test cases / 604 FBX files
  • Fuzzed in multiple layers
    • Parsers (fbx binary/fbx ascii/deflate/xml/mcx/obj/mtl) fuzzed using AFL
    • Structured FBX binary/ascii fuzzing using AFL
    • Built-in fuzzing for byte modifications/truncation/out-of-memory
    • Semantic fuzzing for binary FBX and OBJ files
  • Public dataset: 4.7GB / 323 files
    • Loaded, validated, and compared against reference .obj files
  • Private dataset: 33.6GB / 12618 files
    • Loaded and validated
  • Static analysis for maximum stack depth on Linux GCC/Clang
  • In total 95% branch line coverage (99% partial line coverage)

Versioning

The latest commit in the master branch contains the latest stable version of the library.

Older versions are tagged as vX.Y.Z, patch updates (Z) are ABI compatible and work with older versions of the header from the same minor version (Y). Minor versions within a major verision (X) are expected to be source compatible after 1.0.0 but the 0.Y.Z releases can break for every minor release.

License

------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2020 Samuli Raivio
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------

ufbx's People

Contributors

attilaz avatar bqqbarbhg avatar kvark avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ufbx's Issues

Point/line support for OBJ

I remember you had to do something extra to support points and lines in FBX (as those are represented as degenerated triangles, IIRC), how hard would it be to extend this to OBJ? In particular, the following two files produce an empty scene and zero meshes, while I'd expect a point and a line mesh:

v 0.5 2 3
v 0 1.5 1
v 2 3 5.0

p 1
p 3
p 2
p 1
v 0.5 2 3
v 0 1.5 1
v 2 3 5.0

l 1 2
l 2 3

I'm mostly just striving for feature parity with Assimp so I can ditch it for OBJ processing, and this is the last missing bit. I don't need ufbx to support the advanced things like curves, just basic lines and points.

Thank you! :)

Wrong orientation

Hi,

I'm trying to use your awesome library, but I run into a slight problem. It looks like the axis are swapped. I've examined your example viewer's code (which works correctly), but couldn't pinpoint what I'm missing.

Currently my code looks like this:

    ufbx_load_opts opts = { 0 };
    ufbx_error error = { 0 };
    ufbx_scene *scene = NULL;

    opts.load_external_files = true;
    opts.allow_null_material = true;
    opts.evaluate_skinning = true;
    opts.target_axes.right = UFBX_COORDINATE_AXIS_POSITIVE_X;
    opts.target_axes.up = UFBX_COORDINATE_AXIS_POSITIVE_Y;
    opts.target_axes.front = UFBX_COORDINATE_AXIS_POSITIVE_Z;
    scene = ufbx_load_memory((const void*)data, size, &opts, &error);

Just for curiousity, I've tried this

-    opts.target_axes.up = UFBX_COORDINATE_AXIS_POSITIVE_Y;
+    opts.target_axes.up = UFBX_COORDINATE_AXIS_NEGATIVE_Y;

but nothing changed, the loaded model is still upside-down. So I'm guessing I might miss a flag telling ufbx to actually apply target_axis conversion maybe? FYI, I don't use the data as-is, I call ufbx_triangulate_face if that matters (but so does your viewer.c, so I don't think that's the reason).

What am I missing? My full code is here if you want to take a look. I'm working on a model converter, which currently uses assimp, but that library is very buggy so I'd like to replace that with ufbx.

Thanks,
bzt

how to pre-calculate the model's tangent

In the structure, there has the varible called "vertex_tangent". I use it without consideration, but unfortunate it didn't exist. I wonder how to open the option to calculate the tangent?

Idea: Detect ASCII from leading character

First check if the file has the binary FBX magic. If not don't just assume it's binary since it would report an confusing parse error. If the first character is [A-Za-z0-9 \t\r\n;] it's probably ASCII.

Stream load function

Should probably add a function like ufbx_load_stream(ufbx_read_fn), we have ufbx_read_fn exposed and all the functionality there so it should be trivial to implement.

Improve triangulation

The goal is to match the triangulation in the FBX SDK, see test/test_triangulate.h for current status. N-gon triangulation currently just assumes the polygons are planar and convex (ie. doesn't do anything reasonable...)

Mapping UV's to vertices

I'm pretty sure this is all coming down to be not understanding the data / api well enough. Any help would be appreciated.

I am using OpenGL and using a Element Array Buffer to keep track of indices for the vertices.

My test cube has 8 vertices, 36 indices, 12 triangles, and 12 faces. The UV data has 36 indices. This is how I am currently pulling the data:

    mesh.uvs = a_alloc(mesh.normals_size,v2);
    for(s32 i = 0; i < uf_mesh->num_indices; ++i) {
        v2 uv = {};
        uv.x = uf_mesh->vertex_uv.data[uf_mesh->vertex_uv.indices[i]].x;
        uv.y = uf_mesh->vertex_uv.data[uf_mesh->vertex_uv.indices[i]].y;
        memcpy(&mesh.uvs[i], &uv, sizeof(v2));
    }

This data gets bound to a glsl location layout(location = 1) in vec2 aTexCoords;

Bound:

    glGenBuffers(1, &obj.uv);
    glBindBuffer(GL_ARRAY_BUFFER, obj.uv);
    glBufferData(GL_ARRAY_BUFFER, mesh.uvs_size, &mesh.uvs[0], GL_STATIC_DRAW);

Called:

      glEnableVertexAttribArray(1);
      glBindBuffer(GL_ARRAY_BUFFER, o.uv);
      glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);

Result:

image

Udim support? (niche)

Do we want udim support?

The algorithm is here to bake udim is at https://github.com/sinbad/FbxUdimUnpack

Tool for unpacking a model that uses UDIMs into a form more suitable for real-time use (separate materials per texture tile) - GitHub - sinbad/FbxUdimUnpack: Tool for unpacking a model that uses UD...
GitHub

Any polygon UVs which reference UDIM tiles trigger the splitting of the material "Foo" into "Foo_U1001", "Foo_U1002", "Foo_U1010" and so on based on the UDIM tile. That poly is then changed to reference that split material and the UVs adjusted back to the 0-1 range.

Idea: Error recovery

Due to the well defined node structure in FBX files it should be possible to recover from errors. There needs to be some way to report partial success.

Semantic fuzzing

Revive the FBX generation support under test2/fbx_write.h but instead of generating dummy files parse real FBX files into trees and mess with them in some way.

  • Add/remove/duplicate/reorder nodes
  • Add/remove/duplicate/reorder node values
  • Truncate/extend arrays
  • Convert array types

Should call ufbxt_check_scene() if the loads succeed.

Example looks convoluted, is there a simpler example?

Im using this with opengl glut.h and was wondering whether I could just extend the quick start lines in the readme.md to also using glut to render the image? Getting a blank screen on this.

#include <ufbx.h>
#include<iostream>
#include <cstdio>

using namespace std;

// Initialize uFBX and load the FBX file
ufbx_load_opts opts = { 0 }; // Optional, pass NULL for defaults
ufbx_error error; // Optional, pass NULL if you don't care about errors
ufbx_scene *scene = ufbx_load_file("thing.FBX", &opts, &error);

// Display callback function
void display() {
    // Clear the buffer
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Set the view matrix
  glLoadIdentity();
  gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);

  // Draw the model
  for (size_t i = 0; i < scene->nodes.count; i++) {
    ufbx_node *node = scene->nodes.data[i];
    if (node->is_root || !node->mesh) continue;

    ufbx_mesh *mesh = node->mesh;
    // cout << "Hello";  // prints Hello

    // Enable vertex arrays
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);

    // Bind vertex and normal data
    // glVertexPointer(3, GL_FLOAT, 0, mesh->vertices.data);
    // glNormalPointer(GL_FLOAT, 0, mesh->fa.indices.data);

    // Draw the mesh
    glDrawElements(GL_TRIANGLES, mesh->faces.count * 3, GL_UNSIGNED_INT, mesh->faces.data);

    // Disable vertex arrays
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
  }

  // Swap buffers
  glutSwapBuffers();  
}

int main(int argc, char **argv) {

  try {

    // Initialize GLUT and create a window
  glutInit(&argc, argv);
  glutInitWindowSize(640, 480);
  glutCreateWindow("uFBX Test");

  if (!scene) {
      fprintf(stderr, "Failed to load: %s\n", error.description.data);
      exit(1);
  }

  // Set up OpenGL state and projection matrix
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45, 640.0 / 480.0, 0.1, 100);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // Set up lighting
  GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
  GLfloat light_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
  GLfloat light_diffuse[] = { 0.8, 0.8, 0.8, 1.0 };
  // GLfloat light_specular[] = { 1
  // Set up materials
  GLfloat material_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
  GLfloat material_diffuse[] = { 0.8, 0.8, 0.8, 1.0 };
  GLfloat material_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat material_shininess[] = { 50.0 };
  glMaterialfv(GL_FRONT, GL_AMBIENT, material_ambient);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, material_diffuse);
  glMaterialfv(GL_FRONT, GL_SPECULAR, material_specular);
  glMaterialfv(GL_FRONT, GL_SHININESS, material_shininess);

  // Set up the display callback
  glutDisplayFunc(display);

  // Start the main loop
  glutMainLoop();
    
  }
   catch (runtime_error &e) {
    cout << "Error: " << e.what() << endl;
  }
  

  return 0;
}

Load is crashing

Hi,

ufbnx_loadfile segfaults on file load. The error occurs on the allocation of a map pointer?
image

The loader was loading this file.
SK_EliteTrooper.zip

Thanks.

GCC compiler warnings with lto activated

 /home/runner/work/vengi/vengi/src/modules/voxelformat/external/ufbx.h:4204:35: warning: type of ‘ufbx_axes_right_handed_y_up’ does not match original declaration [-Wlto-type-mismatch]
 4204 | extern const ufbx_coordinate_axes ufbx_axes_right_handed_y_up;
      |                                   ^
/home/runner/work/vengi/vengi/src/modules/voxelformat/external/ufbx.c:25036:28: note: ‘ufbx_axes_right_handed_y_up’ was previously declared here
25036 | const ufbx_coordinate_axes ufbx_axes_right_handed_y_up = {
      |                            ^

a mocap.market data can't load.

I always use ufbx conveniently. Thank you!
When I load an fbx file downloaded from mocap.market,
it fails in ufbxi_read_mesh() in the branch if(n->name == ufbxi_LayerElementSmoothing),
where there is ufbxi_check(ufbxi_read_truncated_array(uc, &mesh->edge_smoothing.data, &mesh->edge_smoothing.count, n, ufbxi_Smoothing, ‘b’, mesh->num_edges));
and the process returns. As far as I can see,
it is looking for “Smoothing” in n(node) passed as an argument by ufbx_find_child,
but the actual data contains “Version”,“Name”,“MappingInformationType”,“ReferenceInformationType”,
and Smoothing is not found, so the process seems to end.
Do you know any solutions or ways to investigate this?

Provide access to preRotation and postRotation components

I need to do my own animation evaluation, and because the animations in the FBX tend to animate the euler angles directly, I need access to the pre- and post- rotation components of the transform, otherwise things come out all crooked-looking. :)

Right now I'm just using the internal interface, with ufbxi_find_vec3, like ufbxi_get_transform does, but still.

Also, I'm lucky that my pivots are all zero, but now that I think about it I probably need access to all that stuff too. :(

Support textures

Support Texture and Video FBX nodes, should probably decode base64 content as well if it exists (! this can be probably done in-place for ASCII).

OBJ metallic/roughness material texture references shown only if a factor is present as well

I was creating some minimal test files by hand and came across the following -- if an OBJ *.mtl file has this content, the imported material doesn't contain any texture references:

newmtl Material
map_Pm a.png
map_Pr b.png

While, if I do the following, it does. It does include the textures also if I set metalness to 0.0:

newmtl Material
Pm 1.0
map_Pm a.png
map_Pr b.png

For e.g. Kd this works well, map_Kd is recognized even without Kd being present.

Could it be perhaps related to #50? I.e., the code should be checking for the map_* values as well? Or, probably best to extend the check to all PBR properties listed at http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr.

Thank you! (And sorry for neglecting PR reviews these days, I'll get to it as soon as I can.)

Add blend shape support

Add support for blend shapes/targets aka vertex position offsets that can be combined with varying potentially keyframed weights.

FBX version 6100 is really straightforward with these:

Shape: "TopH" {
    Indexes: 2,3,4,5
    Vertices: 0.316956400871277,0,0,-0.316956371068954,0,0,0.316956400871277,0,0,-0.316956400871277,0,0
    Normals: 0,0,0,0,0,0,0,0,0,0,0,0
}
...
Channel: "TopH" {
    Default: 100
    KeyVer: 4005
    KeyCount: 3
    Key: 1924423250,100,U,s,0,0,n,46186158000,0,U,s,0,0,n,115465395000,100,U,s,0,0,n
    Color: 1,1,1
}

Unfortunately 7500 makes them a bit more complicted with the generic deformer system, but they should be reasonably close to skeletal deformation.

Geometry: 2111026653664, "Geometry::TopH", "Shape" {
    Properties70:  {
        P: "LegacyStyle", "bool", "", "",0
    }
    Version: 101
    Indexes: *4 {
        a: 2,3,4,5
    } 
    Vertices: *12 {
        a: 0.316956400871277,0,0,-0.316956371068954,0,0,0.316956400871277,0,0,-0.316956400871277,0,0
    } 
}
...
Deformer: 2111027942368, "SubDeformer::Squish.TopH", "BlendShapeChannel" {
    Version: 100
    Properties70:  {
        P: "DeformPercent", "Number", "", "A+",100
    }
    DeformPercent: 100
    FullWeights: *1 {
        a: 100
    } 
}

Vertices query. Some vertices on the X-axis are zeroed out randomly.

Hi

Unity renders my model correctly. The import from the library renders a very anaemic version of the model. How do I triage this? Attached are files, code and screen captures for your reference.

image

The model on the left has been processed through blender, the model on the right is the original file imported through the api.
image
loader_log_ufbx.txt
SK_EliteTrooper.zip

bool LoaderUfbx::ParseMesh(ufbx_mesh *fbx_mesh, Mesh &mesh) {
    mesh.v_size = (uint32_t)fbx_mesh->num_vertices;
    log->Debug("LoaderUfbx", "v size: " + std::to_string(mesh.v_size));
    for (uint32_t ii = 0; ii < mesh.v_size; ii += 1) {
        Vertex vertex;
        vertex.xyz        = Convert(fbx_mesh->vertex_position.data[ii]);
        if(fbx_mesh->vertex_color.data != nullptr){
            vertex.rgba       = Convert(fbx_mesh->vertex_color.data[ii]);
        }
        vertex.uv         = Convert(fbx_mesh->vertex_uv.data[ii]);
        vertex.normals    = Convert(fbx_mesh->vertex_normal.data[ii]);
        if(fbx_mesh->vertex_tangent.data != nullptr){
            vertex.tangents   = Convert(fbx_mesh->vertex_tangent.data[ii]);
        }
        if(fbx_mesh->vertex_bitangent.data != nullptr){
            vertex.bitangents = Convert(fbx_mesh->vertex_bitangent.data[ii]);
        }

        for (int jj = 0; jj < MAX_NODE_SIZE; jj += 1) {
            vertex.id[jj]     = 0;
            vertex.weight[jj] = 1.0f;
        }
        mesh.vertices.push_back(vertex);
    }

    mesh.i_size = (uint32_t)fbx_mesh->num_indices;
    for (uint32_t jj = 0; jj < mesh.i_size; jj += 1) {
        mesh.indices.push_back(fbx_mesh->vertex_indices[jj]);
    }

    for (uint32_t kk = 0; kk < mesh.v_size; kk += 1) {
        auto fbx_skin  = fbx_mesh->skin_deformers.data[0];
        mesh.next_id   = 0;
        mesh.bone_size = (uint32_t)fbx_skin->clusters.count;
        for (uint32_t ll = 0; ll < mesh.bone_size; ll += 1) {
            auto        index   = BoneIndex{};
            uint32_t    bone_id = 0;
            std::string name =
                fbx_skin->clusters.data[ll]->bone_node->name.data;
            // creating a register of ids
            auto check = mesh.bones.find(name);
            if (check != mesh.bones.end()) {
                bone_id  = mesh.bones[name].id;
                index.id = bone_id;
                index.offset =
                    Convert(fbx_skin->clusters.data[ll]->geometry_to_bone);
                mesh.bones.emplace(name, index);
            } else {
                mesh.next_id += 1;
                bone_id  = mesh.next_id;
                index.id = bone_id;
                index.offset =
                    Convert(fbx_skin->clusters.data[ll]->geometry_to_bone);
                mesh.bones.emplace(name, index);
            }

            if (bone_id > 0) {
                auto fbx_weight_size = fbx_skin->vertices.data[kk].num_weights;
                for (uint32_t mm = 0; mm < fbx_weight_size; mm += 1) {
                    if (mm < 4) {
                        auto weight    = fbx_skin->weights.data[mm];
                        auto fbx_index = weight.cluster_index;
                        mesh.vertices[fbx_index].id[mm] = bone_id;
                        mesh.vertices[fbx_index].weight[mm] =
                            (float)weight.weight;
                    }
                }
            }
        }
    }

    for (uint32_t aa = 0; aa < mesh.v_size; aa += 1) {
        NormaliseWeights(mesh.vertices[aa].weight);
    }
    return true;
}```

Problems with 3dsmax FBX

I'm trying to read the new Sponza scene from Intel:
https://www.intel.com/content/www/us/en/newsroom/opinion/intel-graphics-step-up-research.html

The issue I'm encountering is that the normal textures in the FBX are kind of weird. There is a link to a texture node that just defines some 3dsmax parameters

	Texture: 1690443457600, "Texture::stone_trims_01_Normal", "" {
		Type: "TextureVideoClip"
		Version: 202
		TextureName: "Texture::stone_trims_01_Normal"
		Properties70:  {
			P: "3dsMax", "Compound", "", ""
			P: "3dsMax|ClassIDa", "int", "Integer", "",2121471519
			P: "3dsMax|ClassIDb", "int", "Integer", "",-1235266194
			P: "3dsMax|SuperClassID", "int", "Integer", "",3088
			P: "3dsMax|ai_bump2d Parameters/Connections", "Compound", "", ""
			P: "3dsMax|ai_bump2d Parameters/Connections|bump_map", "Float", "", "A",0
			P: "3dsMax|ai_bump2d Parameters/Connections|bump_map.connected", "Bool", "", "A",1
			P: "3dsMax|ai_bump2d Parameters/Connections|bump_map.shader", "Reference", "", "A"
			P: "3dsMax|ai_bump2d Parameters/Connections|bump_height", "Float", "", "A",1
			P: "3dsMax|ai_bump2d Parameters/Connections|bump_height.connected", "Bool", "", "A",1
			P: "3dsMax|ai_bump2d Parameters/Connections|bump_height.shader", "Reference", "", "A"
			P: "3dsMax|ai_bump2d Parameters/Connections|normal", "Vector", "", "A",0,0,0
			P: "3dsMax|ai_bump2d Parameters/Connections|normal.connected", "Bool", "", "A",1
			P: "3dsMax|ai_bump2d Parameters/Connections|normal.shader", "Reference", "", "A"
			P: "3dsMax|ai_bump2d Results", "Compound", "", ""
		}
		Media: ""
		FileName: ""
		RelativeFilename: ""
		ModelUVTranslation: 0,0
		ModelUVScaling: 1,1
		Texture_Alpha_Source: "None"
		Cropping: 0,0,0,0
	}

This is connected to another texture node like this:

	;Texture::stone_trims_01_Normal, Texture::stone_trims_01_Normal
	C: "OP",1690443452320,1690443457600, "3dsMax|ai_bump2d Parameters/Connections|bump_map.shader"

which is the regular one:

	Texture: 1690443452320, "Texture::stone_trims_01_Normal", "" {
		Type: "TextureVideoClip"
		Version: 202
		TextureName: "Texture::stone_trims_01_Normal"
		Properties70:  {
			P: "UVSet", "KString", "", "", "UVChannel_1"
			P: "UseMaterial", "bool", "", "",1
		}
		Media: "Video::stone_trims_01_Normal"
		FileName: "textures/stone_trims_01_Normal.png"
		RelativeFilename: "textures/stone_trims_01_Normal.png"
		ModelUVTranslation: 0,0
		ModelUVScaling: 1,1
		Texture_Alpha_Source: "None"
		Cropping: 0,0,0,0
	}

Not quite sure what ufbx should do? Traverse the entire connections when fetching textures?

With empty file names like for the weird texture node it might also make sense to set UFBX_TEXTURE_PROCEDURAL instead of UFBX_TEXTURE_FILE

How to use ufbx_blend_shape?

Hello,
I read offset point from ufbx_blend_shape. I think ufbx_blend_shape::offset_vertices is the index into the ufbx_mesh::vertex_position array. And ufbx_blend_shape::position_offsets ufbx_blend_shape::normal_offsets are offset of the point. But When adding ufbx_mesh::vertex_position and ufbx_blend_shape::position_offsets, it did not get the correct result.

What did i do wrong?

ufbx_mesh_material::face_indices::count wrong value

The comments in the header say that:

// Indices to `ufbx_mesh.faces[]` that use this material.
// Always contains `num_faces` elements.
ufbx_uint32_list face_indices; 

So I expect face_indices::count to be equal to num_faces. But in the inspector I see the following:

image

And because of that face_indices[i] triggers an assert ufbx_assert(index < count);, but face_indices.data[i] is fine.

Idea: User callback structs for API

Bundle callback functions and their user pointers into structs.

typedef struct ufbx_alloc_fn_s {
	void *(*func)(void *user, size_t size);
	void *user;
} ufbx_alloc_fn;

typedef struct ufbx_free_fn_s {
	void (*func)(void *user, void *ptr);
	void *user;
} ufbx_free_fn;

viewer example assertion failed on windows

OS: Windows 10 x64
GPU: Nvidia GTX 1050 2GB
ufbx version: commit 30f035bbe5ecd90860392825eb586b5110400aa8 (HEAD -> master, tag: v0.5.0, origin/master, origin/HEAD)
Compiler: gcc version 13.1.0 (Rev6, Built by MSYS2 project)
Compile command: gcc ../../ufbx.c external.c viewer.c -ld3d11 -lgdi32
Run command: a.exe test.fbx
The test.fbx is attached here, which can load in https://3dviewer.net/
test.zip
Error:

Assertion failed: ((HRESULT)(hr) >= 0) && _sapp.d3d11.swap_chain && _sapp.d3d11.device && _sapp.d3d11.device_context, file external/sokol_app.h, line 5488

error: duplicated 'if' condition after update

modules/fbx/thirdparty/ufbx/ufbx.c: In function 'ufbxi_read_legacy_root':
Error: modules/fbx/thirdparty/ufbx/ufbx.c:13739:25: error: duplicated 'if' condition [-Werror=duplicated-cond]
13739 |   } else if (node->name == ufbxi_Takes) {
      |              ~~~~~~~~~~~^~~~~~~~~~~~~~
modules/fbx/thirdparty/ufbx/ufbx.c:13737:25: note: previously used here
13737 |   } else if (node->name == ufbxi_Takes) {
      |              ~~~~~~~~~~~^~~~~~~~~~~~~~

Remove bad faces

This is a feature that used to be supported, but broken. We probably want to filter out faces with less than three vertices. In this case we need to re-order face_smoothing and face_material as well!

The bad faces could be stored in a separate array like ufbx_face_list bad_faces.

Broken texture coordinates on Intel's Sponza scene

Hi! Thank you for developing this library!

I tried to use it for parsing Intel's Sponza. It looks like the texture coordinates on some parts of the scene are broken.

I modified ufbx viewer to show texture coordinates, and I see this:
ufbx-viewer-sponza

Notice the pink-colored roof tile, the bottom-rightmost one. It stands out from the others.
There is also a purple one at the top-right, broken similarly.
There are more things that look wrong, but the roof tiles are probably easiest to work with.

Here is the same thing but viewed from Blender:
blender-sponza-uv

I selected the checkerboard texture, and it looks correct, very much like all the other tiles. I can also apply the texture and see it there. Note that I used Autodesk's FBX Converter to convert Intel's ASCII FBX into binary, so that Blender (2.81) could load it.

Here are the exact changes I had to make in ufbx for loading and visualizing the texture coordinates:

diff --git a/examples/viewer/external/sokol_gfx.h b/examples/viewer/external/sokol_gfx.h
index 7005d7f..10e7f06 100644
--- a/examples/viewer/external/sokol_gfx.h
+++ b/examples/viewer/external/sokol_gfx.h
@@ -3107,7 +3107,7 @@ enum {
     _SG_SLOT_SHIFT = 16,
     _SG_SLOT_MASK = (1<<_SG_SLOT_SHIFT)-1,
     _SG_MAX_POOL_SIZE = (1<<_SG_SLOT_SHIFT),
-    _SG_DEFAULT_BUFFER_POOL_SIZE = 128,
+    _SG_DEFAULT_BUFFER_POOL_SIZE = 2048,
     _SG_DEFAULT_IMAGE_POOL_SIZE = 128,
     _SG_DEFAULT_SHADER_POOL_SIZE = 32,
     _SG_DEFAULT_PIPELINE_POOL_SIZE = 64,
diff --git a/examples/viewer/shaders/mesh.glsl b/examples/viewer/shaders/mesh.glsl
index fdca3c0..5032637 100644
--- a/examples/viewer/shaders/mesh.glsl
+++ b/examples/viewer/shaders/mesh.glsl
@@ -112,7 +112,7 @@ void main()
     l += v_uv.x * 0.0001;
 
     l = l * 0.5 + 0.5;
-    o_color = vec4(l, l, l, 1.0);
+    o_color = vec4(fract(v_uv), l, 1.0);
 }
 @end

Thank you for your time reading this. Let me know if I can provide more info.

Shadowing of variables

In file included from modules/fbx/gltf_document.cpp:74:
modules/fbx/thirdparty/ufbx/ufbx.h: In constructor 'ufbx_ref<T>::ufbx_ref(T*)':
Error: modules/fbx/thirdparty/ufbx/ufbx.h:4654:37: error: declaration of 'ptr' shadows a member of 'ufbx_ref<T>' [-Werror=shadow]
 4654 |  explicit ufbx_ref(T *ptr) noexcept : ptr(ptr) { }
      |                                     ^
modules/fbx/thirdparty/ufbx/ufbx.h:4690:5: note: shadowed declaration is here
 4690 |  T *ptr;
      |     ^~~

Thanks to your tweets I was trying out ufbx.

image

image

Implement stable sort

There's a few places where we sort UV sets or blend keyframes which should be done using a stable sort for correctness..

Skinning Trouble

Thank you so much for this library; it's exactly what I was looking for when it comes to an FBX reading library!

I'm having a bit of trouble with evaluating skins. In particular, I have one model that this library just doesn't seem to make sense of.

I'm using the "overhaul" version of the code, from commit 8237c4f.

When I load the model without skinning the geometry comes out just fine...

working

...but when I tell ufbx to evaluate skinning and start using skinned_position and skinned_normal everything goes wrong...

busted

FWIW Unity and open3mod both render this FBX just fine, but Godot has the same trouble as I do.

Any advice? I've attached the FBX and Textures.

repro.zip

"metallic" should be "metalness"

{ UFBX_MATERIAL_PBR_METALLIC, ufbxi_string_literal("metalness") },

The term used in all PBR literature is "metalness" and I think this should be reflected in the ufbx API as well. Very confusing otherwise.

Crash while computing AABB, and maybe two other issues.

Hey there Samuli,

  1. I found a crash during the AABB computation for the kgirls01 model which can be downloaded from here:
  1. I'm not sure if blendshapes are correctly working with that fbx either. I think the model uses blendshapes to animate both eyes+mouth. To my understanding, eyes seem to be properly animated but mouth likely not?
  2. Finally, when using the (lovely) supplied viewer, there is a 1-frame pose reset when the animation loops from start again.

What an awesome project. Keep up the good work!

SIGBUS (signal SIGBUS: illegal alignment)

Hi, friend,

I encountered a crash issue when I try to use your ufbx to parse fbx in my personal project.

On the PC platform either 32bit or 64bit are running swimmingly.

however on the android platform-armv7a, crash.

by armv8a-64bit is ok.

here is the concrete code snippet:

vals[i].i = (int64_t)(vals[i].f = ufbxi_read_f64(data + 1));

Idea: Error stacks

Pushing nodes/names while parsing forms a stack that gives context to errors.

How to read bone information and how to combine with vertex point

Hello author!
I read vertex point information from ufbx_mesh::vertex_position.
How do I read bone index and bone weight? And How to map How to map bone weight to vertex points?

The following is my read vertex point method:

`
// Get mesh bones
std::vector bones;

if (node->mesh->skin_deformers.count > 0)
{
    ufbx_skin_deformer *skin = node->mesh->skin_deformers.data[0];
    for (size_t i = 0; i < skin->clusters.count; i++)
    {
        ufbx_skin_cluster *cluster = skin->clusters.data[i];

        BoneInfo bone_info;
        uint32_t bone_index = cluster->bone_node->typed_id;
        bone_info.name = std::string(cluster->bone_node->name.data, cluster->bone_node->name.length);

        ufbx_matrix geometry_to_bone = cluster->geometry_to_bone;
        glm::mat4 mesh_point_to_bone_space = GetGlmMatrix(geometry_to_bone);
        bones.push_back(bone_info);
    }
}
mesh_data.bones = std::move(bones);

// 获取网格顶点
std::vector<VertexData> vertex_data_array;

ufbx_skin_deformer *skin = node->mesh->skin_deformers.data[0];

uint32_t vertex_point_count = node->mesh->num_indices;
node->mesh->num_vertices;
for (uint32_t i = 0; i < vertex_point_count; i++)
{
    VertexData vertex_data;

    ufbx_vec3 tmp_point = ufbx_get_vertex_vec3(&node->mesh->vertex_position, i);
    vertex_data.vertex_pos.x = tmp_point.x;
    vertex_data.vertex_pos.y = tmp_point.y;
    vertex_data.vertex_pos.z = tmp_point.z;
    
    ufbx_vec3 tmp_normal = ufbx_get_vertex_vec3(&node->mesh->vertex_normal, i);
    vertex_data.normal.x = tmp_normal.x;
    vertex_data.normal.y = tmp_normal.y;
    vertex_data.normal.z = tmp_normal.z;
    
    ufbx_vec2 tmp_uv_pos = ufbx_get_vertex_vec2(&node->mesh->vertex_uv, i);
    vertex_data.uv_pos.x = tmp_uv_pos.x;
    vertex_data.uv_pos.y = tmp_uv_pos.y;
    // fmt::print("{} vertex point ({}, {}, {}) \n", i, tmp_point.x,  tmp_point.y,  tmp_point.z);
}

// 获取索引顶点
uint32_t faces_count = node->mesh->num_faces;
fmt::print("face count: {} \n", faces_count);

uint32_t index_reciever[128];
for (uint32_t i = 0; i < faces_count; i++)
{
    ufbx_face& face_index = node->mesh->faces[i];
    if (ufbx_get_triangulate_face_num_indices(face_index) > 0)
    {
        uint32_t triangulate_count = ufbx_triangulate_face(index_reciever, 128, node->mesh, face_index);
        for (uint32_t j = 0; j < triangulate_count; j++)
        {
            for (uint32_t k = 0; k < 3; k++) {
                uint32_t index = index_reciever[j * 3 + k];
                mesh_data.indexs.push_back(index);
            }
        }
    }
}

// this code aime to Get vertex point's bone weight and index, But getting the number of bones is far less than reading the number of vertices
int test_i = 0;
for (size_t vi = 0; vi < node->mesh->num_vertices; vi++) {
    size_t num_weights = 0;
    float total_weight = 0.0f;
    float weights[4] = { 0.0f };
    uint8_t clusters[4] = { 0 };

    ufbx_skin_vertex vertex_weights = skin->vertices.data[vi];
    for (size_t wi = 0; wi < vertex_weights.num_weights; wi++) {
        if (num_weights >= 4) 
            break;
        ufbx_skin_weight weight = skin->weights.data[vertex_weights.weight_begin + wi];

        if (weight.cluster_index < 100) {
            total_weight += (float)weight.weight;
            clusters[num_weights] = (uint8_t)weight.cluster_index;
            weights[num_weights] = (float)weight.weight;
            num_weights++;
        }

        test_i++;
    }
}

}`

ufbx_assert unused variable warning

In the file ufbx.c there is this snippet of code, starting on the line 18846:

// Build a KD-tree out of the collected reflex vertices.
uint32_t num_skip_indices = (1u << (UFBXI_KD_FAST_DEPTH + 1)) - 1;
uint32_t kd_slow_indices = num_kd_indices > num_skip_indices ? num_kd_indices - num_skip_indices : 0;
ufbx_assert(kd_slow_indices + face.num_indices * 2 <= num_indices);
ufbxi_kd_build(nc, kd_indices, kd_tmp, num_kd_indices, 0, 0, 0);

The problem is that in the Release build ufbx_assert is reduced to nothing, so the variable kd_slow_indices becomes unused, and the compiler generates a warning. And because the project I'm currently working on uses /WX and -Werror compiler flags the warning becomes an error.

I understand that fixing warnings is not a high priority, but here are a couple of ways to deal with disappearing asserts.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.