Nolimits 2 (tunnels part 1): Global texture coordinates

6th May 2018

Let’s take a look at tunnels. You’ve decided that your newest Nolimits 2 creations needs a tunnel, and because you’re comfortable 3D modelling, you want to create your own custom tunnel rather than using NL2’s built-in tunnel system. And so you create your tunnel model and then move the terrain out of the way, either by using virtual tunnels or by lowering some terrain nodes, as I have.

But now you need to patch up the edges with some “fake” terrain. So you model some flat ground in your mesh and unwrap the UV for the grass texture. After setting up a grass material, and fiddling with the scale for a while, you’ll probably end up with something that looks like this.

Well, that’s certainly not bad, and the textures seem to match in scale. But they don’t line up exactly, and we end up with some fairly ugly seams where the object and terrain meet. Luckily, we can do better.

Texture Coordinates

Texture coordinates state the position of the texture that should be at each point in the mesh. Specifically, each vertex in a mesh has a UV coordinate, which in order to avoid confusion with the XYZ coordinates of the vertex position, use two axes labelled UV.

A texture occupies the coordinates between (0,0) and (1,1) regardless of resolution. By default, textures are repeated, and many vertices will have coordinates outside of the 0..1 range.

When we UV unwrap a model, we’re manually authoring the UV coordinates. However, there’s no reason we need to unwrap every model; in many cases, we could generate UV coordinates using some algorithm, and thankfully NL2 provides a lot of common UV generation algorithms through the material editor. You know, in that tab that you’re afraid to press any of the buttons in.

The last two options, From Model Vertex Position (2D) and From World Vertex Position (2D), are the ones we’ll be looking at today.

I’ll explain how to use them shortly, but for the maths nerds: We transform the 2D texture coordinates into 3D by aligning them with the two 3D vectors as shown in the UI above. The result is like having a display projector literally projecting the texture across the object, with the “light beam” pointing in the direction of the cross product of the two vectors specified.

Matching Terrain

Because it’s quite difficult to see how close we are to being aligned correctly with the grass texture, we’ll use a more obvious texture to test this with. In Blender, go to the UV/Image tab and create a new image. Make sure it’s 1024×1024 and set the Generated Type to Color Grid. You could just use mine, but where’s the fun in that?

Now we have a nice striking texture, create a new park in NL2, and in the terrain settings, we’ll replace the grass layer with our own texture. Set the texture as the basemap, and disable detail and bump mapping. Because the texture is an 8×8 grid, we’ll set the texture repeats to 0.125, so each square aligns with the 1m grid in the editor.

Vibrant! Now we need an object to apply our material to. I went fancy and created an 8×8 grid of pyramids. You can create something simpler, but I’d recommend having it raise above the ground slightly, to avoid z-fighting. You could just use mine, but you know you’re going to have to learn proper modelling software eventually, right?

Create a new material in the park folder, and assign the grid texture to Texture Unit 1. Under the T.C. Gen tab, switch to World Vertex Position (2D). To align a texture with the world axes, we need to set a value in one box per row. To save you the trouble of trying different combinations for yourself, set S = 1X and T = 1Z. This will project the texture downwards, just like how the terrain system works.

Create an nl2sco for your object and assign the new material. Place the object at (0, 1, 0) so it sits above the terrain, and the result should look like this.

Next, we’ll fix the scaling. We set the values in the T.C. Gen tab to 1, meaning the texture repeats once per metre. Replacing the 1’s with 0.125, like in the terrain settings, will scale the texture correctly.

It looks like the texture is vertically flipped, so our values are still a little off. We need to invert the texture on the Y axis, so we’ll change our generation settings to S = 0.125X and T = -0.125Z.

Finally, the texture is still misaligned, by half the texture’s width and height. This is because Terrain textures are centred on the park origin, whereas generated textures like ours are corner aligned with the park origin. We simply need to translate the UV coordinates by (0.5, 0.5) and we’ll finally have a correct match.

To do this, we need to add a coordinate modifier. Go to the T.C. Mod tab, and enable and Setup Mod Stage 1. We’ll perform the translation using a matrix transformation, but the simplified instructions are: select transform, leave the default values alone, but set the right two boxes to your u and v offset respectively.

Now, as for the difference between Local and Global Vertex Position. Select your test object, and move it along the surface. The texture is actually “attached” to the world, rather than following the object as it moves. This is how we can know this texture will be aligned with the terrain, no matter what object it is on.

Final implementation

To try this out on an actual park, we’ll need a tunnel model. You could just use mine, but… wait, really? Fine, as long as you promise to start learning Blender as soon as we’re done here.

All we need to do for our grass texture is follow the same procedure as before. Collectively, the steps as we did them before are as follows:

Create your grass texture and assign two textures from the NL2 Library, Terrain/SFX/DiffuseCloudGreen and Terrain/Grass/GrassVegitation. You’ll need to follow the above instructions twice, once per texture unit. To combine the textures correctly, use the following shader code:

Result = Mul(Tex0, Tex1);
Specular = 0;

If you follow the steps correctly, your grass texture should align seamlessly with the terrain. If you have any questions or got stuck at any point, the 3D Modelling thread at Nolimits Central is a good place to ask.