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 1 year 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

Codecov Report

Attention: Patch coverage is 16.84783% with 153 lines in your changes missing coverage. Please review.

Project coverage is 62.15%. Comparing base (cb8e494) to head (fd36a88). Report is 11 commits behind head on master.

Files with missing lines Patch % Lines
src/io/obj.jl 16.84% 153 Missing :warning:
Additional details and impacted files
@@             Coverage Diff             @@
##           master      #95       +/-   ##
===========================================
- Coverage   77.08%   62.15%   -14.94%     
===========================================
  Files          11       11               
  Lines         576      753      +177     
===========================================
+ Hits          444      468       +24     
- Misses        132      285      +153     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.


🚨 Try these New Features:

codecov[bot] avatar Jul 14 '24 12:07 codecov[bot]

This adds experimental support for #94. To cleanly support this we need changes in GeometryBasics to attach metadata to meshes that's not per vertex.

ffreyer avatar Jul 14 '24 12:07 ffreyer