- What will be the result?
- 3D Scene setup
- Postproduction setup
- Final output
Hello and welcome to the NUTS Technical Documentation!
There are definitely many approaches to how to create 32bpp/ExtraZoom newGRFs. Some people just render individual sprites like zbase, my method is however a lot more sophisticated, first I create a complete postproduction pipeline which almost automatically creates sprites from my renders.
This page will lead you through my workflow pipeline of NUTS, starting with a 3D model and ending with a template-friendly png spritesheet.
This page is very similar to the YETI technical documentation, uses the same software and very similar approach to things.
I cannot express how hugely helpful this infrastructure is for me. Even though I spent weeks creating it (most of this time was due to testing, now that I can give you the values you can get it done much faster), it saves me a lot more time in the long run for the huge amount of sprites NUTS has. If you intend on creating a train set with similar amount of sprites (NUTS has around 60 000 sprites), doing something similar to what I did is a really, really, REALLY good idea.
I am using Autodesk 3D Studio MAX 2014 with V-ray renderer, and for postproduction Adobe After Effects with Adobe Photoshop. In case you do not have these programs, you can probably do something very similar in other software of your choice.
Note: All of the images below have a full-size version when you click on them!
What will be the result?¶
- A 3D scene with 256 vehicles which then renders into a sequence of eight 4096x2048 pngs.
- Then the image sequence is cut into pieces (sprites).
- Then the sprites are re-organized in a way to create a sprite sheet OpenTTD can load with a simple template.
- Alternatively, mask sprites are created for Company Colours. Those then need conversion to 8bpp in Photoshop.
- My pipeline has output of sprites for 256 vehicles in all 8 rotations per single render.
Note: My trains are using stretching on diagonal tracks to keep up with the stretching that OpenTTD does. Simply ignore the scaling part of train dummies if this is not what you want to do.
3D Scene setup¶
The initial source of everything is a 3D model. Because NUTS has about 60 000 individual sprites, it would be absolutely insane to render every vehicle one by one. Even if rendered as a sequence and that way rendering 8 sprites at a time, it would still be 7500 renders! Which would obviously be extremely tedious and boring.
So, we need a 3D scene which has MORE of those vehicles.
The 3D Grid¶
I started with what I want to render.
Regarding scale, 10x10m is one tile, same as for YETI. Only this time, I use centimeters because we will need the extra precision later on.
Everything we will model is going to be on one large plane, let's create that first.
Just by various calculations I decided that 256 vehicles should be able to cover each kind of vehicles. For example, Maglev generation of Flatbed Wagons does not exceed that number, and so on. This means I can have one scene layer which includes all of Maglev Flatbed Wagons, and just unhide and render those whenever I like.
For whatever reason I reached by trial and error, my plane center is at [X = 297,29 ¦ Y = -297,29 ¦ Z = 0], and the plane is for 16x16 vehicles, so 16 000cm Long and Wide, with 32 Length and Width segments. I use 32 so that I get vertices for centers of my trains, while 16x16 segments would just create the full tiles.
I used spacing between vehicles to be 1 tile. This choice was not the most fortunate as it only allows to have vehicles 8/8 (0.5 tile) long. Longer vehicles would simply not fit into my pipeline and I would need to create something else. In case of NUTS this touches the huge steamers like Rail Intercity and Rail MEOW classes.
For MUCH easier orientation later on (you will get batshit insane in the postproduction if you do not do this!), it is definitely helpful to add some systematic numbering to our vehicle positions. Since I am going with 256 vehicles, it is very intuitive to use hexa numbers, 00 to FF. Even though I personally never really used hexa numbers and I hate do do so, this is definitely a good time for them.
So I just created 256 text objects in my scene, offset by 1000 cm in x and y, and changed their text value accordingly. If you can do this by some script, it will save you some time.
The numbers are going to be extremely helpful as they will let us know which sprite we are working with in the postproduction.
Train dummy objects¶
All of the vehicles should have an unified controller. I find it easiest to create 256 dummy objects which are in the correct positions, and animate (rotate and scale).
Each of the dummy objects is then moved exactly to the center vertex that our plane has created.
Remember to create individual groups for each element, like Lights, Camera, Ground (those 3 could easily be in 1 layer), and more importantly Train Dummy Objects. This helps with easily selecting all of the things of the same type quickly.
After creating all of the dummy objects, set the scene timeline to be 8 frames long. Now it is time to simulate train rotations with the dummies.
There are 8 train rotations, and in 4 of them the train is 140% long (precise number should be 141%). You definitely do not want to create the models in one of the stretched directions, but unfortunately the first direction of a spriteset template is always the direction you can see in my frame 1.
We definitely have various options how to solve this: we could keep this upward direction at frame 0, and create our models in frames 1, 3, 5 or 7, OR since we have the power of postproduction and do not really care which direction we start with, I moved the LAST spritesheet direction to frame 0, reshuffling them in the postproduction later.
Last thing to do with the dummies is:
- Select them all by selecting their whole group
- Animate their local Z rotation, rotating them 45 degrees per frame.
- In the frames where you get side or vertical views, increase their scaling to 140% in local Y (or X, depending on your orientation). Make sure the other views stay at 100%
- As it will become clear later on in the postproduction, the scaling to 140% makes vehicles get out of our 128x128 sprite bounds in the vertical || views. To solve this, I moved all of the dummies in frame 1 and frame 5: 100cm in the local Y towards the top of the rendered image. That means in frame 1 50cm in the direction the trains are facing, in frame 5 100cm in the direction trains are having their back turned to. Note that you could change this 100cm value, and just override it with the sprite yoffsets, because that is functionally all it does. Just make sure your vehicles fit into the sprites even with the stretching. Obviously if you do not use stretching, OR use bigger spacing between vehicle dummies than 1 tile, this issue will probably not appear.
Now all of our dummies should be rotating and scaling (plus moving) with time. It is time to make them render correctly...
Originally I just took my camera from YETI which was looking at 4x4 tile grid, multiplied the distance from origin times 4, and was done with it. However, after I set up the postproduction infrastructure, I discovered that the differences between e.g. sprite 00 and sprite FF was catastrophically huge. (about 10pixels I think) Of course if I was to have all of the sprites to be interchangeable, I would need a lot smaller error. So what I did is put 5 resulting sprites on top of each other (00, 0F, F0, FF and 77), and looked at the differences. Then I moved the camera a little bit in order to get a better result, rendered again, and compared the sprites.
After almost 2 days of testing, I came to these values:
Camera Target: [X = 0, Y = 0 , Z = 0]
Camera: [X = 16725,612 Y = -16725,612 , Z = 13656,412]
Rendered at 4096px x 2048px
The differences I am getting are about 0.5 px or something along that value, which is not really noticeable in the game.
Note: I could get rid of this imprecision issue entirely if I just animated the camera to stay at each vehicle for 8 frames. It would just require different infrastructure, but would be equally doable, might even be quicker in total, but with less editability and more likelihood of errors. It would be necessary to offset the keyframes of each of the dummy object, make the camera animate correctly, end up with 2048 frame long animation, etc... You would just have to animate the camera instead of moving the cutting mask in the postproduction program and setting up precise camera position... You would still need to do some a lot of the postproduction work anyway, so I do not think it would be worth it. And you would still have to get the camera position exactly right at some point sooner or later. Ultimately all the sprites would be more consistent, but the difference is probably so minor that it is not visible in the game. As I said, my current difference is about 0.5px, which is nothing... therefore I prefer my method of doing it but there definitely are other options you could consider.
Setting up the global light is a lot simpler, I just set up the following sun:
Sun Target: [X = 297,288, Y = -297,288 , Z = 0] (center of the ground plane)
Sun: [X = 500 000, Y = 50 000 , Z = 500 000]
Any train models should be up to 500cm long (1000cm is a full tile), width could probably be the same but my trains are around 250-350cm wide so far. As far as height goes, 450cm should not be a problem.
Try to make a train box, Align it to a dummy object, Link it to the dummy object so it also gets the rotation and scaling, and try to render out your sequence. Then we can proceed with cutting it into sprites.
Example result of my test render, frame 0. For the postproduction it is important to have all 8 frames of your animation. It is not really important to have any trains in there however, IF you link the numbering to the dummy objects like I did. - it is just helpful to be able to recognize between various rotations.
With this output, you could now probably go and cut sprites by NML code, changing some values in the template or something, and picking separate train rotations from separate images. I prefer to get it all in one nice spritesheet file though, applying some adjustments and making it easy to orientate in.
Plus you would need at least some postproduction if you wanted to get 8bpp CompanyColour masks.
Another problem would be that we would have to either do some total wtf magic by NML template coordinates or define various 256 templates, or some other stuff.
I also like to adjust tiny things of my render, like brightness/contrast/matting/... so it is very beneficial to have some postproduction anyway.
Therefore I prefer to put all of the 8 frames we rendered into one file in a form that NML can easily load with a simple standard template.
At this point we have a rendered sequence of 8 png images, each of them representing one of the train rotations.
This part of the documentation will also work as a step by step tutorial in case you are not very experienced with After Effects, or just to illustrate the idea better.
What are we going to create¶
We are aiming to create a RENDER composition in which are going to be loaded all of our renders, switching them by visibility switches. This RENDER composition should then lead through the whole pipeline and output in one SPRITESHEET composition.
- First of all we will split the RENDER into 256 CUTs - each CUT being one vehicle,
- and put the CUTs into SPRITESETs which then aligns all of the CUTs together, sorting all of the vehicle rotations.
- Last but not least we will sort all of the SPRITESETs into a complete SPRITESHEET - which will be our final output as OpenTTD can easily load that.
- Add an option to render CompanyColour masks.
Note that we will create x4 zoom sprites through this postproduction, but x1 is also an option by downsizing the final spritesheet and putting it next to it in the composition.
In case you wanted to create x1 by some different method - like directly render x1, or choose some specific downscaling algorithms After Effects does not have, you would need to choose some different approach.x1 sprites are important because:
- x1 8bpp sprites are mandatory for all things you code! In my case of NUTS I already have all of the 8bpp drawn from my previous efforts, but if you were starting a new set then you probably need your x1 8bpp sprites to be created somehow. (Pikka just breaks Pineapple trains in 8bpp by supplying empty 8bpp images ... you could do that but I think it is a lot nicer to keep things operate even in 8bpp... for example people with weaker computers could easily find themselves in problems in a medum sized game with e.g. YETI and NUTS, and might prefer to use 8bpp blitter. After all, once we get our infrastructure done, it will be just a few clicks to convert a full set of 256 vehicles)
- x1 32bpp sprites are great to add because if you only supply x4 32bpp sprites, OpenTTD does the downscaling to x1 and x2 itself. And in some cases of contrasty/rough textures, it can pick the wrong pixels, and the result could look quite weird. Due to that it is sometimes nicer to have the x1 a little more blurred, but more accurate.
Unlike YETI, the cutting mask we are going to use here is very simple - a 128 x 128 px square, cutting out each of the 00-FF positions.
Here you have two options, you can either create this one 128x128 image, and move it around in After Effects later. Advantage of this is that you can easily edit this image later on.
Alternatively, you could create 256 layers in photoshop, each of the layer being the final cutting mask. Creating them would be a bit tedious, loading them in After Effects would be a bit tedious, and I think it is more likely to have errors in Photoshop than in After Effects. (Though the CC2014 version of Photoshop is a LOT better in that with the move snapping by alpha. In older versions of Photoshop you just move pixels, in After Effects you could do this by just incrementing/decrementing the Masking layer position.
What I did:¶
- import the rendered PNG sequence, and create an 8-frame composition from it. Call the new composition anything you like, it is just temporary. How about RENDER_CUT. ... here I demonstrate it on some CUT_MASKS_NEW.
- pre-compose the PNG sequence and call it RENDER.
- create a helper in photoshop, made of 128x64 rectangles to help me position the CUTTING MASK layers in After Effects, and import this .psd/.png ... I also like to lock this thing so I do not accidentally move it.
- import the photoshop CUTTING MASK.
- Now we should have a composition RENDER_CUT which has RENDER composition, CUTTING MASK psd, and helper psd in it.
- move the CUTTING MASK to appropriate location - first by mouse to close it up, after that finish it precisely by numeric input.
- With the layer selected, press P to see the exact position. Make sure the values you end up with are whole numbers. In my case they also have to be multiples of 32 - defined by the grid - if it is not, something went wrong.
- Duplicate the CUTTING MASK and do the same positioning on the opposite end of the grid (top/bottom)
- Now duplicate one of the CUTTING MASKs as many times as needed to fill the grid space between the two CUTTING MASKs.
- Finally, select all of the CUTTING MASKs in the column, and Distribute them.
- Repeat this for all of the columns you need. You could possibly duplicate a whole column and then move it 128px horizontally, and 64px vertically.
- Check if everything is right ... it is easy to see - nothing must overlap. If any are overlapping, then you either have too many duplicates, or something else is majorly wrong. Their edges must touch precisely. This is seen as their edge points show as one.
- HIDE all of the MASK layers
- Duplicate the RENDER composition inside RENDER_CUT Set TrkMatte of the RENDER layer to Alpha.
- Duplicate the RENDER composition 255 times to get 256 copies of it total.
- Move the RENDER layers between every two MASK layers. This way each of the RENDERS will take the MASK as its Alpha TrkMatte. We hid the MASK layers earlier because the mask gets automatically hidden only upon applying the Alpha matte, but not when a layer is moved to it after duplication.
- Pre-compose pairs of MASK+RENDER, and call each of the new pre-composed compositions for example CUT 00 - the numbers indicating the ID of the tile.
- With the 256 CUT compositions we have now we can move to the next step - creating spritesets.
Note: there are other ways to reach this result, for example you can first create 256 CUT duplicates with only the RENDER in them, and then copy-paste the appropriate MASK in each of them. Up to you, both approaches take a lot of time.
Once we have all of the CUTs with individual tiles in them, it is time to put all these tiles into spritesets. You CAN go ahead and just dump all of the CUTs into the final composition, but I think it would be total hell to find mistakes in such a thing and downsizing to small spritesets would be pretty much impossible, so I suggest to use the same method as I do here...
- Create a new composition called SPRITESET_00, size 1152 x 128, with duration of 1 frame only.
- Add the MASK into this (your could as well just create a new 128x128px solid), and align it to the left.
- Add a text layer on the MASK layer, and write there the tile ID of the current spriteset. This is just for orientation purposes but it is really helpful to have it for checking purposes.
- Add the appropriate CUT composition to the SPRITESET composition (for example CUT_00 into SPRITESET_00)
- Position the CUT next to the MASK - by mouse to close up first, finish off by numeric input. Same as with the cut.
- Apply time-freeze effect to the CUT. - right-click on the layer -> time -> time-freeze
- Duplicate the CUT to have 8 duplicates.
- Move each of the duplicates by 128px horizontally (vertical position should already be correct from step 5
- Assign the correct time-freeze value to get the right rotation.
- Create a new composition called SPRITESET_01 and repeat the whole process 255 times!
Note: You can probably save some time by duplicating a SPRITESET composition which already has the MASK and the text inside of it. What I did was that I first created all of the SPRITESET compositions, and at the same time pasted the mask+text into all of them. So all that remained was only changing the text value, and adding the CUTs.
Now that you are probably HATING to do anything more because the last two jobs were undescribably EXHAUSTING, I can cheer you up, all we need to do is:
- Create a new composition called OUTPUT, size 2304 x 16384, 1 frame.
- Drag spritesets 00-7F to this composition.
- Align spriteset 00 to the top of the composition, align spriteset 7F to the bottom.
- Select all of the layers by Ctrl+A, and distribute them all vertically.
- Align all of the 00-7F to the left of the composition.
- Do the same for 80-FF, but at the end align them to the right of the composition.
- Select all of the spriteset layers 00-FF, and pre-compose them. Call the pre-composed composition OUTPUT_x4
- When in the OUTPUT composition, press Ctrl+K to change the resolution to 2880 x 16384
- Align the OUTPUT_x4 to the left, and duplicate it
- Set the Scale of one of the duplicates to 25%, and align it to the right of the Composition
- RENDER! - render as PNG sequence, do not forget to select RGB+Alpha as the color channel otherwise you will have the composition background where you want transparency instead.
Note that After Effects cannot create a composition larger than 30 000 pixels. Since our cute little spritesheet is 128px x 256, it would require 32k pixels. So it is necessary to simply put them next to each other and only get 16k height. Or of course you could do even more columns, up to you. Earlier what I also tried was to make the OUTPUT 16k high 2-frame composition, and have first 128 spritesets in the first frame, and the other 128 spritesets in the second frame. This was a bit more confusing and it is nicer to have everything in one file if possible - especially after you have to do things manually with some of them, like convert 8bpp masks.
Company Colour infrastructure¶
As an extra, we can make our pipeline also flush out CC masks. In order to do this, we have to render masks from 3D. I do this by a matte material, matting out all of the parts except the CC part I want to see. This means that we have to render 3 times - Full sprite, CC1, and CC2. The model also needs some special adjusting - you have to use Material IDs and a multi-material (you could also have completely separate geometry if you like.In the postproduction:
- Create a new composition called CC_MASK - 4096 x 2048, 8 frames
- Import the renders of CC1 and CC2 and put them into the composition
- Pre-compose each of the renders and call the new compositions RENDER_CC1 and RENDER_CC2
- In CC_MASK duplicate each of the two layers 8 times
- Apply Extract effect to all of the layers
- Put various values to the Extract effect - 0-32 for first layer, 32-64 for second, ... - this will grab 1/8th of the brightness spectrum from each layer.
- Create new solid with the colour of one of the company colours - pick them from an opened image or import a CC layer to pick colours from. Create all the 16 colours.
- Put all of the colour layers to the CC_MASK composition below the appropriate RENDER_CC compositions, and set the TrkMatte of the colour layers to Alpha.
- Put the CC_MASK composition in our main RENDER composition. Hide everything else and render out OUTPUT.
- Open the output file in photoshop and convert it to 8bpp palette. (What I do is fill the alpha with the 8bpp blue, and copy-paste everything to a correct-size 8bpp image, instead of just assigning 8bpp conversion or something.)
After all the work done, we are getting up to 2 large spritesheet files, one normal and one combined CC mask. The only thing remaining is to open the CC mask in Photoshop and re-save it as 8bpp paletted image.
Lastly, you need to load the sprites into the game. Here follows the main example file, and the template I use for both x4 and x1. The CC MASK is in the same place so you just need to define one coordinate, and both the file names.
Here I use THE SLUG just because the company colours are not very applicable to our example render.
Extra things I did¶
The process I just described and the various options for alternatives, are all just a straight path to the final result. When I was actually creating all this, I did a lot of temporary testing things, just to get things right.
As a prime example goes the testing composition for render precision. I just have a 1152x128 composition, in which are spritesets 00 and FF, and another same composition with 0F and F0 - to check vertical, and horizontal error amount. Once all of the sprites match, precision is good enough. I did this with a testing render, just boxes on the 4 tiles. I also added 77 for the eventual check in the ~middle.
I do not know how your chosen renderer handles matting, but when I render with alpha, the objects have a slight bright edge. That can be easily fixed by correcting Matting - After Effects has a function for this Refine Soft Matte. The rnedered objects fit much better into the game environment without the strange white outlines.
Note that the adjustment layer has the CC mask above it - the CC masks are getting their own Refine Soft Matte in their compositions.
Adding a bit of contrast is generally better than ramping up sun intensity values - at least that is what I think. Do not hesitate to add similar adjustments like brightness, vibrance or saturation - not everything has to come from the render. If you want, you can even put it all to an adjustment layer - I put the Refine Soft Matte because that is used simply for all of the renders, and then I just copypaste Brightness & Contrast effect to each render and adjust them individually (just slightly though, some differences could lead to too much inconsistency).
I hope you enjoyed reading this guide/documentation/stuff whatever your intention was, I hope it was useful or at least interesting/entertaining to see how NUTS is done. As for myself, I have to admit this is quite gigantic, the amount of work put in it is insane (especially the "repeat step blabla 256 times" parts), but all in total it is very much worth it. Being able to just focus on creating the models now is priceless. I can absolutely recommend trying to set up something very similar if you intend to create a large train (or anything else) set newGRF for OpenTTD.
I hope you found this useful, thank you for reading and if you have any feedback/ideas for improvement/references when you used this/stuff, I would be glad to hear it all.