Many typical source formats already do what you're asking for in data. The format will contain a list of N materials and a list of M meshes with a mapping for each particular mesh to the material it uses. Your converter can convert these directly to your in-engine format.
Other formats make use of external references, which is handy if you have multiple meshes that share materials. The format is again usually the same: the format has a list of N material references and M meshes and maps each mesh to its material.
In engine, you would either use a reference/pointer/handle to each material for each mesh or use an ID system. You load your optimized format which would likely use a reference system as in the previous paragraph. When you load
foo.model you'd get a hierarchy of M meshes each with an identiier of the material resource to load. You load the material if not already loaded and attach the handle/id to the mesh.
For example, you might have the files:
In such a setup, your
gargoyle.mod might have 4 sub meshes using 3 materials, something like:
body => /asset/material/stone.mat
sword => /asset/material/steel.mat
armor => /asset/material/steel.mat
head => /asset/model/gargoyle/face.mat
This maps to a structure like:
materials: [0:stone, 1:steel, 2:face]
meshes: [0:(body, 0), 1:(sword, 1), 2:(armor, 1), 3:(head, 2)]
Your engine basically loads that data, loading any materials referenced that's it's missing. Assuming you've already loaded a number of materials, the in-memory format would be similar to the above but with different IDs (mapping to in-memory structures and not the on-file structure):
materials: [..., 25:stone, ..., 57:steel, ..., 93:face, ...]
models: [..., gargoyle: [0:(body, 25), 1:(sword, 57), 2:(armor, 57), 3:(face, 93)], ...]
A library like Open Asset Importer makes writing your converters much easier given all the formats it supports. You may need to modify it or branch out if your source format extensions (3d authoring plugins) get too advanced, though it does have a degree of flexibility.