Some experience records of Unity terrain dynamic generation

        Who is a serious person who keeps a diary
                                                --Wang Han

        I used to think that how could a serious and busy person have the time and energy to write a diary?

        But I was wrong, it is because I am busy and have no energy that I have to record important things.

        The things I recorded are also some relatively simple use of Unity, and the simple things of C# programming.

        But who knows, when I keep exploring, the content I send out may be a guide for those newcomers who are just getting started.

About Terrain Objects

        There are four important parameters, terrain width, terrain length and terrain height, and heightmap resolution.

        The first three are easy to understand, mainly the resolution of the last height map, which is a two-dimensional plane map, using gray depth to represent a map of terrain ups and downs. (The darkest is 0)

        It is not shown visually, it only works when the program calculates the height of the terrain, which is more abstract.

        And the resolution of the height map and the terrain resolution (length * width) may not be the same.

        Lower resolutions will generate steeper terrain heights. (This is not the way to create steep terrain, this will only affect the player's gaming experience.)

        If a 1024*1024 terrain map is matched with a 513*513 height map, the ups and downs of the entire terrain will be very smooth

But if a 10240*10240 terrain map is matched with a 513*513 height map, the ups and downs of the entire terrain will become very abrupt.

Because each pixel of the height map will bear 10 times the ups and downs of the 1024*1024 terrain map.

   

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class CreateTerrain : MonoBehaviour
{
    public int seed;
    public float terrainWidth = 1024f;//地形的宽度
    public float terrainLength = 1024f;//地形的长度
    public int terrainHeight = 50;//地形的高度

    public Texture2D[] terrainTexture;
    public float perlinScale = 10.0f;
    public float[] perlinThreshold = {0.25f, 0.5f, 0.75f };

    private float randomOffsetX;
    private float randomOffsetY;
    public float centerHeight ;
    public float edgeHeight ;
    [Range(0, 2048)]
    public float slopeRadius;
    int terrainResolution;

    public static float Add(float a, float b)
    {
        float c = a + b;
        return c;
        //计算高度图分辨率
    }

    Therefore, a dynamic change should be made with the terrain width and length when creating the heightmap resolution.

Add the length and width and divide by 2 to meet the requirements of clarity and configuration.

 void Start()
    {
        terrainResolution = ((int)Add(terrainWidth,terrainLength)/2);
        Random.InitState(seed);
        randomOffsetX = Random.Range(0f, 10000f);
        randomOffsetY = Random.Range(0f, 10000f);

         Any game object in Unity has two coordinate systems, one is the world coordinate system and the other is the local coordinate system. If it is a 3D game, it is a three-dimensional coordinate system, and if it is a 2D game, it is a two-dimensional coordinate system.

        The three-dimensional coordinate system is really confusing. It is a basic concept of computer graphics. It is the most entangled in the lens control system. In the terrain creation, you only need to pay attention to his world coordinate system.

 //创建一个简单的平面地形,并将其移动到(0,0,0)坐标
        Vector3 terrainPosition = new Vector3(terrainWidth,terrainHeight,terrainLength);
        TerrainData terrainData = new TerrainData();
        terrainData.heightmapResolution = terrainResolution;
        terrainData.size = terrainPosition;

        The Vector3 type is a three-dimensional vector structure, assign the length, width and height of the terrain to be created to it, and then assign the value to the created terrain object.

        TerrainData is the data object in the Terrain game object. You can control the specific data of the terrain through the terrainData object. (Length, width, height, location, etc.)

        And you can also control the terrain in real time in the game.

        

 GameObject terrainObject = Terrain.CreateTerrainGameObject(terrainData);//创建地形的方法

        float terrainHalfWidth = terrainWidth * 0.5f;
        float terrainHalfLength = terrainLength * 0.5f;

        Vector3 terrainOffset = new Vector3(-terrainHalfWidth, terrainHeight, -terrainHalfLength);
        terrainObject.transform.position += terrainOffset;

        Followed by creating terrain method.CreateTerrainGameObject();

        What is created is just a terrain object, if you don't set any value for it, it will be a default size and position.

        The terrain is generated from the lower left corner of the terrain as (0,0), so the 1024*1024 terrain will reach the coordinates of (1024,1024) at the upper right corner.

        So I also added an operation to move the terrain object to align the center of the terrain with (0,0)

        The method is to divide the terrain size by 2, and then change the 3D coordinates of the existing terrain.

The coordinate system of the terrain object and its size are always the same. When the terrain becomes larger, its coordinate system will also become larger, so directly subtract half of the length and width of the terrain, and assign the new position to the terrain object. transform.position, you can move the terrain center point to (0,0) of the world coordinate system.

About Terrain Texture (TerrainTexture2D)

        Terrain maps support multi-map function, so they can only be assigned in the form of an array, even if there is only one map

        TerrainLayer[] terrainLayers = new TerrainLayer[terrainTexture.Length];//TerrainLayer支持多贴图功能,因此只能使用数组形式来进行赋值,哪怕只有一个贴图.
        for (int i = 0; i < terrainTexture.Length; i++)
        {
            terrainLayers[i] = new TerrainLayer();
            terrainLayers[i].diffuseTexture = terrainTexture[i];
            terrainLayers[i].tileSize = new Vector2(16, 16);
            terrainLayers[i].tileOffset = new Vector2(0, 0);
            terrainLayers[i].diffuseTexture.wrapMode = TextureWrapMode.Repeat;
            terrainLayers[i].normalMapTexture = null;
            terrainLayers[i].normalScale = 0.0f;
            terrainLayers[i].smoothness = 0.0f;
            terrainLayers[i].metallic = 0.0f;
        }

           The above code is followed by terrain creation.
        Declare an array of TerrainLayer[] type, which is an array of texture layers, which stores all the textures imported in the unity interface in advance.
        I imported 4 textures here

                         

        So it will loop 4 times, create new terrain map layer objects in turn, and assign the texture to the terrain scattering (diffuse reflection) texture.

        Set the map size, set the texture tile offset of the terrain layer, and set the wrapping mode of the terrain layer's scattering texture to "repeat". This means that when the texture is tiled on the terrain surface, if the texture extent exceeds the terrain surface, the texture will repeat at the border, thus covering the entire terrain surface, set the normal map of the terrain layer to null,set , set the smoothness of the terrain layer, and set the metalness of the terrain layer.

terrainData.alphamapResolution = terrainResolution;
        terrainData.terrainLayers = terrainLayers;
        float[,,] splatmapData = new float[terrainData.alphamapResolution, terrainData.alphamapResolution,terrainTexture.Length];

        for (int i = 0; i < terrainData.alphamapResolution; i++)
        {
            for (int j = 0; j < terrainData.alphamapResolution; j++)
            {
                float perlinValue = Mathf.PerlinNoise((float)(i+randomOffsetX) / terrainData.alphamapResolution * perlinScale, (float)(j+randomOffsetY) / terrainData.alphamapResolution * perlinScale);
                if (perlinValue <= perlinThreshold[0])
                {
                    splatmapData[i, j, 0] = 1;
                    splatmapData[i, j, 1] = 0;
                    splatmapData[i, j, 2] = 0;
                    splatmapData[i, j, 3] = 0;
                }
                else if(perlinValue <= perlinThreshold[1])
                {
                    splatmapData[i, j, 0] = 0;
                    splatmapData[i, j, 1] = 1;
                    splatmapData[i, j, 2] = 0;
                    splatmapData[i, j, 3] = 0;
                }
                else if (perlinValue <= perlinThreshold[2])
                {
                    splatmapData[i, j, 0] = 0;
                    splatmapData[i, j, 1] = 0;
                    splatmapData[i, j, 2] = 1;
                    splatmapData[i, j, 3] = 0;
                }
                else
                {
                    splatmapData[i, j, 0] = 0;
                    splatmapData[i, j, 1] = 0;
                    splatmapData[i, j, 2] = 0;
                    splatmapData[i, j, 3] = 1;
                }
            }
        }
        terrainData.SetAlphamaps(0, 0, splatmapData);

         Next, mix the textures. The maximum values ​​of I and J are respectively the resolution of the terrain height map. PerlinThreshold has defined a fixed value in the previous variable declaration. Here, the weight distribution of the 4 textures is carried out at a ratio of 25%.

        Perlin noise and random value offset are used inside the loop to control the value generated by Perlin noise, and the random value offset can be used to realize the seed function. Under the same seed, the noise shape generated by Perlin noise will be the same.

        

        For texture junctions, unity will automatically blend smooth transitions, no need to write additional code.

        Finally, set the alpha maps to assign this 3D array to the terrain we just created.

About terrain height map (terrainHeightMap)

float[,] circleHeightMap = CreateCircleHeightMap(terrainResolution, centerHeight / terrainHeight, edgeHeight / terrainHeight, slopeRadius);
 terrainData.SetHeights(0, 0, circleHeightMap);//应用高度图

float[,] CreateCircleHeightMap(int resolution, float centerHeight, float edgeHeight, float radius)
    {
        float[,] heightMap = new float[resolution, resolution];
        float halfWidth = terrainWidth * 0.5f;
        float halfLength = terrainLength * 0.5f;
        float maxDistSqr = radius;

        float transitionWidth = Mathf.Min(terrainWidth, terrainLength) * 0.2f;//计算高度过渡区的宽度.
        float transitionEndSqr = maxDistSqr+transitionWidth;//计算高度过渡区结束点.
        float transitionStartSqr = maxDistSqr;


        for (int i = 0; i < resolution; i++)
        {
            for (int j = 0; j < resolution; j++)
            {
                float distX = (i / (float)resolution) * terrainWidth - halfWidth;
                float distY = (j / (float)resolution) * terrainLength - halfLength;
                float distSqr = distX * distX+ distY * distY;
                distSqr = Mathf.Sqrt(distSqr);

                if (distSqr <= transitionStartSqr)
                {
                    heightMap[i, j] = centerHeight;
                }
                else if (distSqr <= transitionEndSqr)
                {
                    float t = (distSqr - transitionStartSqr) / (transitionEndSqr - transitionStartSqr);
                    heightMap[i, j] = Mathf.Lerp(centerHeight, edgeHeight, t);
                }
                else
                {

                    heightMap[i, j] = edgeHeight;
                }
                
            }
        }
        
        return heightMap;
    }

The above code defines a platform that starts from the center of the map and obtains a high platform according to the radius of the input circle, and smoothly transitions to the low zone of the terrain at the edge of the platform.

        Use the square distance and the Pythagorean theorem to determine the distance between the current (i, j) point and the center point, the t coefficient is a normalized value, responsible for smooth transition interpolation, and the smooth transition width area is the coefficient of transitionWidth It is determined by 0.2f, the larger the coefficient will make the transition area wider, because the maximum value of the length and width of the entire terrain is used, so basically the entire terrain can be covered at 0.5f.

Set it according to the actual situation.

        The figure below shows the terrain generated by platform height 60, area height 10, and transition zone width coefficient 0.2f. It meets expectations.

Guess you like

Origin blog.csdn.net/weixin_45643107/article/details/129782537