Published Monday 3rd July 2023

Making URP Lit & TerrainLit shaders produce matching renders

In case you don't already know, I'm making an MMORPG, and in doing so I'm also learning Unity. I've been a programmer for a very long time but I'm not particularly familiar with drag and drop programming so the concept of making a scene by dragging in objects, setting them up via dialogs full of options, and only writing actual code as a last resort when the engine doesn't already offer the desired functionality still feels a bit alien to me. It's been about a year now since I first installed Unity, and of all the stumbling blocks I've come across in this time, the ones that seem to trip me up the most always relate to imported game objects, and making them look how they're supposed to via the various dialogs of settings. Unless you're using your own shaders and rendering pipelines, things feel pretty restrictive a lot of the time.

One of the first things I did when starting to develop this game, was to create a terrain with a river flowing through the middle. I created a couple of base textures to represent dirt, sand, and grass areas, and I painted the terrain with these, sinking a deep cut-out in the middle where the river would be. I then added a plane inside this cut-out and designed a custom shader for the water flow. So far, so good.

Then I designed a bridge in Blender ready to import in. My idea was to texture everything except for the top face, so that I could just replace this in Unity with the same dirt texture used for the terrain and create a seamless path over the river. I created a material in Unity, set it up to use the URP Lit shader, applied the same dirt texture and normals map as used on the terrain, and imported my bridge.

As expected, the import options let me re-map the material I'd applied to that top face in Blender, swapping out for the material I'd created in Unity to match the terrain material. I set everything up, dragged the bridge into the scene to create a prefab, positioned it appropriately across the river, and... it didn't look at all right! Awful seams where the terrain and bridge textures meet.

At this point I tried adjusting everything I could. I experimented with the Lit, SimpleLit, TerrainLit, and even non URP shaders to try and make the two textures consistent. I also considered forking the TerrainLit and/or Lit shaders to create my own, but it felt like I was missing something simple. Nothing really got me very far until somebody on Mastodon with evidently more experience than I have, mentioned that terrain normals always point upwards, which makes a lot of sense!

Terrains in Unity aren't real meshes you see, they're effectively just a plane with a height-map applied so that the renderer knows to distort the vertical position of a particular pixel, so when sculpting a terrain all that you're really doing is colouring in a height-map texture to control that distortion. Terrains have to work this way for performance but it means that they effectively only have the one face, which in turn means they only have one per-face normal and this always points up regardless of the curves of your terrain. So the lighting engine is looking at that one upward pointing normal when calculating how brightly to render a pixel. On the flip side, the imported object is a real mesh with many per-face normals pointing in all sorts of directions, so when the lighting engine hits a face on this, it's calculating a different brightness to apply and the resulting rendered texture looks completely different.

Bridge model imported into Unity with default normals, rendered by the URP Lit shader differently to the terrain

Object with default normals, rendered by the URP Lit shader differently to the terrain rendered by the URP TerrainLit shader.

Bridge model imported into Unity with corrected normals, rendered by the URP Lit shader the same as terrain

Object with corrected normals, rendered by the URP Lit shader the same as terrain rendered by the URP TerrainLit shader.

In a nutshell, the fix is to make sure the per-face normals of the object all point upwards, the same as with the terrain, and then the results of the URP Lit shader will match up with the results of the URP TerrainLit shader. You're probably reading this post because you've come across this problem yourself and hit Google, so here's what to do:

  1. Open your model up in Blender, and add a new plane to the scene via the Add -> Plane menu while in Object Mode.
  2. Select the plane, switch to Edit Mode, and copy its normal vectors via the Mesh -> Normals -> Copy Vectors menu. The plane by default points upwards and just has upward pointing normals, the same as the terrain in Unity.
  3. Switch back to Object Mode and select your original mesh object, then switch back to Edit Mode and select all of the faces that need to look the same as your terrain. The easiest way to do this is by tapping the Face Select selection mode, which is an icon next to the mode selection drop-field that looks like a cube with a highlighted front face. With this mode enabled, just tap the first relevant face on your mesh and then hold shift while tapping the rest.
  4. At this point it's worth checking that you can see the existing normals. In the top right of the of the editor view there's an Overlays icon which looks like two overlapping circles and a drop-arrow. Tap the drop-arrow on this and at the very bottom, under Normals, make sure the middle icon, Display Split Normals, is selected and that the size is set to something reasonable, like 1. This should show a series of lines pointing out from the corners of your objects faces.
  5. With the appropriate faces selected and the per-face normals visible, paste the vectors you copied earlier via the Mesh -> Normals -> Paste Vectors menu. You should see the lines switch to pointing upwards.
  6. Back to Object Mode, delete the plane that these vectors were copied from, and export to a new model for importing in to Unity.
  7. In Unity, re-import the object and make sure the appropriate material is swapped out for your Unity material. I'm assuming you've already created a material before looking for this tutorial.
  8. Finally, select the relevant mesh part in the prefab from the hierarchy panel and unfold the material properties. Make sure the Shader is set to URP Lit, assuming your terrain is URP TerrainLit, that the Base Map colour is white, and that both the Specular Highlights and Environment Reflections are enabled in the Advanced Options.

These options combined with the upward pointing per-face normals should mean that the objects material now renders the same as the terrains.

Photo of Ric

Ric

Ric is a senior web and game programmer with nearly 30 years industry experience and countless programming languages in his skillset. He's worked for and with a number of design and development agencies, and is the proprietor of QWeb Ltd. Ric is also a Linux server technician and an advocate of free, open-source technologies. He can be found on Mastodon where he often posts about the projects he's working on both for and outside of QWeb Ltd, or you can follow and support his indie game project on Kofi. Ric also maintains our Github page of useful scripts.

Blog posts are written by individuals and do not necessarily depict the opinions or beliefs of QWeb Ltd or its current employees. Any information provided here might be biased or subjective, and might become out of date.

Discuss this post

Nobody has commented yet.

Leave a comment

Your email address is used to notify you of new comments to this thread, and also to pull your Gravatar image. Your name, email address, and message are stored as encrypted text. You won't be added to any mailing list, and your details won't be shared with any third party.

This site is protected by reCAPTCHA and the Google Privacy Policy & Terms of Service apply.