3
$\begingroup$

I have read variations of this question in the past, but often times those questions had more to do with how to also split up a model, and this variation does not.

I have a set of 4k PBR textures (Albedo, displacement, normal, roughness) for a single map for a rock texture. I want to make 64 individual rock objects. The plan is to have each object get it's own slice of the large texture on a grid on the texture. Each object gets a texture space of 512x512. I will have to make 64 materials for this.

My question is, is it better to slice up all of the images ahead of time, and have the variations each with their own set of images OR is it better to make 64 materials, each one using the exact same single set of 4k images, and use the the texture mapping + coordinate nodes to control the offset. In the second scenario I would have to shrink the UV down to fit 512x512, but that should be doable.

My initial theory is that it would be better to use the mapping nodes for control? But I don't really have any strong data or understanding to back that up.

$\endgroup$
4
  • 3
    $\begingroup$ It really doesn't matter. In a game engine, the ideal would be to use 1 albedo image, 1 material, using UV only (no mapping in the shader), but in Blender, for what you're describing, we're talking about bytes (not even kilobytes!) and nanoseconds saved, if that. The more significant concern is texture filtering at seams, but it doesn't sound like you're caring about seams at all anyways. $\endgroup$
    – Nathan
    Jul 4 at 19:18
  • 3
    $\begingroup$ Just to add, you probably dont even need to make 64 materials. Just one that shifts the uv map of each object :) $\endgroup$ Jul 4 at 20:00
  • $\begingroup$ @JachymMichal That is an interesting idea, I will try to look that up, I am not sure how to make a material that shifts the UV per object instance. $\endgroup$
    – aaron
    Jul 5 at 15:43
  • 1
    $\begingroup$ @aaron if you have multiple textures in a single file, you can use technique like here: blender.stackexchange.com/a/210107/60486 but if you have just one huge texture and want to randomize coordinates on it, the setup is as simple as that: i.imgur.com/XUInu6l.png it uses to its advantage the texture repeat mode, so if your texture is not seamless, you may want something like this: i.imgur.com/7GzOHiX.png which limits the random x to be in range 0..(1-width), and y to be 0..(1-height) $\endgroup$ Jul 5 at 17:16
3
$\begingroup$

I have a feeling Nathan is right that this is irrelevant to the performance, however it could very much be the opposite: I remember when I had a project with a UV sphere displaying a different video on each face. When rendering a test animation, most of the time of the renders was taken by loading the video files on each frame. However, would a single higher resolution movie work better in this case, probably not... However, there might be various optimizations in Blender that make one work better than the other, so...

Let's test it

I created a plane, and added a texture node, with color grid texture in 16K resolution. Subdivided the plane with 7 cuts, twice, so that each face is associated with 4 titled squares. Separated all those faces as individual objects, made some random transformations and a 60 frame animation:

Using Eevee, samples: 1, 500x500 resolution, Image Texture directly connected to shader output, to make it as light as possible to only measure the actual performance of loading the big image or small images.

After making first test with this setup, I reset all UVs, added mapping to the material:

Then I used a script to separate the materials and use the mapping to position each plane on a different coordinate without using UV:

import bpy

coords = ((x/64, y/64) for x in range(64) for y in range(64))

for o, co in zip(bpy.data.objects, coords):
    if not o.name.startswith("Plane"):
        continue
    mat = o.material_slots[0].material.copy()
    loc = mat.node_tree.nodes['Mapping'].inputs['Location'].default_value
    loc.xy = co
    o.material_slots[0].material = mat

Rendered the animation again and it took twice as long now, evidently the 8 seconds needed at first, now was needed just to prepare for the first frame - but once that rendered, the rest went smoothly, because apparently Blender cached that data.

Now in order to divide the image to many images, I used this node setup:

Rendered 64*64=4096 frames from 0 to 4095. The folder with those images weighs 341 MiB while the single file weighs only 41.8 MiB. This alone might be significant.

Reverting the test file back to the situation with a single material, removing the Mapping node, and applying this script:

import bpy

for o, i in zip(bpy.data.objects, range(64*64)):
    if not o.name.startswith("Plane"):
        continue
    mat = o.material_slots[0].material.copy()
    image_node = mat.node_tree.nodes['Image Texture']
    image_node.image = bpy.data.images.load(f"C:/imgs/{i:04d}.png")
    o.material_slots[0].material = mat

I experienced more UI lags then on previous test. But rendering took a second or two less, but I used a faster SSD, so with all this slopiness, I decided to be a little more rigorous and repeated those tests 3 times per each technique, each on the faster SSD with a measurement error of +/- 0.5 s, starting a stopwatch at the same time as pressing CtrlF12 and stopping it as soon as I see Frame: 60 in the render window:

try # 1 material 1 image 4096 materials 1 image 4096 materials 4096 images
1 8.19s 13.91s 15.29s
2 8.15s 13.98s 16.75s
3 8.15s 14.56s 15.50s

So out of the 2 last options the OP considers, the 2nd option (single image file) seems better. UI is more responsive in my experience, it renders faster, it's easier to work with. However, what comes to mind is that perhaps if some of your scenes don't use entire texture, dividing the texture may make it easier to render or to work with your file because thanks to that Blender will load only those parts that are needed.

It's very important though that you consider what Jachym Michal said: not only using a single material was much faster than using many materials, it also allows you to put some kind of offsets (e.g. random offsets), and then, since your texture is not a tiled grid like the testing texture here, this could allow you for more variance, by having overlapping mappings but still different enough to not be perceived as the same rock.

$\endgroup$
3
  • 1
    $\begingroup$ Thanks for testing. In the context of your table, "2nd option seems better" seems to refer to 4096 mats? But the table is showing best rendering from 1 mat. Do have to say, 4096 seems like overkill when compared to 64; I think you'd see much smaller differences at 64. Also wondering about shader compilation time, if 1st frame is responsible for the bulk of the difference-- there's significant overhead to compiling 4096 mats, even if they're all basically the same mat, but hopefully Blender doesn't recompile for every single frame. $\endgroup$
    – Nathan
    Jul 4 at 23:39
  • $\begingroup$ @Nathan let me edit it, what I meant is that the OP only considers either 2nd or 3rd option, out of the two, the single image option is better. But of course a single material option is way better. You're right about seeing smaller differences at 64, this is why I used 4096 - to actually be able to measure the difference. :D Maybe tomorrow I'll try on a longer animation, to see how it scales and what is the flat difference when starting the render. $\endgroup$ Jul 4 at 23:42
  • $\begingroup$ I can definitely see the value in doing things to exaggerate differences. $\endgroup$
    – Nathan
    Jul 4 at 23:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.