第五节 对生成的灰度图进行上色填充


__hakamada_hinata_rou_kyuu_bu_drawn_by_tinker_bell__sample-14a79f10c5c7554cd212171458c5cabc.jpg
上一篇:


我们根据不同灰度进行上色填充,为后面的三维地形构建打好基础
进入mapGenerator脚本,在最后新建结构体:

public struct TerrainType{
    public string name;
    public float height;
    public Color colour;
}

为了让此结构体各项属性能够方便地在inspector面板进行展示调整,我们用到
[system.Serializable]申明在结构体上方。
之前我们知道,对类里面一些基本属性,成员变量进行序列化,用serializefield指令即可
而结构体这些是我们自己构建的较为复杂高级的结构,并不是从MonoBehavior派生出来的,默认不会被unity3d识别为可以序列化的结构,通过添加 [System.Serializable]属性,让unity3d检测并且注册这些自建结构为可以序列化类型
Serializable只可以对 class类,struct结构体,enum枚举,delegate委托,这些比较复杂的类型进行序列化,不可以对常见的属性序列化
简单来说,普通常用的属性,变量用 serializefield,复杂点的结构体,自定义类或者其他代码体用 serialiable申明注册为可序列化对象。
好了,介绍完毕 serializable,我们继续,结构体初建完毕,对应的,我们新增一个数组,记录地形不同区域

public TerrainType[] regions;

保存,进入Map Generator物体,我们可以将TerrianType数组大小size设置为2,即构建2个区域,进行初步测试。
name对应为Water,Land,对应Color为蓝色系,绿色系即可。Height为0.4,1,代表从0-0.4就是水域,0.4-1就是陆地,这样就可以着手和上一节获得的灰度图进行数值匹配输出了
回到MapGenerator脚本,在我们调用的float[,]noiseMap数组后,就可以开始我们喜闻乐见的遍历数据了

for (int y = 0; y < mapHeight; y++){
    for (int x = 0; x < mapWidth; x++)
    {
        float currentHeight = noiseMap[x, y];//取出数组中的值,作为当前高度进行分析
        for (int i = 0; i < regions.Length; i++)
        {
            if (currentHeight <= regions[i].height)
            {
                     break;
            }
        }
    }
}

break之前,我们需要对颜色进行存储,我们参考下MapDisplay脚本,我们之前在这个类里面对noise数组的值做了普通的黑白配色灰度图,那么在MapGenerator里面,我们现在也需要做类似的功能,对数组值进行刚才的2个regions的水域蓝色和陆地绿色上色,代码可以仿照着写:
那么开始仿照灰度图生成方法,①开始构建彩色图尺寸:
在这个for循环外部,增加:

Color[]colourMap =new Color [mapWidth*mapHeight];

接下来我们在循环内部,break之前添加:②遍历所有像素点,填充对应区域色彩

colourMap[y*mapWidth+x]=regions[i].colour;

这样,我们所有的noiseMap数组里的点,通过regions筛选,储存对应色彩到咱们的colourMap数组里了
我们想要生成彩色地图,这就实现了
目前我们在测试阶段,灰度图生成方式还是需要暂时保留着的,所以我们可以重写绘制方法,这里用到enum枚举,新建一个enum

public enum DrawMode {NoiseMap,ColourMap};

枚举的含义就是自己定义的集合,方便管理整理归类一些变量,方法,结构体都行,里面的每个元素,还是它本身的取值范围或者定义规则,相当于自己新建一个大箩筐把需要一起管理的数据类型,各种东西放一起,方便选取。
再定义一个变量来执行枚举功能:

public DrawMode drawMode;

再来到下方,我们 display.DrawNoiseMap(noiseMap);
显示绘制最终地图功能上方,先进行枚举选择判断支分析:

if (drawMode==DrawMode.NoiseMap){
    display.DrawNoiseMap(noiseMap);
    }else if (drawMode ==DrawMode.ColourMap){
        //在绘制灰度图和彩色图功能实现之前,考虑到MapDisplay脚本功能是最后输出显示,
//而我们当前Display脚本却包括了黑白上色功能,
//如果把接下来彩色上色功能也写在这里面,那就有比较完整的材质渲染模块代码了,
//这和脚本名字功能不太相符,可以考虑将功能细分,新建一个材质生成脚本,
//通过generator脚本下达输出对应色彩地图指令,传递给材质生成脚本里面生成对应材质贴图,
//最后传递给display脚本进行渲染输出显示,这样代码结构会更加明晰。
    }

好了,那么我们先新建一个脚本:
TextureGenerator
新建2个方法,对传入的地图信息进行绘制材质,一个是彩色图方法,一个是高度图
这也是一个静态的固定功能实现脚本,不需要进行外部修改(static),同时也不需要被unity3d场景直接调用(不继承mono),

public static class TextureGenerator
{
    public static Texture2D TextureFromColourMap(Color[] colourMap, int width, int height)
    {
        Texture2D texture = new Texture2D(width, height);
        texture.SetPixels(colourMap);
        texture.Apply();//存储地图像素到材质的每个像素里面之后别忘记应用,才能生效;
        return texture;//传出已经存储好了材质信息的材质。
    }
}

接下来是高度图绘制方法:

public static Texture2D TextureFromHeightMap(float[,]heightMap){
//将MapDisplay里面DrawNoiseMap方法相关代码内容剪贴出来,粘贴到这里,
//将材质生成功能彻底拆分 出来, 别忘记对粘贴过来的代码进行一些细节修正
int width =noiseMap.GetLength(0);
int height =noiseMap.GetLength(1);
    Texture2D texture = new Texture2D (width,height);
    Color[[ colourMap = new Color [width*height];
    for (int y=0;y< height;y++{
        for(x=0;x<width;x++){
            colourMap[y*width+x]=Color.Lerp(Color.black,Color.white , noiseMap[x,y]);
            }
        }
        texture.SetPixels (colourMap);
        texture.Apply ();
  1. 对路径进行修正:总共三处noiseMap改成HeightMap
  2. 由于我们TextureGenerator只需要填充计算灰度信息到我们的地图数组中,不需要生成材质,这是display渲染方面的任务,于是删除复制的代码中多余的与Display渲染显示相关代码:
   Texture2D texture = new Texture2D (width,height);
   texture.SetPixels (colourMap);
   texture.Apply ();
   删除这三段
  1. 最后我们的TextureGenerator最后是生成高度图的材质信息,返回相关灰度图数组和对应的数组宽高度信息即可:
return TexutreFromColourMap(colourMap,width,height);

我们回到MapDisplay脚本,对原进行重命名和功能修正:我们现在只负责材质渲染输出功能了:
public void DrawNoiseMap改为 DrawTexture
传入的参数也变为texture变量:(Texture2D texture)
渲染出来的相应的宽度高度也变成对应材质的宽高度:

textureRender.sharedMaterial.mainTexture=texture;
textureRender.transform.localDcale= new Vector3(texutre.width,1,texture.height);

然后回到MapGenerator里面,原来的drawMode为NoiseMap时候,调用的 display.DrawNoiseMap(noiseMap)也要改为 display.DrawTexture(TextureGenerator.TextureFromHeightMap(noiseMap));
传入我们TextureGenerator脚本里对应的材质信息,
然后我们可以补齐我们的drawMode为colourMap时候的代码了:

}else if (drawMode ==DrawMode.ColourMap){
    display.DrawTexture(TextureGenerator.TextureFromColourMap(colourMap,mapWidth,mapHeight));

同样也是传入TextureGenerator脚本里对应的材质信息
保存,unity里面Inspector drawMode选择为NoiseMap或者ColourMap,观察两种地图都已经顺利生成。
仔细观察colourMap渲染两种色彩相接处会比较模糊,地图四周边界色彩也不太美观,会溢出显示另一边的颜色,因为默认渲染填充色彩采用的是bilinear双线性插值渲染,我们改为单点point插值,这样每个像素点会明晰呈方块化一些,改材质循环模式为Clamp(强制拉伸),也会比默认的Repeat(重复模式)填充的边界来的更精准,不会出现地图边界色彩越位现象。
回到TextureGenerator脚本,我们在TextureFromColourMap静态方法中SetPixels前先修改下材质显示模式,增加

texture.filterMode=FilterMode.Point;
texture.wrapMode=TextureWrapMode.Clamp;

保存,再回到unity界面查看,色彩渲染成标准正方块,地图边界也不会出现越界渐变色彩了
我们接下来手动增加regions,最终设置8个区域,依次为Water Deap,Water Shallow,Sand,Grass,Grass 2,Rock,Rock2,Snow,对应Height范围0.3,0.4,0.45,0.55,0.6,0.7,0.9,1,对应颜色为深蓝色,浅蓝色,沙子黄,浅绿,深绿,浅褐色,深褐色,白色。
好了,我们基本的灰度图和彩色图生成功能已经实现,目前存在一个明显的问题就是,如果我在region区域中间新增一个区域,那就比较蛋疼了,得手动修改后推所有的已存在region信息,那么,我们在以后的章节里考虑制作一个自定义编辑器,让region上色和增加区域更加方便快捷
下一篇:

Last modification:August 12, 2020
If you think my article is useful to you, please feel free to appreciate