MeshIO.jl icon indicating copy to clipboard operation
MeshIO.jl copied to clipboard

Add experimental support for materials in wavefront obj files

Open ffreyer opened this issue 7 months ago • 2 comments

This adds a couple of things:

  • the main load("filename.obj") now also reads "usemtl" commands and treats them like a vertex attribute. I.e. it maps the related material names to indices and generates faces for them so that a mesh can later be split into submeshes based on materials
  • MeshIO.split_mesh(mesh) does the splitting based on material indices. It's not exported as I see it as an experimental feature. It's hard-coded to assume a mesh has normals, uvs and material ids for now. It returns a Dict mapping material ids to sub meshes.
  • MeshIO.load_materials("filename.obj") repeats some of the work of the main load function to provide a material id => material name mapping and also loads the attached mtl file via MeshIO._load_mtl("filename.mtl"). I consider those experimental as well so they're not exported. The output is a nested Dict of the information in the mtl file.

For example you can use this to load https://free3d.com/3d-model/girl-blind-703979.html. (This isn't using all the material settings specified in the mtl file so the shading doesn't match exactly. )

using FileIO, MeshIO, GeometryBasics
path = "D:/data/Julia/"
filepath = joinpath(path, "14-girl-obj/girl OBJ.obj")
mesh = load(filepath)

meshes = MeshIO.split_mesh(mesh)
materials = MeshIO.load_materials(filepath)
# filename incorrect in mtl file...?
materials["FACE"]["diffuse map"]["filename"] = "D:/data/Julia/14-girl-obj/tEXTURE/FACE Base Color apha.png"

using GLMakie

begin
    fig = Figure()
    ax = LScene(fig[1, 1])

    for (id, m) in meshes
        # get material associated with a submesh
        material_name = materials["id to material"][id]
        material = materials[material_name]

        # load texture from filename specified in .mtl file
        texture = if haskey(material, "diffuse map") && haskey(material["diffuse map"], "filename")
            load(material["diffuse map"]["filename"])
        else
            RGBf(0,1,0)
        end

        # texture uses white as transparent for some reason...
        if material_name == "FACE"
            texture = map(texture) do c
                RGBAf(c.r, c.g, c.b, 1 - c.r)
            end
        end

        # render mesh with specified material properties if specified
        mesh!(ax, m, color = texture,
            diffuse = get(material, "diffuse", Vec3f(1)),
            specular = get(material, "specular", Vec3f(0.2)),
            shininess = get(material, "shininess", 32f0),
        )
    end

    fig
end

Screenshot 2024-07-14 133931

ffreyer avatar Jul 14 '24 12:07 ffreyer