前言:最近在准备作品集,想吧Blender里面利用shader实现的程序化地形导入Unity中,发现我弄不了,于是转向了直接在Unity中实现程序化地形的新途径,但是网上的教程很少,b站和CSDN好像都没有详细的教程,所以今天我科学上网找了一下,查到了一个16年的程序化视频教学,自带机翻英文的那种,就是按他的翻译根本翻译不对,听也听不太懂,所幸能够理解代码意思,想借这个机会去做一篇程序化地形的教程,先是以博客的形式去记录每次所学,把所学弄懂,再去下一章的学习,最后我会在b站制作一个视频教程,希望大家多多支持,文章是我学习后自己的理解,肯定有些地方不对,不对的地方还请大家多多指教,感兴趣的朋友可以点一波关注,持续更新!
初步直租出一张噪声图,为后续创作打下基础
简单来说程序化地形需要一个高效的伪随机数生成器,随机数包括地形的长、宽、高度、大小,然后利用一个载体把这些数合理的展示出来,大家最常见的其实就是黑白噪声图
以这张图为载体,他的长、宽、大小就是程序化地形的大小,那么有人就会问:地形高度和地形间的间隙是如何表现出来的?这里其实是使用到了噪声的波形来做的
波形图看起来是不是很像山脉的起伏一样?从而我们可以自己定义一个波形图,通过修改波形图的长宽大小,以及噪声的分布来实现一个地形的设计,这样之后噪声图就相当于一个地形编辑器了,我们通过编辑噪声图间接的编辑出地形,进而我们再加上颜色和其他属性,就能为地形增添上更多的细节,以此来达到一个程序化地形的效果。
首先我们需要像我上面所说的一样,定义一个噪声图,让它成为程序化地形的一个载体,为此我们编写一个噪声脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class Noise
{
//定义一个函数生成地图 利用噪声来设置地形的长宽大小等属性
public static float[,] GenerateNoiseMap(int mapWidth,int mapHeight,float scale)
{
//定义一个两个维度的浮点数类型的数组 用于存储长、宽数据
float[,] noiseMap = new float[mapWidth, mapHeight];
//防止地图的大小出现负数
if(scale<=0)
{
scale = 0.0001f;
}
//对传入的函数的长、宽参数进行灰度值赋值 模拟噪声
for(int y =0;y<mapHeight;y++)
{
for(int x=0;x<mapWidth;x++)
{
//除以scale 让影响scale的同时能够影响到地图缩放的大小
float sampleX = x / scale;
float sampleY = y / scale;
//柏林值 Mathf.PerlinNoise可以简单理解为随机获得在这个点的灰度值
float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
//将灰度值返回给这个点
noiseMap[x, y] = perlinValue;
}
}
return noiseMap;
}
}
在这里就会用到我们刚刚写好的noise脚本,用这个脚本来实现对噪声参数传入的控制,用这个脚本来控制地形的生成
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public int mapWidth;
public int mapHeight;
public float noiseScale;
//生成地图
public void GenerateMap()
{
//这里将输入的长、宽、大小代入刚刚定义好的生成噪声的函数中
float[,] noiseMap = Noise.GenerateNoiseMap(mapWidth, mapHeight, noiseScale);
//MapDisplay是用来将噪声转化为地形的脚本 在这里将上面处理后的数据转化为地形
//下一个脚本再具体介绍 这里可以直接写
MapDisplay display = FindObjectOfType<MapDisplay>();
display.DrawNoiseMap(noiseMap);
}
}
将我们所输入的参数得到的噪声图转换为一个地图展现出来
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapDisplay : MonoBehaviour
{
//通过控制plan的Renderer来控制地形的显示
public Renderer textureRender;
//绘制噪声图
public void DrawNoiseMap(float[,] noiseMap)
{
//用来获取数组指定维数中的元素个数 这里分别是长和宽
int width = noiseMap.GetLength(0);
int height = noiseMap.GetLength(1);
Texture2D texture = new Texture2D(width, height);
//Color类型是接收的是一个[0,1]的值,需要用R,G,B,A四个值各自除以255
//这里是定义整张图的取值面积 即要修改颜色点的多少
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);
//将定义的图像纹理进行应用 此前需要进行SetPixels
texture.Apply();
//将应用后的图交给Randerer去展现效果
textureRender.sharedMaterial.mainTexture = texture;
textureRender.transform.localScale = new Vector3(width, 1, height);
}
}
用于实时呈现出更改后的地形的样式
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
//将我们定义好的控件展示出来
[CustomEditor(typeof(MapGenerator))]
public class MapGeneratorEditor : Editor
{
public bool mapGenChange = true;
public override void OnInspectorGUI()
{
MapGenerator mapGen = (MapGenerator)target;
//修改参数不用运行就能看到修改后的效果
if(DrawDefaultInspector())
{
if(mapGenChange)
{
mapGen.GenerateMap();
}
}
//点击按键就能更新
if (GUILayout.Button("Generate"))
{
mapGen.GenerateMap();
}
}
}
首先在3D场景中创建一个Plane,只保留如下两个组件
创建一个材质球,将它的类型修改为Unlit/Texture,并赋值给Plane,如上图所示
再新建一个空物体用来控制地形的生成,挂载脚本以及赋值如图所示
最后我们通过修改Map Genertor脚本中的参数来控制地形的生成
这样一个基本的用于控制地形生成的噪波图就制作好了,是不是很有趣呢?感兴趣的话点个关注,后面持续更新!