转载自 Cat Nine 的
Unity实现Minecraft(二)——优化原理 - 知乎 (zhihu.com)(Unity实现Minecraft(一)——原理介绍 - 知乎)
最近专门研究了下程序化生成地形,其中最典型的莫过于Minecraft中的地形生成,于是打算自己实现一下。闲话少说,游戏引擎:(2021.2) 。
①先来简单介绍原理:利用一个二重循环生成一个网格地图,然后对于每一个网格的位置坐标生成一个噪声值(噪声值介于0和1之间),然后乘以一个自己定义的高度值Height,就会得到一个(0,Height)的值;然后再用一个循环从0到Height,每个高度放置方块即可。
注意:我们规定每一个单位的方块叫Block,后面也可以叫它体素(Voxel)。
②在Unity模拟一下过程:
using System.Collections.Generic; using UnityEngine; using System;
namespace Test
{
public class ProceduralTerrain : MonoBehaviour
{
[SerializeField] private float noiseScale = 0.05f;
[SerializeField] private int worldSize = 6;
[SerializeField] private int worldHeight = 10;
[SerializeField] private int waterHeight = 4;
//用于保存每个Block类型的颜色
private Dictionary<t_BlockType, Color> colorDic = new Dictionary<t_BlockType, Color>();<span class="k">private</span> <span class="k">void</span> <span class="n">Awake</span><span class="p">()</span> <span class="p">{</span> <span class="c1">//将Block的材质和颜色一一对应
colorDic.Add(t_BlockType.Grass_Dirt, Color.green);//草方块对应绿色
colorDic.Add(t_BlockType.Dirt, Color.yellow);//泥土对应黄色
colorDic.Add(t_BlockType.Water, Color.blue);//水对应蓝色
colorDic.Add(t_BlockType.Sand, Color.gray);//沙对应灰色
}
private void Start()
{
GenerateTerrainData();<span class="p">}</span> <span class="k">private</span> <span class="n">List</span><span class="p"><</span><span class="n">t_BlockData</span><span class="p">></span> <span class="n">worldBlockData</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">t_BlockData</span><span class="p">>();</span> <span class="c1">//生成地形所需数据,就是求出一个每个坐标的高度值
void GenerateTerrainData()
{
t_BlockType blockType;
for (int i = 0; i < worldSize; i++)
{
for (int j = 0; j < worldSize; j++)
{
float noiseValue = Mathf.PerlinNoise((transform.position.x + i) noiseScale, (transform.position.z + j) noiseScale);
int groundPosition = Mathf.RoundToInt(noiseValue * worldHeight);
for (int y = 0; y < worldHeight; y++)
{
blockType = t_BlockType.Dirt;
if (y > groundPosition)
{
if (y < waterHeight)
{
blockType = t_BlockType.Water;
}
else
{
blockType = t_BlockType.Air;
}<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">y</span> <span class="p">==</span> <span class="n">groundPosition</span> <span class="p">&&</span> <span class="n">y</span> <span class="p"><</span> <span class="n">waterHeight</span><span class="p">)</span> <span class="p">{</span> <span class="n">blockType</span> <span class="p">=</span> <span class="n">t_BlockType</span><span class="p">.</span><span class="n">Sand</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">y</span> <span class="p">==</span> <span class="n">groundPosition</span><span class="p">)</span> <span class="p">{</span> <span class="n">blockType</span> <span class="p">=</span> <span class="n">t_BlockType</span><span class="p">.</span><span class="n">Grass_Dirt</span><span class="p">;</span> <span class="p">}</span> <span class="n">t_BlockData</span> <span class="n">temp</span> <span class="p">=</span> <span class="k">new</span> <span class="n">t_BlockData</span><span class="p">(</span><span class="k">new</span> <span class="n">Vector3</span><span class="p">(</span><span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">x</span> <span class="p">+</span> <span class="n">i</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">transform</span><span class="p">.</span><span class="n">position</span><span class="p">.</span><span class="n">z</span> <span class="p">+</span> <span class="n">j</span><span class="p">),</span> <span class="n">blockType</span><span class="p">);</span> <span class="n">worldBlockData</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">temp</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">//用Gizmos来模拟生成的地形
private void OnDrawGizmos()
{
if (worldBlockData.Count > 0)
{
foreach (var block in worldBlockData)
{
if (block.blockType == t_BlockType.Air)
{
continue;
}
Gizmos.color = colorDic[block.blockType];
Gizmos.DrawCube(block.blockPosition,new Vector3(1f,1f,1f));
}
}<span class="p">}</span> <span class="p">}</span> <span class="k">internal</span> <span class="k">class</span> <span class="nc">t_BlockData</span> <span class="p">{</span> <span class="k">public</span> <span class="n">Vector3</span> <span class="n">blockPosition</span><span class="p">;</span> <span class="k">public</span> <span class="n">t_BlockType</span> <span class="n">blockType</span><span class="p">;</span> <span class="k">public</span> <span class="n">t_BlockData</span><span class="p">(</span><span class="n">Vector3</span> <span class="n">position</span><span class="p">,</span><span class="n">t_BlockType</span> <span class="n">type</span><span class="p">)</span> <span class="p">{</span> <span class="n">blockPosition</span> <span class="p">=</span> <span class="n">position</span><span class="p">;</span> <span class="n">blockType</span> <span class="p">=</span> <span class="n">type</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">internal</span> <span class="k">enum</span> <span class="n">t_BlockType</span> <span class="p">{</span> <span class="n">None</span><span class="p">,</span> <span class="n">Air</span><span class="p">,</span> <span class="n">Grass_Dirt</span><span class="p">,</span> <span class="n">Dirt</span><span class="p">,</span> <span class="n">Sand</span><span class="p">,</span> <span class="n">Water</span> <span class="p">};</span>
}
③总结:这样就简单完成了一个模拟过程,但这样有一个很明显的问题,一旦我们的地形过大就会导致游戏帧率下降,影响游戏体验,所以我们就来讲讲真正的Minecraft地形实现是从哪些方面来进行优化。
接上一篇文章,我们讲述了Minecraft地形生成原理,并在Unity中简单实现了出来。但我们可以看出这样的地形存在很多不必要的Block,我们在Minecraft中根本不会看到那些地面以下的Block,也就是只渲染那些我们玩家能够看到的那些处于地面上的Block,像下面这张图一样:
当然,不渲染不代表它不存在,我们仍然需要保留其数据。也就我们需要维护两个List,一个存放每个Block的数据(包括位置信息,Block类型等),另一个就是存放真正需要被渲染的Block的实体。(为了保证游戏的拓展性,我们需要将数据和渲染相分离,这样也是为了游戏性能更好,详细说明后面也会讲到)对于那些不需要在开始被渲染的Block,我们只是将其数据保存,以便后序需要渲染它时才把它渲染出来。
细心的读者可能会发现,上面那张图片还有一个神奇之处:
地面上的Block也应该是一个完整的方块,为什么它的有些面没了。这是因为游戏为了性能的进一步提升,采用了面优化技术。就是将某些玩家永远看不见的面给剔除,这里简单介绍一下在游戏中如何实现面优化:我们知道在Unity中,一个面由2个三角面构成,这两个三角面又是4个顶点的组合按照左手螺旋的顺序来构成的。所以要想单独渲染一个面,我们就得提供顶点和三角形。所以我们实现Minecraft的地形时采用的动态Mesh生成,而不是把方块做成Prefab,然后实例化。针对不需要渲染的面,我们就不给它提供顶点或者三角面即可。(建议读者可以去了解下Unity中的Mesh)
下篇文章就正式开始制作我们的Minecraft了。