UDN
Search public documentation:

TextureOptimizationTechniques
日本語訳
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 Home > Materials & Textures > Texture Optimization Techniques
UE3 Home > Performance, Profiling, and Optimization > Content Profiling and Optimization > Texture Optimization Techniques

Texture Optimization Techniques


Overview


Memory and disk space usage is a constant struggle with the development of any game. When it comes to content taking up large amounts of memory and disk space, the biggest culprits tend to be the textures used. Quite often this is due to textures being added to the pool without careful planning and efficient use of channel packing.

This document explains some tricks for cleaning up inefficient texture use and making smarter, more efficient content up front instead of at the end of a project.

Content Assesment


If you are running into issues with disk space during development, running an assessment of the content making it onto the disk may be able to provide some insight as to where savings can be achieved. This is generally a quick process as the majority of it is based on an eye-test. Looking at the textures in a package in the generic browser can quickly lead you to see where space is being wasted.

For example:

assessment.jpg

Master Material Defaults


One area where unnecessarily large textures can sometimes make it into the game's content is through the use of master materials. When using texture sample parameters in a master material, a textrue must be applied to that parameter, but often it is never seen because each child material overrides it to suit its specific need.

The T_Test_Mask texture in the package shown is a good example of this. By examining the master material, we can see it is applied to a texture sample parameter.

master_default.jpg

This texture is a 512x512 texture that is never used or seen in the actual game other than being applied as a default in a master material. The problem is, any texture in a master makes it into a child, regardless of whether the chile material overrides it or not. This is easily rectified by replacing it with a simple 8x8 pixel black texture, which should be saved in a general package and reused by any master materials with similar setups. Also of note, if you have a master material that has a texture sample parameter used for normal maps, a seaparate placeholder texture should be used as you always want a normal map as a placeholder for a normal map.

Checking Material References


When profiling materials, keep an eye out for textures for effects that are not part of a channel pack (i.e. it's not a single channel in an RGB image). These are prime candidates for waste that can be easily remedied. Take the example shown below:

effect_no_pack.jpg

This effect is using the RGB output of what appears to be a grayscale texture, so the texture is obviously not part of a channel packed texture. In order to investigate further, sync it to the browser by pressing the find_in_browser.jpg button for the texture sample's Texture property. In this case, you can see the texture is a 512x512 noise tile.

The next step is to find out what else, if anything, is using this texture. Right click on the texture and choose Find All Materials Using This Texture from the context menu. This command is incredibly useful as it makes a collection consiting of materials that use the texture in question. It is slow to run the first time (it can take as much as 5 minutes in some instances depending on teh size of your game's content). However, it is generally very fast after the first run.

Once the collection has been generated, select it to see what else is referencing the texture.

In this case, there are quite a few materials, so it's likely shared across a large amount of the game. This provides no indication that removing it will cause a gain, but keep it in mind in case more optimization is needed down the line.

Channel Packing and Consolidation


Examining the package from before, three of the textures stand out as possibilities for wasting space:

assessment_waste.jpg

Each of these is a 1024x1024 emissive texture with all three color channels being used. Also, notice how only a small portion of each texture is actually being used. All of those black pixels are taking up memory needlessly. The problem here is two-fold, but the solution is fairly simple: pack the textures into a single texture by placing each one in a single channel and crop them down to use as few pixels as possible.

Emissive textures are actually ideal for this optimization case because they are soft, easily cropped, and don't have tons of detail. They also do not generally contribute a great deal to the look of the material overall. Of course, if that were not the case, this optimization may not be ideal for that specific texture and circumstance.

emissive_mesh.jpg

A great deal of space and memory can be saved by cropping these emissive textures and then performing a 'channel pack'. First, crop all the emissives so they fit into a 256x256 area. It may be necessary to go beyond a simple crop in certain circumstances. For instance, you may want to crop along the horizontal or vertical and then scale along the other axis to squish the texture. You can then unsquish it in the material and see if it looks OK. Soft emissive textures should hold up fine. This wouldn't work very well for a hard edged emissive, however.

emissive_crop.jpg

Now, in Photoshop, I am going to channel pack these emissives, which means I will store each one of them in the color channel of a compiled 3 channel texture.

emissive_pack.jpg

One useful note about channel packing textures, the compression format we use is DXT1, which, through a quirk in the compression scheme, stores an extra bit of data in the green channel of the texture. Thus, if you have an emissive that deserves some higher frequency detail or is delicate, put it in the green channel for the highest quality.

Now, even cropped to 256, it is possible the texture will still contain a good deal of wasted space. If the room is there and the texture is already being cropped and offset in the material anyways, why not get a quality gain? By taking the high-res source art of the texture, you can drop in a higher res version of that emissive, increasing the quality in that channel while using the same amount of space.

emissive_highres.jpg

Also note that if you are thinking to yourself, "Self, why don't I add yet another channel to the texture if, for instance, I had a fourth emissive to include?" The problem there is that a four channel texture compresses as a DXT5 instead of a DXT1, the gotcha with a DXT5 is that the alpha channel is full resolution from a compression standpoint, meaning that it takes up the same memory as all three color channels combined! This is why DXT5s are expensive.

At this point, you can sav and import the channel-packed texture.

Now, the master material needs to be set up to support cropping and offsetting the emissive textures. This does add a little bit of complexity to the shader (6 instructions), but since your texture pool is probably already so high it's likely worth the few instructions.

If you also happen to know that the master shader is texture stalled, meaning it spends more time processing texture samples then it does pixel shader instructions, adding a few instructions should not affect performance at all. You can find this out from taking a PIX capture of the material and analyzing it using the microsoft performance analysis tool.

Initially, the shader just takes this emissive texture "straight up" as is.

emissive_no_offset.jpg

We need to be able to offset and move the texture around on the material, so add some simple shader math to handle those cases, with parameters per material instance.

(Click for full size)

emissive_offset_small.jpg

The Static Switch Parameter that lets you decide whether to use cropped emissives at all on a per instance basis. Static switches work as a binary switch, meaning if you check it on, it uses ONLY the shader instructions running into the "A" input, and if it's checked off, it uses what's in "B". This means until you "Opt In", you get no additional performance burden.

One helpful note about switches, it is a good idea to always name switches with a verb, i.e. create an action out of toggling the switch. This will tell someone who is looking at the material for the first time what both options mean. Example, if you had named the switch "CroppedEmissive", and you look at the shader, how would you know which option meant which state? Since it is named "UseCroppedEmissive", anyone knows at a glance that the switch has an action, and what state it's in just by the name and whether the value is checked or not. Always name your switches with verbs.

The parameters in here are straightforward, the parameter called Char_Emissive_CropOffset allows the X and Y coordinates of my emissive glows to be manipulated, and then X and Y scale parameters let you independently scale the texture on one axis or another. This will allow you to "unsquish" any emissive textures!

There are a few ways to improve this math and probably save an instruction or two but this method is a good way to show everyone the different options you have.

Next, you need to be able to choose which channel of the RGB channel packed texture to use. Adding a StaticComponentMaskParameter expression to the material allows any of the color channels in the new packed texture to be used selectively, by passing only one of the possible 3 channels along in the shader chain:

emissive_channel_mask.jpg

Now, hit apply on the master shader, and open up one of the instances using the master as it parent so you can test to see if the changes are working properly.

First, turn on the static switch to use the cropped emissives.

emissive_usecropped.jpg

Then, apply the new packed texture to the emissive slot:

emissive_texture.jpg

Choose the color channel to use (in this case, red):

emissive_componentmask.jpg

Set the scale parameters to 2 to get the emissive to the right size on the material.

emissive_scale.jpg

emissive_wrap.jpg

OK, well, that doesn't look right. Why not? Because the texture is not CLAMPED. By default, a new texture imported into UE3 is set to WRAP in its texture properties. This means that if it tiles, it keeps repeating. Double click the texture in the browser and set it to CLAMP instead.

emissive_texclamp.jpg

While you're in there, set these flags as well:

emissive_preserveborder.jpg

Since all the edge pixels of our texture are black, this means that they will always STAY BLACK regardless of which mip level is being displayed. This prevents the texture from smearing if you're really, really far away from it.

emissive_clamp.jpg

Better! Now, offset the texture using the parameters. Eyeballing it usually works well enough, but if you prefer you can do the math in photoshop to know the exact crop values to type into these boxes. In this case, by pur coincidence, it is lined up perfectly with no offset.

emissive_cropoffset.jpg

Change the emissive color in the material instance, and check it:

emissive_color.jpg

Perform the same process on the other material instances using the new packed emissive texture. Here, you can see the final result:

emissive_mesh_final.jpg

Now to get rid of the unused textures. For this we use the consolidate command:

Select the 3 old textures as well as the new one we made.

emissive_select.jpg

Right click, choose Consolidate. This will delete three of these, and redirect them permanently to the fourth. You can do this cross packages, but to be safe, check EVERYTHING out before hand. This operation is not something you can undo, so be careful with it!

This dialog will pop up:

emissive_consolodate.jpg

Choose the new packed emissive texture, and hit the consolidate_button.jpg button. The other three go away for good, the new one remains. Again, CAREFUL! You can can cause yourself serious problems!. Make sure to save the package.

Parting Thoughts


  • When you are packing UVs, pack your emissives close to each other! It makes them easier to crop and saves memory.
  • Setting up stuff like this in the beginning saves a lot of pain in the end
  • Sometimes the shader complexity tradeoff is not worth the memory savings. This is very rare and case by case. It's almost always worth some savings.
  • Even without cropping your emissives, channel packing them is still a good idea and doesn't use any extra shader math at all, if the quality is there.
  • Consolidate... and Find All Materials Using This Texture are powerful tools.