原文所在:https://catlikecoding.com/unity/tutorials/hex-map/part-25/
机翻+个人润色
- 显示原始舆图数据
- 演化细胞气候
- 创建部分水循环模拟
这是六边形舆图系列教程的第25章。上一个章节是地区和侵蚀。这次我们将给大陆增加水分。
使用水循环确定生物群落。
1.云
到现在为止,我们的舆图生成算法只调解单位格的高度。单位格之间最大的区别是它们是否被淹没。虽然我们也设置了差别的地形类型,但这只是一个简单的对差别高度的可视化。分配地形类型的更好方法是考虑当地气候。
地球的气候是一个非常复杂的系统。幸运的是,我们不需要创建一个真实的气候模拟。我们需要的只是看起来足够自然的东西。气候最重要的方面是水循环,因为动植物需要液态水才能生存。温度也很重要,但这次我们将重点关注水,始终保持全球温度稳定,同时改变湿度。
水循环形貌了水是如安在情况中流动的。简而言之,水体蒸发,形成云,云产生雨,雨又流回水体。它的作用远不止于此,但模拟这些步调大概已经足够在我们的舆图上产生一个看似自然的水分布。
1.1可视化数据
在我们举行实际模拟之前,如果我们可以或许直接看到相关的数据,将是非常有用的。为此,我们将调解我们的地形着色器。给它一个切换属性,这样我们可以将它切换到数据可视化模式,显示原始舆图数据,而不是通常的地形纹理。这是通过一个带有toggle属性的浮动属性来完成的,该属性指定了一个关键字。这将使它作为一个复选框显示在material inspector中,它控制是否设置了关键字。属性的实际名称无关告急,重要的是关键字,我们将使用SHOW_MAP_DATA作为关键词。
- Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Terrain Texture Array", 2DArray) = "white" {} _GridTex ("Grid Texture", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Specular ("Specular", Color) = (0.2, 0.2, 0.2) _BackgroundColor ("Background Color", Color) = (0,0,0) [Toggle(SHOW_MAP_DATA)] _ShowMapData ("Show Map Data", Float) = 0 }
复制代码
添加一个着色器特性来支持关键字。
- #pragma multi_compile _ GRID_ON #pragma multi_compile _ HEX_MAP_EDIT_MODE #pragma shader_feature SHOW_MAP_DATA
复制代码 我们将显示一个浮动值,就像其他地形数据一样。要实现这一点,请在界说关键字时向Input布局体添加mapData字段。
- struct Input { float4 color : COLOR; float3 worldPos; float3 terrain; float4 visibility; #if defined(SHOW_MAP_DATA) float mapData; #endif };
复制代码 在顶点步伐中,我们将使用单位数据的Z通道来填充mapData,像往常一样在单位之间举行插值。
- void vert (inout appdata_full v, out Input data) { … #if defined(SHOW_MAP_DATA) data.mapData = cell0.z * v.color.x + cell1.z * v.color.y + cell2.z * v.color.z; #endif }
复制代码 当需要显示舆图数据时,直接使用它作为片断着色器的albedo,而不是平常的颜色。将它与网格相乘,以便在可视化数据时仍然可以启用网格。
- void surf (Input IN, inout SurfaceOutputStandardSpecular o) { … o.Albedo = c.rgb * grid * _Color * explored; #if defined(SHOW_MAP_DATA) o.Albedo = IN.mapData * grid; #endif … }
复制代码 为了真正地将一些数据放到着色器中,我们必须向hexcellent shaderdata中添加一个方法,把一些东西放到它的纹理数据的蓝色通道中。这个数据是一个独立的浮点值,固定在0-1范围内。
- public void SetMapData (HexCell cell, float data) { cellTextureData[cell.Index].b = data < 0f ? (byte)0 : (data < 1f ? (byte)(data * 255f) : (byte)255); enabled = true; }
复制代码 然而,这种方法干扰了我们的探索系统。数据组件的蓝色值o和255用于指示单位格的可见性是否正在转换。为了保持这个工作,我们必须使用字节值254作为最大值。请注意,单位移动将清除舆图数据,但这没关系,因为我们只在调试舆图生成时使用它。
- cellTextureData[cell.Index].b = data < 0f ? (byte)0 : (data < 1f ? (byte)(data * 254f) : (byte)254);
复制代码 向HexCell添加一个同名的方法,它将请求通报它的着色器数据。
- public void SetMapData (float data) { ShaderData.SetMapData(this, data); }
复制代码 要测试这是否工作,请调解HexMapGenerator.SetTerrainType 用于设置每个单位格的舆图数据。让我们设想一下将海拔,从整数转换为0-1范围内的浮点数。这是通过从单位格的高度中减去高度的最小值,然后除以高度的最大值减去最小值。确保它是一个浮动分区。
- void SetTerrainType () { for (int i = 0; i < cellCount; i++) { … cell.SetMapData( (cell.Elevation - elevationMinimum) / (float)(elevationMaximum - elevationMinimum) ); } }
复制代码 通过切换地形材质资产的Show Map data复选框,您现在应该可以或许在平常地形和数据可视化之间举行切换。
舆图种子1208905299,正常地形和高度可视化。
1.2创建气候
为了模拟气候,我们必须跟踪气候数据。由于我们的舆图由离散的单位组成,每个单位都有自己的当地气候。创建一个包含所有相关数据的ClimateData布局体。虽然我们可以将这些数据添加到单位格本身,但我们只会在生成舆图时使用它。我们将分别存储它。这意味着我们可以在HexMapGenerator中界说这个布局,就像MapRegion一样。我们将从只跟踪云开始,这可以通过一个单独的浮点数来实现。
- struct ClimateData { public float clouds; }
复制代码 添加一个列表来跟踪所有单位的气候数据。
- List climate = new List();
复制代码 现在我们需要一个方法来创建舆图的气候。它应该从清理气候列表开始,然后为每个单位添加一个项目。最初的气候数据是零,这是我们通过默认的数据布局ClimateData得到的。
- void CreateClimate () { climate.Clear(); ClimateData initialData = new ClimateData(); for (int i = 0; i < cellCount; i++) { climate.Add(initialData); } }
复制代码 把气候的创建放在土地被侵蚀之后,在地形类型确定之前。在现实中,侵蚀主要是由氛围和水的运动造成的,这是气候的一部分,但我们不计划模拟它。
- public void GenerateMap (int x, int z) { … CreateRegions(); CreateLand(); ErodeLand(); CreateClimate(); SetTerrainType(); … }
复制代码 修改 SetTerrainType,这样我们可以看到云的数据,而不是单位格的高度。一开始,它看起来像一张玄色的舆图。
- void SetTerrainType () { for (int i = 0; i < cellCount; i++) { … cell.SetMapData(climate[i].clouds); } }
复制代码 1.3变化的气候
我们气候模拟的第一步是蒸发。应该蒸发多少水?我们用滑块来控制它。0体现完全没有蒸发,1体现最大蒸发。我们将使用0.5作为默认值。
- [Range(0f, 1f)] public float evaporation = 0.5f;
复制代码
蒸发的滑动条
让我们专门创建另一个方法来发展单个细胞的气候变化。将单位的索引作为参数,并使用它来检索相关单位及其气候数据。如果细胞在水下,那么我们要处理处罚的是一个会蒸发的水体。我们将立即将水蒸气转化为云——忽略露点和冷凝——因此直接将蒸发添加到单位的云的值中。一旦我们完成了,将气候数据复制回列表。
- void EvolveClimate (int cellIndex) { HexCell cell = grid.GetCell(cellIndex); ClimateData cellClimate = climate[cellIndex]; if (cell.IsUnderwater) { cellClimate.clouds += evaporation; } climate[cellIndex] = cellClimate; }
复制代码 在CreateClimate中的每一个单位调用这个方法。
- void CreateClimate () { … for (int i = 0; i < cellCount; i++) { EvolveClimate(i); } }
复制代码 只做一次是不敷的。为了创建一个复杂的模拟,我们必须多次发展单位的气候。我们这样做做的越多,效果就越精致。我们取一个固定的量,用40个循环。
- for (int cycle = 0; cycle < 40; cycle++) { for (int i = 0; i < cellCount; i++) { EvolveClimate(i); } }
复制代码 因为现在我们只增加了被淹没的单位上方的云层,我们最终得到的是玄色的陆地和白色的水体。
蒸发了海水的效果
1.4云的流传
云不会永远停留在一个地方,尤其是当越来越多的水不停蒸发的时候。气压差导致氛围移动,体现为风,风又使云移动。
如果没有一个主导的风向,均匀来说,单位的云会均匀地分散到各个方向,最终到达单位的邻人。在下一个循环中将生成新的云,让我们将单位中当前的所有云分布到它的邻人中。所以每一个邻人得到细胞云的六分之一,之后局部云降为零。
- if (cell.IsUnderwater) { cellClimate.clouds += evaporation; } float cloudDispersal = cellClimate.clouds * (1f / 6f); cellClimate.clouds = 0f; climate[cellIndex] = cellClimate;
复制代码 要实际地将云添加到邻人中,需要对它们举行循环,检索它们的气候数据,增加它们的云值,并将其复制回列表中。
[code] float cloudDispersal = cellClimate.clouds * (1f / 6f); for (HexDirection d = HexDirection.NE; d |