[Tutorial, 2D] Creating a Simple Texture Atlas with OpenBRF

Users who are viewing this thread

Eärendil Ardamírë

Subforum Moderator
WBWF&SM&B
One way to optimise a module is to reduce the amount of draw calls for textures. Every time a new scene prop or item is appearing on the screen, the game engine is calling the material reference of the mesh and afterwards the textures of the materials. To reduce the amount of draw calls we have an incentive to bundle those textures together which have a high chance to appear together at a battle. This is for example the case for all the armouries of troops of a specific faction since they appear in most cases together. Such a bundle of textures is then called a Texture Atlas.

When or why should one create Texture Atlases? Texture Atlases are much harder to maintain than the single textures themselves. As a result modders should know when not to bother with them. Draw calls alone aren't the biggest culprit of performance loss in Warband, at least on a modern machine. The main culprits are multi-meshing (since every new mesh has to be rigged by the CPU individually, every frame), and high Vram usage with too many big textures. An atlassed 2k texture has not necessarily the exact same size on ram as four 1k textures, so it doesn't help a lot with Vram.
However if you use multimeshes a lot, atlassing all the textures use can drastically increase performance if you had a lot of the multimeshes.

This tutorial is also the ideal way to show an example for armouries which are looking like a slow upgrade of each other. For this I make use of the LSP by Fredelios, who splitted up the Native armour meshes and created new combos for them. Texture atlases are a good way to make a good use of them.
For this tutorial you will need:
To mention it ahead: This tutorial is for those modders with no real skills in 3D-modelling. Modders with skills at 3D-modelling, especially at UV-mapping, are probably not learning something new here. The core info of this tutorial is on how to move around the UV-maps of armouries with OpenBRF, which can be done as well within a 3D programm of your choice. (Additionally you might perhaps get an idea for armour upgrades coming along troop upgrades)

It can be useful to have some pencil and paper next to you. I tend to always take notes with the coordinates and do some simple calculation on sheet to have some simple "documentation" of what I did in case I need to revert or redo some parts of it (for the specular and normal maps for example).

Step 1: Basic Preparations
Prepare a folder with whatever name at your desktop and put into it the brf file which contains all the armour meshes which you want to bundle upon one single texture. This sounds like it is done very quickly but it can also take some time until you have made up your mind on how to do it the best. Start with the core armour meshes, others can follow up later when you see that you still have place for them. Copy paste also all required materials and textures into this brf file and the texture files into the folder, for the ease of use.
I have prepared a small tutorial bundle which you can download here. It contains all the files with which I have started at this tutorial, so you can follow the tutorial step by step. Take note that I concentrate on the diffuse textures and will ignore the normal and specular maps as well as on the mip maps.

Step 2: Texture Atlas Preparations
In our example we want to create a texture atlas around the Native armour mesh "lamellar_vest_a" (named "khergit_vest_a_lamellar" at the files of Fredelios and in the tutorial example brf file). Copy paste the texture lamellar_vest_a (so that you still have the original texture file) and rename it, for example to "khergit_armour_a". Open it then in Gimp. First delete there the mipmaps, so that they won't accidentally mess up something later.

You see now a 1024x1024 texture. At the end our texture atlas is supposed to be 2048x2048. The loading speed for a 2k texture might be minimally bigger than for a 1k texture but it won't make an impact compared to the reduced amount of texture draw calls which we will gain.

To prepare the texture atlas we now want to have our texture at the top left corner. We go for ->"Image"->"Canvas Size" and edit the canvas size there from 1024x1024 to 2048x2048. Your image should automatically be in the top left corner as you can also see in the preview. Select at "Resize layers" the option "All layers". Click then on "Resize" and save the file. Gimp is saving them as xcf files, other programmes have other naming conventions. You can always differentiate them from the dds files needed by the game engine since they have a bigger size. Export now also your texture as a dds file, simply clicking at ->"File"->"Export as...". Select there your texture "khergit_armour_a.dds and click on "Export". You want to replace that file, and at the Compression you select "BC1 / DXT1" and click on "Export" again.

eDsH9.png

At OpenBRF you can now either import a new texture or simply copy paste "lamellar_vest_a.dds" and rename it to "khergit_armour_a.dds". You should then see the new texture with our lamellar vest at the top left corner (sometimes you need to reopen the brf file for it to work). Now also copy paste the material "lamellar_vest_a" and rename it to "khergit_armour_a" and don't forget to replace the texture entries there (in our example we only need to replace DiffuseA with the new texture name, you can replace the Bump and Specular Entry with "none" and add them later when you have created such).

Then copy paste the mesh "khergit_vest_a_lamellar" and rename it, for example to "khergit_soldier". Replace the material entry with the new selected name, "khergit_armour_a" in our example. You will then see that the armour looks a bit messed up. You can see that it is the correct texture but it doesn't seem to be at the right place. That's the point at which we need to edit the UV-coordinates of the texture. Right-click on the mesh and select "Transform texture coords". A little window will open, giving you some few choices. "Scale" is basically a check if the old texture has a different relative size than the new one, "Translate" is basically a check if the UV-position of a specific mesh on the texture has changed. In our case, our "old" texture has been half the size of the "new" texture, so we need to scale the UV-map down to 50 %. We don't need to translate it because we have kept it in the top left corner which is kind of the starting point of the texture (at Gimp you can also see the coordinates (0/0) at the top left corner). OpenBRF is previewing each little change you make, so you can check if your "calculations" before you click "OK".

uJ4yw.png

fzEVC.png

This is now our starting position before step 3.

Step 3: Bundling Four Armour Ḿeshes upon one Texture Atlas
Fredelios has created a downleveled mesh form of the lamellar vest called khergit_vest_a (I haven't found it in the Native files, perhaps I have missed it though). It looks optimal for a Khergit Recruit, if it levels up, it gets a lamellar armour, that sounds logically. So we take it.

At our texture khergit_armour_a.xcf we create now a new layer. This way you have an original file which contains different textures or texture elements at different layers, so you don't need to redo the whole texture if you want to rework only a part of it. Now open up the texture "khergit_vest_a" and copy paste it into the new layer. The programme should copy past it automatically directly into the middle of the texture but we actually want it at the top right corner. You then need to offset it by clicking on ->"Layer"->"Transform"->"Offset..". We have a 1024x1024 texture sitting in the middle of a 2048x2048 texture. That means we need to offset it 512 px (or 25 %) to the right and 512 px (or 25 %) upwards, so -512 px. Check in the preview again if it is placed correctly.

J7gCF.png

Tf9SQ.png

Now save your texture file again. Afterwards merge all layers to one and export it again as "khergit_armour_a", replacing again the old file. At Gimp, you either need now to undo the merge or you need to close the file without saving. Otherwise you will loose your layers. I always undo the merges until all my layers are back again.

Reopen the brf file, so that the texture gets reloaded again. Copy paste now the mesh "khergit_vest_a", rename it to "khergit_recruit" and position it next to our "khergit_soldier" mesh. Edit the material name of it to "khergit_armour_a". It looks now again as messed up as our first one before. Now, what do we need to do? We know that we need to scale it again to 50 % because the old texture has had half the size. In the preview you will see that it does still not look alright then, it will actually show you the texture of our mesh with lamellar vest. That is because we have moved our texture now to the top right corner. So you need to translate it half the texture to the right, so 0,5 at U (while it is alright at the top, so V stays at 0).

C5GWs.png

CqsRK.jpg

Now we have already bundled two textures to one. You could say that you just want to have retextured versions of those two at the bottom. For this we could use "khergit_vest_b_lamellar" at the bottom left and "khergit_vest_b" at the bottom right. And now it is just the same procedure again and again:
  1. Create a new layer at the main texture file
  2. Copy paste the new texture upon the new layer and offset it to the wanted position
  3. Save the file, merge all layers to one and export to the dds file, undo the merges to get the main file again/close main file without saving
  4. Reopen the brf file to reload the dds file
  5. Copy paste (to still have the original one!) and rename the mesh, edit the material name
  6. Transform the texture coordinates accordingly to your texture changes
Summary of "Transform Texture Coordinates":
If you have placed four 1024x1024 textures upon one 2048x2048 texture like this (see also the image below)
A B
C D
then all the meshes should be scaled by 50% (both U and V) and
  • meshes originally using A should not be translated.
  • meshes originally using B should be translated by 0.5 in U (horizontal)
  • meshes originally using C should be translated by 0.5 in V (vertical).
  • meshes originally using D should be translated by 0.5 both in U and in V.
If there are already values when you open the dialog, always press "Reset" just to make things sure!

*Should one of the translations not work immediately it might be that the modeller has UV-mapped the mesh a bit differently. Try around by adding/substracting 0.5 to the value.

MAhPC.jpg

Having bundled four textures to a texture atlas such way results in four meshes all calling the same material and the same texture instead of four. The one texture file is bigger as one single texture of one armour, yes. But the one bigger texture is less in file size than all four single textures together. So you have already saved file size and draw calls with this method.
Of course you could now assemble four 4x4 textures and them assemble them into a 16x16 one, so a 4096x4096 texture. That makes sense if you are really sure that all the meshes calling this texture atlas are always appearing together. But as mentioned at the beginning, a texture atlas is harder to maintain. Most modders stay at a 2048x2048 texture, handling that one is already challenging enough.

Step 4: Bundling Multiple Armour Ḿeshes and Submeshes upon one Texture Atlas
However, no we don't want to have a simple retextured version of the armouries at the bottom half but other armour parts to make our troop tree more interesting. We want to integrate Submeshes upon the same texture as the main mesh, to gain a bigger optimisation effect. Keep in mind that this all evolves at some point into a kind of tetris game as we try to fit as many meshes upon one texture atlas as we can. Sometimes that is a bit trial and error to find the best places for it all. And often you will replan which meshes you will use. That's where the fun begins :grin:

For the example here we simply take a look around the other meshes prepared by Fredelios. We want to find some cominations which are reusing at least one of the textures which I have integrated already into my texture atlas. We quickly find the mesh "khergit_vest_a_mail". The main mesh texture is already integrated, so I only need to transform the texture coordinates again (copy paste and rename the mesh, edit the material name, transform texture coordinates by scaling to 50 %). The texture for the submesh, "khergit_vest_a_mail.1" calls the material and texture "mail_shirt_a". We integrate it into our texture atlas at the bottom left corner (create a new layer, copy paste texture into new layer, move layer to bottom left). We reopen the brf file again and transform the texture coordinates of the submesh (scale 50 %, moving it down (V) by 0.5) and do the same also for the original Native mesh "mail_shirt_a" (armors_g.brf in CommonRes).

Now we take a look around again to find another armour combo which uses our three textures now and which still looks like it fits into our new Khergit troop tree set. For this the mesh "shirt_a_lamellar_mail" could be a good fit, it uses the lamellar and the mail texture. Only the shirt a texture is new. If you look around a bit more you will notice that if you integrate the shirt_a texture, you can also use the meshes "shirt_a_ew", "shirt_a_lamellar" and "shirt_a_mail_new". So there are three other new armour combinations for which you don't need any new textures!

This might however open up a new conflict about which you need to decide: Does the shirt_a part fit into your faction design? I decide that it does. I simply state that all recruits with the armour mesh "khergit_recruit" are coming from villages and all recruits with the armour mesh "shirt_a" are coming from the Khergit cities. This gives a greater variety to my troop trees.

Now I can however also decide that the texture "shirt_a" is not that important for me. That gives me the option to reduce the texture quality of it. Instead of integrating it into my texture atlas as a 1024x1024 texture I will only integrate it as a 512x512 texture. This way I still have place at my texture atlas for other perhaps more important things I want to integrate there in the future. The shirt_a meshes will however look a bit worse. This is a design decission you will have to make.

In Gimp I create again a new level and copy paste the texture "shirt_a" into it. Instead of offsetting it I do now first rescale that layer to 50 % (->"Layer"->"Scale Layer"), or from 2048x2048 to 1024x1024 (alternatively you can first resize the original texture file to 512x512 and then copy paste it into the new layer). Afterwards you need to click on ->"Layer"->"Layer to image size", so that you can move it around again freely. Since it is smaller now, I don't need to offset it by 512 px but only by 256 px too the right. Instead of offsetting it 256 px down I move it 768 px down so that it is at the bottom left of the right corner.

AI-sn.jpg

Reopen the brf-file. Now edit the material of all (copy pasted and renamed) meshes and submeshes with the texture mail_shirt_a and lamellar_vest_a and transform the texture coordinates as you did before. Take note that you can transform the texture coordinates of multiple meshes at once (Press Ctrl to select multiple at once) but take care that you only combine those meshes which have had the same texture at the beginning.
Then set the material of all meshes/submeshes with the texture "shirt_a" to the new material and try to calculate on your own by which values you need to transform the texture coordinates.
The "old" texture was 512x512 which is 25 % of 2048x2048 => Scale (both): 25 %.
From left to right, the texture is starting at the middle, 1024/2024=0.5 => Translate U: 0.5
From top to bottom, the texture is starting at 1536, 1536/2048=0.75 => Translate V: 0.75

Now you will have noticed that "shirt_a_lamellar_new" something is still looking odd like you can see at the screenshot below. For some reason the UV-map of the upper arms and at one part of the back have been moved at the original mesh by 100 % to the right (you can see it in OpenBRF if you translate U +0.25 to the right, now they look alright but the rest doesn't), meaning that if you organise a texture like
A B
the UV map for those parts shows up at B. You don't notice the same at Fred's combo armour with the old texture because it gets wrapped around A again due to the missing B (if the texture would be 2048x1024 instead of 1024x1024, you would see the same black spots). In our example case you would need some quick help by a modeller which can move it back again to the original place. This means for you: Creating a texture atlas only with OpenBRF is not always working out perfectly! Sometimes you need help by modellers.

We ignore this problem for now and pretend that it is alright. As a last step we now decide that we want to add one more texture to our texture atlas, so we look through the other armour combos. If we stay at the shirt_a stuff we might find that shirt_a_leather_jerkin and shirt_a_byrnie would be a good addition to our troop tree. We only need to integrate the texture "ragged_leather_jerkin" at our texture atlas and we again decide that we only want a downgraded version of it, so again 512x512, putting it at the bottom right corner of our texture atlas (offset X and Y: 76:cool:.

5-OgW.jpg

Again, edit the material name of the copy pasted and renamed meshes. First, transform the texture coordinates of the submesh with the old texture "mail_shirt_a", it works the same way as before. At the meshes which have had "shirt_a" before, you will notice again a problem. Here you see that if you apply the exact same transformation date, they look pitch black. If everything looks wrong and not only parts of it that means you still have a chance to solve it only with OpenBRF. In those cases always edit the translation values up and down in 0.25 steps (scale calculation value, at a scale factor of 50 % you try around with 0.5 steps) and you might end up lucky nearby. In our case, setting V to 1.0000 did it. (This is also one of the reasons to keep the original mesh with original material/texture calls around, to check again on how it should have looked like)

BZ3t2.jpg

f6wBM.jpg

For the ragged_leather_jerkin meshes, you can again try to calculate on your own the correct transformation values.
The "old" texture was 512x512 which is 25 % of 2048x2048 => Scale (both): 25 %.
From left to right, the texture is starting at 1536, 1536/2048=0.75 => Translate U: 0.75
From top to bottom, the texture is starting at 1536, 1536/2048=0.75 => Translate V: 0.75

Now, at the end, we have ten different armouries all calling the same material and texture. That means also that you can merge all meshes which are calling the same material. That means that if you have meshes like

khergit_city_soldier
khergit_city_soldier.1

you can select them both, right click and select "Combine meshes". This way the engine does not need to call the submesh too and you reduce the workload, even if minimally, a bit more. That means you should have only ten meshes now at your brf file, not counting the safe copies of the original meshes.

Mc53h.jpg

And now you can set up your troop tree. It is of course the best that you already plan your troop tree before and create the armouries for it. In my case here I can simply make up as I deem it fitting and it will show a nice slow development of the troops as they upgrade
Code:
Village Recruit (1) -> Scout (2) -> Rider (3)

City Recruit (4) -> City Bowman (5) -> Archer (7)
                 \> City Militia (6)     -> Soldier (7)
                                         \> Citadel Guard (8)

City Recruit (4) -> Town Watch (9) -> Town Guard (10)

Step X: Normal and Specular Maps
You either need do the same steps again for each single normal and specular map or create new ones for your texture atlas. Doing the same steps again can be tricky, you should write down each single step you do, to make sure that you are doing the same ones in the correct order again.



I hope some of you will find this tutorial useful. I am looking forward what you will come up with :grin:
 
Last edited:
Top Bottom