第三节 基于柏林噪音生成灰度图

__original_drawn_by_tinker_bell__4e25619f9082e285de40ce7f80b6a4ab.jpg
上一篇:


柏林噪音介绍octave倍频波形叠加技术讲解


上一节我们提到了Mathf方法中的PerlinNoise函数,这里具体讲解下什么是柏林噪音以及如何通过柏林噪音生成灰度图,为接下来地形生成做准备
柏林噪音示意图.png
普通噪音,类似电视上的雪花点,杂乱随机黑白像素,柏林噪音则是连续的黑白渐变像素表现。
取一段柏林噪音变化横截面切片,对应的数值曲线,类似于高山谷地曲线变化,这可以作为我们的地形构造基础元素。
y轴为振幅(ampitude),x轴为频率(frenquency),波形的基础常识
单条柏林噪音曲线,要构造我们的地形,明显过于平滑,我们需要一些细节
我们通常采用多层柏林噪音曲线叠加,对不同层数进行限定进行局部地形模拟,最后获得比较真实的地形模拟效果,这个方法叫做多层波形倍频Octave技术
这里讲解一个基本的Octave倍频多层波形叠加实现
柏林噪音基本波形叠加.png
三个序列octave波形层,我们构想三块地形部分模拟
傅立叶提出“任何连续周期信号可以由一组适当的正弦曲线组合而成”大家对波形转换滤波方法感兴趣的话可以研究下傅里叶各种转换公式,同时也可以感受下任何自然事物都可以由简单的共通元素叠加模拟出来的哲学思想,这里主要引入两个参数,对我们的柏林噪音波形进行控制
一个是lacunarity孔隙度,对波形频率进行控制,frenquency=lacunarity^0,^1,^2 0次方1次方2次方,对三块地形波动频率进行修饰。孔隙度越大,地形信息越复杂。
比方说孔隙度我们设为2,那么三个octave频率系数依次为1,2,4,那么最右侧的碎石模拟地形波纹将获得最复杂的4倍长度的波形,这对于我们的地形构造基本目标也是符合的,大地形信息少,小细节增加丰富信息。
另一个参数则是Persistence持续度,用来表示每个频率内的振幅,用来修饰控制每一段波形的振幅波动剧烈情况。越小的持续度,波形变化起伏越平缓
ampitude=persistance^0,^1,^2 0次方1次方2次方,对三块地形波动振幅进行修饰。
比方说持续度我们设为0.5,那么三个octave振幅系数依次为,1,0.5,0.25,那么最大的地形起伏将会保持不变,细节碎石地形起伏则会更加平缓,这和我们真实地形体现效果也是比较接近的,地壳运动产生大地形高度变化,风化作用流水侵蚀对局部地形进行细微缓慢地改造。
柏林噪音基本波形叠加最终效果.png
这样我们就可能实现一个基本自然地形模拟效果了,接下来就是代码具体实现过程
下一个目标,生成柏林噪音灰阶高度图
主要代码用到unity3d内置的 Mathf.PerlinNoise(float x , float y);
scripts 文件夹下创建一个新的c#脚本:Noise
脚本内主要用来定义柏林噪音一些参数,并不准备用来挂载应用到unity物体上,可以不需要继承monobehaviour类,
我们也不需要生成各种实例化对象,因此可以定义为静态类,增加static关键词
默认的start和update方法也用不着。
我们主要是构建一个方法,为了生成noise map,需要返回0,1范围内的值,记录我们的灰度值。
创建:

public static float[,] GenerateNoiseMap(int mapWidth, int mapHight){
    float [,] noiseMap = new float[mapWidth,mapHeight];
    for (int y=0; y<mapHeight;y++){
        for(int x =0;x<mapWidth;x++){
            float smapleX =x;
            float sampleY=y;
            }
        }
    }

遍历整个二维数组,将x,y每个像素点的值存储到noisemap数组里。
考虑到每次需要生成不同规模的柏林噪音地图,方法还需要定义一个scale 规模 参数,用来每次传入scale值,随机生成不同规模地图,
追加float scale参数传入到我们的GenerateNoiseMap数组中,同时将样板每个像素点存储的柏林噪音像素控制在对应scale大小里:

float smapleX =x/scale;
float sampleY=y/scale;

为了防止除以零的错误发生,遍历之前做一个条件判断,

if (scale<=0){
scale=0.0001f;
}

接下来,遍历数组中,我们获得sampleX,Y之后,

float perlinValue=Mathf.PerlinNoise (sampleX,sampleY);
noiseMap[x,y]=perlinValue;

这样我们就将地图中所有样本点生成对应柏林噪音像素值,同时存储到我们的noiseMap数组里了
GenerateNoise方法最后我们返回这个数组,柏林噪音生成基本功能就实现完毕了:

return noiseMap;

接下来,我们将构建地图生成功能
新建一个脚本,命名:MapGenerator
这个脚本里,我们将通过对Noise信息加工,最后展现在unity获得柏林地图噪音灰度图。
Hierarchy面板新建一个空物体挂载这个生成器,方便最后展示生成效果
命名MapGenerator,reset位置归零
进入脚本编写:

public int mapWidth;
public int mapHeight;//我们之前提到需要用到间隙度,持续度来控制,这里先搭建最简单的
public float noiseScale;//引入基本的地图长宽属性和尺寸就好,之后再完善
public void GenerateMap(){
     float[,] noiseMap=Noise.GenerateNoiseMap(mapWidth,mapHeight,noiseScale);
    }

生成器脚本主要就是调取咱们Noise生成的noise信息,加工后传递给unity显示出来,那么我们还需要一个显示功能处理脚本
新建一个MapDisplay脚本
将这两个脚本都挂载到Map Generator空物体上
基本思路就是,generator脚本处理noisemap数组里的noise value信息,然后生成黑白像素材质,材质导出到Display脚本,通过Display脚本加工构建对应的三维物体平面,赋予这个noise灰度图材质,展现在unity里面
新建一个plane,去掉Mesh colider组件,别忘记位置归零,我们通过mapDisplay脚本构建新的渲染方法来实现
MapDisplay脚本内开始构建:

public Renderer textureRenderer;
public void DrawNoiseMap(float[,] noiseMap){
    int width =noiseMap.GetLength(0);//获取数组第一维元素长度值,赋值给width;
    int height =noiseMap.GetLength(1);//获取数组第二维元素长度值,赋值给height;

    Texture2D texture = new Texture2D (width,height);//实例化一张空白平面贴图材质

    Color[[ colourMap = new Color [width*height];//创建一张色彩分布图
    for (int y=0;y< height;y++{
        for(int x=0;x<width;x++){
            colourMap[y*width+x]=Color.Lerp(Color.black,Color.white , noiseMap[x,y]);
            //类似之前遍历绘制三角面,这里我们遍历色彩图每个像素点,线性差值转换柏林噪音数值信息为黑白色彩间分布的图像色彩信息。
            }
        }
        texture.SetPixels (colourMap);//拿到色彩图之后别忘记将每个像素点色彩绘制到材质上
        texture.Apply ();//新手最容易忘记的应用我们的材质,才会生效。
    
        textureRender.sharedMaterial.mainTexture = texture;
        textureRender.transform.localDcale = new Vector3(width, 1, height);
    }

我们这就可以回到Generator脚本调用我们Display脚本里面的方法了:
GenerateMap方法里继续编写:

MapDisplay display = FindObjectOfType<MapeDisplay>();
display.DrawNoiseMap(noiseMap);

来到Heirarchy面板,将plane物体挂载到Map Generator物体上Map Display脚本里面Texture Renderer参数上。
为我们的plane新建一个材质,新建文件夹Materials
新建材质球Map Mat,材质球右侧inspector面板修改shader为Unlit/Texture
因为我们需要排除光线反射干扰,这里先删除场景中的默认光源,材质设为无光线自发光材质方便观察。
回到Map Generator物体,我们接下来将要在inspector面板增加编辑生成按钮,用来生成随机柏林噪音地图
所以我们还需要一个脚本,定义自己的编辑器
Assets主目录新建Editor文件夹,里面新建MapGeneratorEditor脚本

Using Unity Editor;

[CustomEditor (typeof (MapGenerator))]

继承monobehaviour改为继承Editor父类
里面进行代码书写:

public override void OnInspectorGUI(){
    MapGenerator mapGen = (MapGenerator)target;
    //修改调用Inspector界面方法,如果检测按钮“Generate”变化,执行生成地图方法
    DrawDefultInspector();
    if(GUILayout.Button("Generate")){
        mapGen.GenerateMap();
        }
    }

回到unity,mapGenerator物体上设置下地图高度10宽度10还有柏林噪音单元尺寸0.3,然后点击生成按钮,应该就可以实现柏林噪音灰度图了
修改高度宽度为100,再次点击Generate应该可以获得更大的地图。
基本功能实现,我们优化一下
比如修改高度宽度后在scene界面直接体现
回到MapGenerator脚本,我们定义一个bool变量:

public bool autoUpdate;

再来到MapGeneratorEditor脚本
原来的

public override void OnInspectorGUI(){
    MapGenerator mapGen = (MapGenerator)target;

    DrawDefultInspector();

修改为

public override void OnInspectorGUI(){
    MapGenerator mapGen = (MapGenerator)target;
    //增加当面板数值发生变化,并且autoUpdate按钮勾选情况下,也执行生成地图方法
    if(DrawDefultInspector(){   //表示Inspector面板发生变化,即修改数值了
        if(mapGen.autoUpdate){  //如果打开autoUpdate判断开关,只要面板修改数值,同样也执行地图生成功能。
            mapGen.GenerateMap();
            }
        }

这样,我们实现柏林噪音获得地图基本功能就实现完毕,这一节到此结束。
下一篇:

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