I feel like the answer is already somewhere on the forum, but I've searched for some time and haven't found anything...
I'm working with a proprietary engine, my programmer keeps saying that texture draw calls are one of the most damaging things to performance. Well, I'm trying to figure out what the better approach is: Single, unified unwrapped models or multi-sub object materials... not that I think they're mutually exclusive or anything.
I've noticed that most props in UT3 and UT2004 usually have one texture/material but I've also noticed props with 5 or more. So how are the other Polycounters out there make this decision?
I'm trying to figure out what is more computationally expensive - calling 5 unique unwrapped textures or re-using 5 tileable textures on multiple props. (5 was just a random number...) Or does it matter? If so, how much?
Of course my programmer doesn't really have an answer for me, this is more of the art side of things anyway.
Replies
The GPU works like a factory assembly line. You tweak all the settings, then you send it a large batch of vertices to work on. The bigger and fewer the batches, the better.
If you want to upload a different set of textures into the texture units, or change vertex arrays, or change any other setting (alpha blender toggle, etc...) then you have to stop the whole factory, tweak those settings, and start it up again.
This happens really fast, but it's relatively slow. It's the slowest thing to do on a GPU, so a good rendering engine will try to optimize around having the fewest state changes as possible.
There is no difference between tiled and un-tiled textures other than the UV values assigned to the vertices. The GPU doesn't care, it just reads the numbers and samples from the proper spot in the texture data. Only thing that changes is the wrap mode state that tells it how to handle coords out of the 0-1 bounds.
Changing shaders is also VERY expensive.
It has to do a lot with architecture of the engine and game, like how textures are reused and which ones are dynamic like swap able parts of a texture.
And yes in the end you want to reduce drawing calls and every different texture in memory increases the changes of another drawing call even though in many cases the batching might do a good job.
You could also unwrap and texture several objects independently and at some point stitch them together using photoshop and for example the UV transform modifier in max. Or the transform canvas tool in TexTools which uses the UV transform modifier as a base to shift and resize the UV area.
Screenshot or it didn't happen :P
...(Still laughing 30 min after reading this)...
Good ideas, renderhjs, I'm facepalming over not thinking of the UVXform trick you mentioned. I already tried the "multiple parts in one texture" and that worked OK, but I wasn't sure if that was a "legit" method.
I need to get with the program on TexTools, everyone here's always talking it up. I'll admit I tried it and didn't immediately see what the fuss was about, but every other post mentions using it to shift/offset UV's so there must be something to it.
Let's get a little more specific here- the current object in question is a simple primitive thatched-roof hut, it has wood plank walls, wooden beams/trim, a stone foundation, and a brick chimney. That's a minimum of 5 textures. I've done a version with multiple tileable textures packed into one 512 square, single UV unwrap, and I've also done a version with 256 square multi-sub individual tileable textures. They look pretty much the same, though the detail on the muti-sub is a little better.
This has been some great feedback so far, you've all given me some ideas but of course I'll keep reading if there's any more good tips!
It has been a habit ever since to first consider what can all be merged, and usually means not just 1 or 2 objects but several objects. My point here is that once you experienced limits (crash, slow downs, failures,..) you will appreciate thinking about it more - because being a CG artist (games or film) is always about working with technology in mind.
The coder will love you if you can just put all all needed textures together into 1 sheet (if that works with the shaders, and level loading,...) - because at some point it might even get the drawing calls down to just 1! and that means super nice frame rates and space for processing intense effects or shaders.
Like Saiainoshi metioned, there are many things that will affect performance. Texture fetches are just one piece of the puzzle.
What you seem to be asking about is the number of draw calls per object. Whether those draw calls use unique textures or the same texture is another matter. In DX9 (maybe DX10 as well?), a single draw call can access up to 16 textures.
When you break an object into 5 materials, you are essentially turning it into 5 different objects. If that object is on screen 10 times, then you have 50 draw calls.
For example, if you texture a motorcycle on one sheet, you might think it is one draw call. Then you create different shaders for rubber, chrome, paint, leather and glass, each with different values for specular, reflection and opacity. That motorcycle is now 5 separate draw calls that use the same texture.
I have to disagree with renderhjs on the single texture/single draw call approach. So let's assume that you put everything in the scene on one big sheet and one material and collapse it into a single draw call. That should be ultra fast right? Not necessarily. Now you have a new problem. If even one vertex of an object is in view then you send the entire object to the renderer. Unless your engine has a snazzy way to cull individual polygons that are off screen before they go to the vertex shader (and it probably doesn't), you will waste performance by prepping off-screen geometry that never actually gets rendered. Having your entire scene as one asset could also be a workflow and bug fixing nightmare for the artist.
In short, you have to balance memory, performance, quality and usability. Unique texture unwraps are artist friendly and tend to improve performance when used properly. They also cost more memory and can sacrifice pixel density. Reusable tiling textures can save memory and improve pixel density but will cost more draw calls.
Discuss it with your programmer and see where the bottleneck occurs. Test the results with the game running. It would be even better if you can get your hands on the profiling tools and see the results for yourself... 'cause the art might not be the problem.
There's some good info in this thread, but this is particularly relevant.
How important is your hut? How often will it be used? How many will you ever see on-screen at once? Does your renderer merge draw-calls for indentical objects?
These are all really important questions. If your hut is on screen once as a landmark and is the focus of the scene, then you can probrably go with multiple draw-calls. If your hut is going to be used in a town where there will also be 50 other huts, and your renderer doesn't merge draw calls, then it's likely going to have an impact on performance that could have been avoided by merging it all onto one sheet.
As a general rule of thumb, if I know I'm making something that will be used A LOT, then I make a point to use as few draw calls as possible. And If I'm making something that won't be used very much, then I'll give myself some breathing room.
When it comes down to the end and you are analyzing overall performance of environment draw-calls, the highest frequency objects are the ones that will potentially gain you the biggest savings, so it's always where you turn first. Build those right the first time, and then optomize further if you need to.
Here's another option I haven't heard mentioned;
You can always have your model use multiple draw-calls, but LOD to a version that is baked to a single texture and lower resolution. This could mean that only the few near you are multiple draw calls, and the others all use only one draw call each. This is generally viable, but is also trading performance for memory, which isn't always feasible (especially on consoles). It's also good practice on large-scale environments because once these are in the middle-ground and not the foreground, they'll drop their draw-calls pretty quickly.
Hope this helps.
We have several LOD and optimization functions, but while I can deal with making mip maps, mesh-based LOD gives me chills just thinking of it - unless it's handled on the programming side!
Without going into too many specifics, we use a DX9 shader-based material system that supports use of diffuse, normal, specular, envrion/cube, and a few other things.
We're really moving out of a testing and development phase and into a production phase, I'm trying to soak up as much as possible here so I can get my workflow down to avoid going back and re-making things.
This has all been helpful, I can never get over how much just about everyone here really knows their shite, it's great.
Anyhow, in general the less material changes the better. To figure out what works and what doesn't, you'll want to make some test scenes and do some profiling in your engine. If you haven't seen these articles, they might help you get your head around some of the art tech issues.
http://www.ericchadwick.com/examples/provost/byf1.html
Also might help to check out the ExtremeTech 3D Pipeline Tutorial.
Like cman2k pointed out, you could try making LOD models. Yes, you will have to spend memory to get that performance. If your programmer thinks LOD will help then give it a shot. Don't be afraid! It's not as hard as you think. It's a good skill to have and will teach you a lot about your engine. The first few LOD models you make will probably be awful and pop really bad when they swap, but THAT'S NORMAL until you find the sweet spot. Keep working with it and you'll get it right.
Some bad assumptions in here.
Texture fetches = reading data from the textures that are loaded for use.
Texture binding = loading the texture from one part of the GPU's ram into texture units. Very slow.
You are also falsely correcting renderhjs about the one texture sheet. Every scene is going to get split up into multiple chunks for frustum culling, and put into different vertex arrays, which are much quicker to swap out then textures, material settings, or especially shaders. The speed cost of switching vertex array is considered negligible these days, unlike in the past, where people often threw EVERYTHING in one big vertex array.
Every time a frame is rendered, the scene structure will throw out a list of everything to be drawn, and then that list will be sorted and organized down to the least possible draw calls.
Sometimes, the entire scene is pre-drawn is one material just to find out how many pixels every object ended up taking up(occlusion querying), so you can know if a certain shader in the scene can be skipped altogether, or a cheaper version can be used.
.set shader (most expensive)
..set up textures / material settings (less expensive)
...draw all vertex arrays that use the above settings (least expensive)
repeat...
Note that every part on every part of those previously mentioned motorcycles with 5 shaders that are using that shader and texture set can be drawn at the same time up there. 50 calls is 50 calls, but not the same as switching shaders and materials once between each of them. in this case, there are only 5 changes.
Best to keep the draw calls under a few hundred. But they add up quick.
And lol @ the 27 textures on a vehicle.
---
Also, more on that ultra LOD stuff someone mentioned: Imposter Rendering.
http://www.youtube.com/watch?v=lJE5g18KLsQ#t=0m34s
Heads up to mute the audio. Skip to 2 min for a quick shot of it in VBS2.
hilarious. made my day. :-)
Some engines handle Texture fetching better than others. Some engines handle drawcalls better than others.
Other handle polygons at the expense of texture budget, and other games want to push animation power at the sacrifice of art quality.
I think Drake was made with 45,000 Triangles, but to a lot of games that's the poly budget of 9 characters.
REALLY depends on the tech, the title, and goals of what you're trying to make.
General rule of thumb I've seen is 1 texture set per character.
It's situational. I know people who are just starting out love their absolutes and concrete rules to follow but that's not how things work. Fuck it up a few times, get yelled at, learn and move on.
It's not "how many textures can I use" (blanket statement).
It's how many textures can I use for this object, under these conditions, doing these specific things, with this much time given.
The fast way to get yourself in trouble is to deal in absolutes and not understand why the rule was laid down. For the most part you'll probably be in a position that has to set the rules for you or other people to follow.
So the real question is "how do I set up the rules" not "what is the rule".
I would love to see PIX captures of a full scene rendered this way. I think a lot of ms would be spent on portions of the scene that only resulted in a few pixels on screen. Unless the engine does one hell of a job of breaking up the scene, manually separating it is likely to provide better frustum culling (because we know what is playable space and what is background) and finer control over final performance.
Drawn at the same time? Not sure what you mean here. Are we disagreeing? Certainly every part of one motorcycle that shares the same shader and material could be rendered at once, but I was referring to a set up where different parts of the motorcycle would use different material settings AND a different shader. Rubber without a reflection or opacity, glass without a normal map, etc. The texture would be the only thing shared. I'm not suggesting that is the only way to set up a motorcycle, of course.
Again, I think you are assuming something about mayaterror's engine. It depends on the target platform and the desired frame rate. 30fps is achievable on console with 1000+ draw calls.
Isn't that an absolute? tsk tsk...
This!
our engine does texture streaming right off the disk, and it is actually better to use a ton of small stuff vs one big texture. also if you have lots and lots of characters its best to make everything separate for maximum reuse. if you made everything on one sheet then wanted to re-use the pants on something else, your kinda fucked.
Textures (as well as smoothing groups) can in some cases dramatically increase the amount of objects in your level regardless how you exported them. (ofc this can also be used to your advantage in some cases)
i.e. if you have an object with 5 material IDs (or five diffuse textures applied to them) these objects then are split in multiple objects. even if you export your scene as one single piece, the engine will still break them down and treat them as separate objects based on the amount of materials you got. this way you increase not only texture fetch time but also object fetch time and this could be bad.
for the engines we use, we often combine textures that need only a single channel into the "alpha" channel of some other texture. i.e. throw your specular B\W texture into the alpha of your normal map\specular colour map, or the parallax map in the alpha of the normal map. this is a way we can limit down the amount of textures per object.
if we take crowd textures as an example then you should try to stick as many as you can in one pack from the people who belong into the same group. I have received crowd files with as many as 20 people crammed in one texture page.
there is one very nice plug-in for max called flatiron. this is an amazing tool that can help you to easily combine (or break down) textures and it is a huge time saver while at the same time it gives you great flexibility to manage your textures in the max scene as you please, and at the same time exporting them to the engine as you need. it is not perfect and doesn't fit in every case but it is a huge time-saver none the less.
I actually used that method a couple of times, because there is channels you don't need, and can use them for other maps, for example there was a time where I had to use the blue channel of normalmaps for specular. Its a way to press everything into fewer textures.