This article was created for Blender 2.79, Gimp 2.10, Unity 2019 and World Machine build 3019.
This is a workflow tutorial on how to set up a very complex, squared top-down view PBR-terrain for e.g. next-gen mobile games.
Table of Contents
World Machine
First, create a terrain in World Machine and export the following images with the listed resolutions:
- 2033*2033 pixels
- Heights: This image is used as height information for the terrain in Blender
- Heights: This image is used as height information for the terrain in Blender
- 4096*4096 pixels
- Global Colors: This image will be used to add a layer of color variation to the whole terrain.
- Normals: This image has the double resolution of the height and will be used to add some more details to the 3D terrain mesh.
- Vegetation ( or e.g. Sediment ): This image sets the location of the tiled vegetation ( or sediment ) image.
- Erosion: This image marks the eroded areas covered with a tiled erosion image.
- Slope Low: This image sets the locations with a low angle of elevation.
- Slope Medium: This image sets the locations with a medium angle of elevation.
- Slope High: This image sets the locations with a high angle of elevation.
- Riverbed: This image marks the water areas covered with a tiled riverbed image.
- River: This image is used to create the water areas object.
The Riverbed, Vegetation and Erosion grayscale images are combined in one image. As well as the Slope Low, Slope Medium and Slope High images.
If you don't have any idea how to export these images, just download my terrain example and have a look.
Blender
Terrain
- Create a new Grid mesh:
- 3D View -> menu bar -> Add -> Mesh -> Grid
- In the Tool Shelf:
- Set the Subdivisions X and Y to 2033.
- And the Radius to 25 m.
- Enable Generate UVs.
- Set the mesh to Tool Shelf -> Tools -> Shading: Smooth
- Create the mesh surface:
- Add a Displace modifier to the Grid object
- Add a New texture. Click the Texture tab and open the rendered image from World Machine as the heights image. Set the Image Mapping -> Extension: to Extend
- Back in the Modifier tab set the Texture Coordinates to UV
- Set the Strength to for example 500
- Apply the Displace modifier.
- Add a Displace modifier to the Grid object
- Select the Grid mesh and cut the mesh in two pieces ( north and south ) with my CutGridMeshInHalf Python script. ( This is necessary because Blender would crash if you'd try to cut the mesh in 256 pieces at once. )
Save the result in two different Blender files. One file with the north piece and one file with the south piece. - Open the Blender file with the north piece, select the terrain and run my SeparateQuardMeshesFromGridMesh256_North Python script. This may take several minutes. You can see the progress in Blender's System Console window.
When the script is complete Blender will save and quit automatically to perform better. - Open the Blender file with the north pieces again and change the UV map of each squared quad with my CreateProjectionBoundsUVMapOfAllSelected Python script by selecting all meshes in Object mode with an orthographic top-down camera view ( Numpad 5 and Numpad 7 ).
- Apply the rotation and scale of all pieces: 3D view -> menu bar -> Object -> Apply -> Rotation & Scale
- Set the meshes' pivot point to Tool Shelf -> Tools -> Edit -> Set Origin -> Origin to Center of Mass (Surface)
- Export all selected terrain Meshs as NorthTerrain.fbx without any animations etc. selected.
- Repeat step 5. - 9. with the south terrain piece and my SeparateQuardMeshesFromGridMesh256_South Python script. Name the result SouthTerrain.fbx.
River
TODO
Gimp
Use my ExportImagesPNG Python script to automate the export of all images' pieces: Add it or its folder as Gimp Plug-in ( menu bar -> Edit -> Folders -> Plug-ins ) and restart Gimp.
To cut all the rendered 4k images from World Machine in Gimp to a size of 256*256 pixels use the Guides4096to256 project blueprint with guides.
- Open the blueprint project with guides, delete the Separator layer and copy & paste the Global Color image as a layer.
- Save the image as GlobalColors.xcf in a folder also called GlobalColors, to name all its pieces correct ( and automatically set the save-location ), and cut the image in pieces at the guides with menu bar -> Image -> Transform -> Guillotine
- In a final step export all the 256 pieces as PNG files to the folder where the project GlobalColors.xcf was saved:
- Close all images ( projects ) in Gimp that were not generated by the Guillotine command. Like for example the project GlobalColors.xcf. ( Use the key Pos1 to switch back to the first image in Gimp. )
- Then use menu bar -> Image -> ExportImagesAsPNG to export all open images as PNG-files.
- Quit Gimp when the export has finished.
Repeat these four steps for all of the remaining images exported from World Machine:
- Normals
- RiverbedVegetationErosion
- SlopeHighMedLow
Unity
Create a new LWRP Unity project and copy the two 3D terrain files in a folder called Assets -> Terrain.
Copy all the folders with the cut image pieces into a folder called Assets -> Resources.
Texture Import Settings
Click here to download all seven import presets mentioned in this and the next chapter. Import this Unity Package with menu bar -> Assets -> Import Package -> Custom Package...
First, prepare the exported images from World Machine.
Use my ColorsClamp.preset to import all images in the GlobalColors folder. ( And set the Max Size to 256 and the Format to RGB 24 bit. )
Use my NormalsClamp.preset to import all images in the Normals folder. ( And set the Max Size to 256 and the Format to RGBA 32 bit. )
Use my Locations.preset to import all images in the RiverbedVegetationErosion and SlopeHighMedLow folder. ( And set the Max Size to 256 and the Format to RGB 24 bit. )
Secondly, get and export some tiled PBR terrain images with a resolution of 10242 ( for better quality ) pixels. You can get this kind of images from e.g. Substance Share for free. Make sure that the normals are encoded R=X+, G=Y+, B=Z+. That's OpenGL, in DirectX the Y value is inverted. ( Gimp -> menu bar -> Colors -> Components -> De/Compose... )
You need their
- Color
- Normal
- Smoothness
- ( Ambient ) Occlusion
information in for example PNG-files.
Use my ColorsRepeat.preset to import all Color images. ( And set the Max Size to 256 and the Format to RGB 24 bit. )
Use my NormalsRepeat.preset to import all Normal images. ( And set the Max Size to 256 and the Format to RGBA 32 bit. )
Use my Singles.preset to import all Smoothness and ( Ambient ) Occlusion images. ( And set the Max Size to 256 and the Format to R 8. )
Terrain Pieces
- Select the two 3D terrain files and use my Terrain.preset to import all terrain pieces.
- Drag and drop both files from the Project window to the Hierarchy window.
- Select the two 3D terrain files in the Hierarchy and right click -> Unpack Prefab
- Create an empty GameObject and name it TerrainGroup. Set its position to 0, 0, 0. Flag it as Static.
- Drag and drop all SeparatedQuad_xxx_yyy terrain pieces into the TerrainGroup. Starting with SeparatedQuad_00_00 to SeparatedQuad_15_15.
- Delete the two empty GameObjects left from the two prefabs.
Texture Arrays
Locations Textures Arrays
- Create a folder Assets -> Resources -> LocationsTexturesArrays
- Attach the TerrainLocationsTexturesArrays script to your TerrainGroup gameObject.
- Click the Play button to generate the texture arrays. Leave play mode when the generation is done. ( Right after the Game window shows your scene. )
- Delete the TerrainLocationsTexturesArrays script from your TerrainGroup gameObject.
Tiled Textures Arrays
- Create a folder Assets -> Resources -> TiledTexturesArrays
- Attach the TerrainTiledTextureArrays script to your TerrainGroup gameObject.
- Drag & drop all your textures to the proper variables in the Inspector of the TerrainTiledTextureArrays script.
- Click the Play button to generate the texture arrays. Leave play mode when the generation is done.
( Right after the Game window shows your scene. ) - Delete the TerrainTiledTextureArrays script from your TerrainGroup gameObject.
Generate Materials
Click here to download the LWRP terrain shader. Import this Unity Package with menu bar -> Assets -> Import Package -> Custom Package...
- Add the TerrainMaterialsWithTexturesCreator script to your TerrainGroup gameObject, make sure you have an Assets -> Materials folder.
- Drag & drop your Colors, Normals, Smoothness and Occlusion texture arrays in the script's Inspector.
- Hit the Play button.
- In play mode, copy the TerrainGroup gameObject in the clipboard and leave play mode.
- Paste the new TerrainGroup gameObject from the clipboard to the Hierarchy window and delete the original TerrainGroup gameObject.
- Delete the TerrainMaterialsWithTexturesCreator script from your TerrainGroup gameObject.
Terrain Details
Add additional terrain objects to the terrain pieces as children like for example trees or rocks.
TODO
Bake Lightmaps
Bake the shadows and ambient occlusion to the terrain pieces.
TODO
Generate Prefabs
These prefabs will be loaded automatically at runtime whenever the camera gets close.
- Add the TerrainPrefabsCreator script to your project.
- Create an Assets -> Resources -> TerrainPrefabs folder.
- To create prefabs of all your terrain pieces use menu bar -> Terrain -> Create Prefabs of pieces
- Delete all child gameObjects with the terrain meshes from your TerrainGroup gameObject.
Generate Collision Detectors
These detectors will trigger the terrain prefab loading.
- Create a new layer: Project Settings -> Tags and Layers -> Layers called TerrainDetection
- Add the TerrainCollisionDetectorCreator script to your TerrainGroup gameObject.
- Hit the Play button.
- In play mode, copy the TerrainGroup gameObject in the clipboard and leave play mode.
- Paste the TerrainGroup in the clipboard to the Hierarchy window at position 0, 0, 0 and delete the original TerrainGroup gameObject.
- Delete the TerrainCollisionDetectorCreator script from your TerrainGroup gameObject.
Collision Detector Camera
Set up the camera to handle the un-/loading of your terrain.
- Rotate the camera to point downwards.
- Add a BoxCollider, with a Size of 500, 300, 750 and a Center at 0, 0, 500, a Rigidbody, with Use Gravity disabled, and the HeavyDataTerrainDetector script to the camera.
- Set the camera's layer to TerrainDetection and it's position to 0, 150, 0.
- In The Project Settings -> Physics -> Layer Collision Matrix set the TerrainDetection to only collide with itself.
- Click play to test the dynamic un-/loading of terrain pieces.
Create a Build
To create a build you need to move or delete all the scripts with Editor functionality into the Assets -> Editor folder. ( All except the HeavyDataTerrainDetector script. )