Unity地形动态生成的一些经验记录

        正经人谁写日记啊
                                                --汪涵

        我以前也一直认为一个正经忙碌的人哪有时间和精力去写日记呢?

        但是我错了,正因为忙绿没有精力,才要将重要的事情记录下来.

        我记录的东西也都是一些比较简单的Unity使用,C#程序设计的浅显的东西.

        但是谁知道呢,当我不停地探索之后,发出来的内容,也许对于那些刚入门的新人来说,会是一个引导.

关于地形(terrain)对象

        有四个重要参数,地形宽度,地形长度和地形高度,以及高度图分辨率.

        前面三个都好理解,主要是最后一个高度图分辨率,它是一个二维的平面图,用灰色深度来代表地形高低起伏的一个图.(最黑为0)

        它不直观展示出来,它只在程序计算地形高度时起作用,这就比较抽象.

        并且高度图的分辨率和地形分辨率(长*宽)可以不是一样的.

        更低的分辨率在生成地形高度时会变得更陡峭.(这不是创建陡峭地形的方法,这种只会影响玩家的游戏体验.)

        一个1024*1024的地形图如果配上513*513的高度图那么整个地形的起伏会非常平滑

扫描二维码关注公众号,回复: 14990084 查看本文章

但是一个10240*10240的地形图,如果配上513*513的高度图,那么整个地形的起伏会变得非常突兀.

因为每个高度图的像素点,将要承担10倍于1024*1024地形图的起伏.

   

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;
        //计算高度图分辨率
    }

    所以在创建高度图分辨率时应该和地形宽度和长度进行一个动态改变.

将长度和宽度相加,并且除以2,已达到清晰度和兼顾配置的需求.

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

         Unity的任何游戏对象,都有2种坐标系,一个是世界坐标系,一个是本地坐标系.如果是3D游戏就是三维坐标系,如果是2D游戏就是二维坐标系.

        三维坐标系实在是让人头大,它是计算机图形学的一个基础概念.在镜头控制系统中是最绕人的,在地形创建中只需要关注他的世界坐标系即可.

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

        Vector3类型是一个三维向量结构体,将要创建的地形的长宽高赋值给它,并在后面将值赋值给创建出的地形对象.

        TerrainData是Terrain游戏对象里面的数据对象,通过terrainData对象你才能控制terrain的具体数据.(长宽高,位置等等.)

        而且还可以在游戏中实时控制地形.

        

 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;

        紧跟着就是创建地形方法.CreateTerrainGameObject();

        创建出的只是一个地形对象,如果不给他设置任何的数值,它将是一个默认的大小和位置.

        生成地形时是从地形的左下角作为(0,0)的,所以1024*1024的地形将会在右上角处达到(1024,1024)坐标.

        所以我还加了一个移动地形对象的操作,将地形中心点对准(0,0)

        方法就是将地形大小除以2,之后对先有地形的三维坐标进行更改.

地形对象的坐标系和其大小始终是一样的,当地形变大时,他的坐标系也会跟着变大,所以直接减去地形的长宽的一半,并将新的位置赋值给地形对象的.transform.position,就可以实现将地形中心点移动到世界坐标系的(0,0)了.

关于地形贴图(TerrainTexture2D)

        地形贴图支持多贴图功能,因此只能使用数组形式来进行赋值,哪怕只有一个贴图

        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;
        }

           上面代码接着地形创建.
        声明一个TerrainLayer[]类型的数组,这个数组就是贴图图层数组,他存储了所有的预先在unity界面里导入的贴图.
        我这里导入了4张贴图

                         

        所以会循环4次,依次新建地形贴图图层对象,将贴图赋予地形散射(漫反射) 纹理.

        设置贴图大小,设置地形层的纹理平铺偏移量,设置地形层散射纹理的包装模式为“重复”。这意味着当纹理在地形表面被平铺时,如果纹理范围超过地形表面,纹理将在边界处重复,从而覆盖整个地形表面,将地形层的法线贴图设置为 null,设置地形层的法线缩放值,设置地形层的光滑度,设置地形层的金属度.

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);

         接下来进行贴图混合,I,J的最大值分别取地形高度图分辨率,perlinThreshold在前面变量声明时已经定义了一个固定的值,这里就是将4个贴图按25%的比例进行权重分配.

        循环内部利用柏林噪声和随机值偏移量来控制,柏林噪声生成的数值,使用随机值偏移量可以实现种子功能,在同一种子下,柏林噪声生成的噪声形态将会一样.

        

        对于贴图交界处,unity会自动混合平滑过渡,不需要另行编写代码.

        最后,设置阿尔法maps将这个三维数组赋值给刚刚创建的地形上.

关于地形高度图(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;
    }

上述代码定义了一个从地图最中心点开始,根据输入的园的半径来获得一个高区平台,并且在平台边缘会平滑过渡到地形的低区.

        利用平方距离和勾股定理来确定当前的(i,j)点对于中心点的距离,t系数是一个归一化数值,负责平滑过渡插值,而平滑过渡的宽度区域,则是用transitionWidth的系数0.2f来决定的,越大的系数将会让过渡区域越宽,因为使用的是整个地形长宽的最大值,所以基本上在0.5f时就已经能覆盖整个地形.

根据实际情况来设置.

        下图为平台高度60,地区高度10,过渡区宽度系数0.2f,所生成的地形.符合预期.

猜你喜欢

转载自blog.csdn.net/weixin_45643107/article/details/129782537