一.地形和小地图的用途
地形,作为游戏中必要的物理环境之一,在游戏中的占比非常高,像原神、GTA5、荒野大镖客等等3A大作,都有地形,小地图也是如此,像PUBG,和平精英,王者荣耀等等,这些游戏也用到了小地图,总之,地形和小地图几乎是一个成熟的游戏中必不可少的一部分,那么,我们可以考虑一下,这些游戏中的地形和小地图都是如何实现的。
先考虑小地图,一种方法是利用RT图实时渲染,需要在场景中创建一个RT相机,这样确实简单,但是对性能的消耗非常大,会造成游戏卡顿,甚至直接死机,还有一种方法就是,把场景中的地图截一张截图,然后把截图赋值到Image上面,等比例计算人物位置,但是这样对截出来的图的精度要求很高,稍有偏差可能就失之毫厘差以千里了,已我们现有的条件实现很困难,所以,我们做一个简易版的小地图,直接把地形的材质赋值给小地图,这样就能实现小地图效果。
地形的实现也比较简单,地形地形,顾名思义得有形状,不能太平,需要有起伏,所以,我们还是需要顶点辅助类的帮忙来绘制地形,绘制出来的地形没有颜色,所以我们就用两种颜色来区分地形高低,话不多说,直接上代码:
using UnityEngine;
using UnityEngine.UI;
public class MeshMap : MonoBehaviour
{
//颜色1
public Color color1=Color.blue;
//颜色2
public Color color2=Color.green;
//地形长度
public int width = 200;
//地形宽度
public int height = 200;
//地形的Texture2D图
public Texture2D texture2D;
//小地图
public Image map;
//玩家在小地图中的位置
public Image playerPos;
//场景中的玩家
public GameObject player;
void Start()
{
//规定地形的Texture2D图大小
texture2D = new Texture2D(width, height);
//创建一个顶点辅助类
VertexHelper vh = new VertexHelper();
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
//柏林噪声,用来实现根据地形高低显示不同颜色值的变化
float y = Mathf.PerlinNoise(i * 0.1f, j * 0.1f);
texture2D.SetPixel(i, j, Color.Lerp(color1, color2, y));
//计算uv坐标
float uvx = (float)i / (float)width;
float uvy = (float)j / (float)height;
//添加顶点
vh.AddVert(new Vector3(i - width / 2, y * 3, j - height / 2), Color.white, new Vector2(uvx, uvy));
//设置顶点顺序
if (i<width-1&&j<height-1)
{
vh.AddTriangle(i * height + j, i * height + j + 1, (i + 1) * height + j + 1);
vh.AddTriangle(i * height + j, (i + 1) * height + j + 1, (i + 1) * height + j);
}
}
}
texture2D.Apply();
//创建一个Mesh类
Mesh mesh = new Mesh();
//把mesh赋值给顶点辅助类
vh.FillMesh(mesh);
//获取网格过滤器
GetComponent<MeshFilter>().mesh = mesh;
//获取网格渲染器
GetComponent<MeshCollider>().sharedMesh = mesh;
//获取网格碰撞器
GetComponent<MeshRenderer>().material.mainTexture = texture2D;
//创建一个Materal类,用来给地形赋材质
Material material = new Material(Shader.Find("UI/Default"));
material.mainTexture = texture2D;
map.material = material;
//根据标签找到场景中的玩家
player = GameObject.FindGameObjectWithTag("Player").gameObject;
}
void Update()
{
//通过控制小地图的中心点来实现小地图移动
map.rectTransform.pivot = new Vector2((width / 2 + player.transform.position.x) / width,
(height / 2 + player.transform.position.z) / height);
//控制小地图上玩家的点的转向
playerPos.transform.localRotation = Quaternion.Euler(0,0, -player.transform.localEulerAngles.y);
}
}
运行效果如下:
这样,我们就实现了地形和小地图效果。
二.合批
我们创建好地形之后,大多数的游戏都是在地形上面创建许多的花草树木,而这些东西都是模型,只要是模型,就是用任意个三角形拼出来的,一个普通的模型可能有上万个三角形,这就是渲染压力,这还只是一个模型,要是场景中有成千上万个模型呢?渲染压力是不是会更大,但是,我们在一开始做游戏的时候,只会考虑实现效果,没时间去优化细节,所以,我们就来了解一下优化的知识。
在unity中,有种优化叫做合批,而我们为什么要进行合批呢?是因为合批(批量渲染)是通过减少CPU向GPU发送渲染命令(DrawCall)的次数,以及减少GPU切换渲染状态的次数,尽量让GPU一次多做一些事情,来提升逻辑线和渲染线的整体效率。但这是建立在GPU相对空闲,而CPU把更多的时间都耗费在渲染命令的提交上时,才有意义,而合批又分为动态合批和静态合批,动态合批与静态合批其本质是对将多次绘制请求,在允许的条件下进行合并处理,减少 CPU 对 GPU 绘制请求的次数,达到提高性能的目的,那怎么开启合批呢,如下如所示:
下面我们来介绍一下动态合批和静态合批:
(1)动态合批:如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行处理(动态批处理是自动完成的),但是要注意四个条件,一是动态批处理仅支持小于900顶点的网格物体,二是如果着色器使用顶点位置,法线和UV值三种属性,只能批处理300顶点以下的物体;如果着色器需要使用顶点位置,法线,UV0,UV1和切向量,只能批处理180顶点以下的物体,三是不能使用缩放尺度(scale),缩放不同的物体不能进行批处理;统一缩放尺度的物体不会与非统一缩放尺度的物体进行批处理,eg:(1,1,1)和(1,2,1)不会批处理,(1,2,1)和(1,3,1)可以进行批处理,四是必须使用相同材质的实例化物体,否则将会导致批处理失败;
(2)静态合批:将static的静态物体(永远不会移动、旋转和缩放) ,如果相同材质球,面数在xx之内。unity会自动合并成一个batch送往GPU处理
这两种合批的缺点就是:动态合批容易被打断,静态合批会造成较大的内存压力,会占用更大的内存,牺牲内存来减小渲染压力,总结来说,动态合批和静态合批的区别,一个是内存给GPU降低压力,一个是拿CPU给GPU降低压力,静态合批一次合批过后不能更改,动态合批在合批之后进行更改。
我们来看下面这张图:
在unity中,Batches叫做批处理,批处理数值越大,渲染的压力越大,游戏的流畅度越低,太大就可能导致游戏卡死,这个值也可以理解为DrawCall值,因为,一次Batch至少包含一次DrawCall,这里又提到了一个新概念:DrawCall。
DrawCall本身的含义其实很简单,就是CPU调用图像应用编程接口,如OpenGL中的glDrawElecments命令或者是DirectX中的DrawIndexedPrimitive命令,来命令GPU进行渲染的操作,这样的一个过程叫做一次DrawCall,那么我们优化的目的就是为了减小DrawCall的次数,所以就用到了上面的批处理(也叫合批)。
最后附加一个合批失败原因总结: