(1) Using the same perlin seed (map) as used for terrain
You could treat ranges in the perlin result as different materials.
Thus at a certain threshold, you transition from air / vacuum to matter, say rock; then at another, higher threshold, you might transition to iron, then to gold etc.
The problem here is that as the values build up in a region, you would always have gold surrounded by iron surrounded by ordinary rock... unless you did some post-processing to remove the surrounds (flood fill, not too hard) leaving only the core material.
(2) Using one perlin map for terrain, and one for materials
Wherever there is rock from the terrain (primary) map, there is also a possibility of other materials occurring, provided the intensity at this point in the materials (secondary) map is high enough. In this case you could just say, well, wherever there is a patch of high enough intensity, then we randomise material type. This could be done by calculating the centroid of those cells which were considered to be a special material (in a connected clump, which would need to be determined by flood-fill), and hashing that centroid's xyz combined (by bitshifting) to get some pseudorandom number which would be your index into the materials table, selecting the material for the clump.
Otherwise you could use the same trick as in the first approach: get the local maximum and let that decide what the core material is, then expand the core material into the surrounding areas until we reach the threshold where we're back to plain rock again.
(3) Using one map for terrain and one for each material
Given that each Perlin noise map / seed gives a 1D value at each point, this is going to be the simplest way to have many materials - have a map per material, and one for (non)solidity of terrain. Then when you exceed the threshold on the map, you can generate that material there, provided there is also solid rock there on the terrain map.
Although this looks the simplest, it probably also costs the most, as in order to get the world layout, you have to sample perlin function for as many materials as there are, plus the base terrain, for every cell in the local zone. Floodfills will probably end up being a bit cheaper, provided you can limit the expanse of each clump by setting thresholds high. But hey, maybe you hash the chunk coords to select only 3 desired materials for the chunk, then sample only these maps?
(x) Fast interpolation of a coarsely-sampled map
To reduce the cost of a flood fill, this can be used in conjunction with approaches 1 & 2. Instead of walking the original map (2D for demo purposes):
0 123456
1 xx0000
2 xxx00x
3 0xxxxx
4 0x0x00
5 00000x
6 xx0000
You could walk / flood fill a reduced map:
0 246
2 x0x
4 xx0
6 x00
...though this would leave you having to address values in between, to complete the map.
There is, however, one way in which those values could be filled in quite rapidly: GPUs do interpolation extremely well. You could interpolate a 3D texture, for instance, to generate all values in between these coarser 0,2,4,6,8 coords... indeed they could even be 0,16,32,48,64... and the GPU could rapidly interpolate all those 15 missing values between, in each axis. Whether you would be interested in linear interpolation is another question... you may prefer something like bicubic interpolation, which will still be very fast on GPU, but not as fast.