前面说要做个2D曲面,因为一直在找工作没时间,如果大家有机会可以帮小弟推荐推荐,小弟不胜感激。好言归正传了,开始讲今天的东西了,先截个图看看效果。图如下:
第一张是在scence视图下的图,可以很清楚的看到这个地形的分布,第二张则是一个球在场景中的运行图,也就是我们的主角了。如果我们需要制作出一个曲面,毫无疑问的是我们得动态构造网格,然后给我们的网格贴uv。一个曲面是有很多个三角形绘制,所以我们知道如何绘制出一个三角形的话,然后间接的就可以绘制出一个四边形,乃至整个曲面网格了。如果不会绘制一个三角形的可以参考前面的博客了。如果我们先绘制出一个长方形的网格,然后通过变幻把直的变成弯的(如果我们能变魔术就好了,把直的变成弯的)。
我们先把直的绘制出来,上面就是一个方的地形了(逗比刘真是菜,一个方的地形还这样,逗比刘就只会装b了 哈哈)我们发现方的地形我们用了很多点来绘制其实是有必要的,这里便于我们理解,我们发现如果把上面的地形用sin曲线或者cos曲线做的话,那不是就是变成曲线了么。好我们先绘制出直的来,然后演变成曲的。
那么首先需要建一个类了。我们定义其为BKCurveEntity,
public class BKCurveEntity
{
}
我们先写个初始化方法,用来给定我们用什么材质,方形网格的高度,整个网物体的其实位置。
public Vector3 P1;
public Vector3 P2;
public Vector3 P3;
public Vector3 P4;
public Vector3 OriginVector3;
private Material _bodyMeshMaterial;
private Material _terrianMaterial;
private float _factor;
public void IntilizedBkEntity(List<Vector3> keyPoints, Material tm, Material bm, float factor)
{
P1 = keyPoints[0]; P2 = keyPoints[1];
P3 = keyPoints[2]; P4 = keyPoints[3];
_bodyMeshMaterial = bm;
_terrianMaterial = tm;
_factor = factor;
}
p2,p3表示我们绘制网格的左上坐标和右上坐标。这里p1,p4的用处我们先不讲,tm代表网格黑色山体的材质,而bm代表的网格灰色山体上面草的材质了。后面的参数先不讲了。接下来我们添加一个主方法。GameObject GenerateMeshGameObject(Vector3 originPos, float startuv)用来生成一个gameobject。
public GameObject GenerateMeshGameObject(Vector3 originPos, float startuv)
{
var bk = new GameObject("BK");
bk.transform.position = originPos;
OriginVector3 = originPos;
return bk;
}
我们还是先对一些参数进行赋值,比如生成网格他的初始位置,起始uv等信息。然后就是我们需要添加网格的点了,这里我们网格左下到右下的点我们可以根据左上到右上的点来进行计算,所以我们需要先得到左上到右上这之间的点了。
<span style="background-color: rgb(255, 153, 0);">_tempVector3S = new List<Vector3>();</span>
var bk = new GameObject("BK");
OriginVector3 = originPos;
<span style="background-color: rgb(255, 153, 0);"> float right = P3.x;
float width = 8f;
_tempVector3S.Add(P2);
for (float i = P2.x; i < right; )
{
i += 0.5f;
if (i > right)
i = right;
var leftTopPoint = P2+new Vector3(i,0,0);
_tempVector3S.Add(leftTopPoint);
}</span>
这个地方我们采用的是线型采样点。把它存在一个临时数组中。接下来我们就生成山体和山体上面的草了。同时我们同样需要得到山体网格的顶点信息和顶点绘制顺序及顶点的uv。
OriginVector3 = originPos;
<span style="background-color: rgb(255, 153, 0);"> _terrainBodyV = new List<Vector3>();
_terrainBodyT = new List<int>();
_terrainBodyU = new List<Vector2>();
_terrainSurfaceV = new List<Vector3>();
_terrainSurfaceT = new List<int>();
_terrainSurfaceU = new List<Vector2>();
</span>
float right = P3.x;
上面分别定义2种网格所需要的基本信息:网格顶点信息,顶点绘制顺序,顶点uv信息。
for (float i = P2.x; i < right; )
{
i += 0.5f;
if (i > right)
i = right;
var leftTopPoint = P2+new Vector3(i,0,0);
_tempVector3S.Add(leftTopPoint);
}
<span style="background-color: rgb(255, 153, 0);"> for (int i = 0; i < _tempVector3S.Count - 1; i++)
{
_terrainBodyV.Add(_tempVector3S[i]);
_terrainBodyV.Add(new Vector3(_tempVector3S[i].x, 0, 0));
_terrainBodyV.Add(_tempVector3S[i + 1]);
_terrainBodyV.Add(new Vector3(_tempVector3S[i + 1].x, 0, 0));
_terrainSurfaceV.Add(_tempVector3S[i] + new Vector3(0, 0.5f, 0f));
_terrainSurfaceV.Add(_tempVector3S[i] - new Vector3(0, 0.2f, 0f));
_terrainSurfaceV.Add(_tempVector3S[i + 1] + new Vector3(0, 0.5f, 0f));
_terrainSurfaceV.Add(_tempVector3S[i + 1] - new Vector3(0, 0.2f, 0f));
_terrainBodyT.Add(0); _terrainBodyT.Add(3); _terrainBodyT.Add(1);
_terrainBodyT.Add(3); _terrainBodyT.Add(0); _terrainBodyT.Add(2);
_terrainSurfaceT.Add(0); _terrainSurfaceT.Add(3); _terrainSurfaceT.Add(1);
_terrainSurfaceT.Add(3); _terrainSurfaceT.Add(0); _terrainSurfaceT.Add(2);
var terrainBodymesh = new Mesh();
var terrainFaceMesh = new Mesh();
terrainBodymesh.vertices = _terrainBodyV.ToArray();
terrainBodymesh.uv = _terrainBodyU.ToArray();
terrainBodymesh.triangles = _terrainBodyT.ToArray();
terrainFaceMesh.vertices = _terrainSurfaceV.ToArray();
terrainFaceMesh.uv = _terrainSurfaceU.ToArray();
terrainFaceMesh.triangles = _terrainSurfaceT.ToArray();
var ob = new GameObject("meshobject");
var surface = new GameObject("Surface Gameobject");
ob.transform.parent = bk.transform;
ob.transform.localPosition = Vector3.zero;
surface.transform.parent = bk.transform;
surface.transform.localPosition = Vector3.zero;
_terrainBodyV.Clear();
_terrainBodyT.Clear();
_terrainBodyU.Clear();
_terrainSurfaceV.Clear();
_terrainSurfaceT.Clear();
_terrainSurfaceU.Clear();
}</span>
这里我们只是对2种网格的顶点信息和顶点绘制顺序进行了赋值。还没有对他们uv赋值了。这里我们画一张图给大家理解一下。
surface.transform.localPosition = Vector3.zero;
<span style="background-color: rgb(255, 153, 0);"> AddColliderToChildMesh(_tempVector3S[i], _tempVector3S[i + 1], bk.transform);
var ren = ob.AddComponent<MeshRenderer>();
var filter = ob.AddComponent<MeshFilter>();
ren.material = _bodyMeshMaterial;
filter.mesh = terrainBodymesh;
var ren1 = surface.AddComponent<MeshRenderer>();
var filter1 = surface.AddComponent<MeshFilter>();
ren1.material = _terrianMaterial;
filter1.mesh = terrainFaceMesh;</span>
_terrainBodyV.Clear();
这样一个方的网格就出来了。这里我们建一个类测试一下。
public class CreateTerrian : MonoBehaviour
{
}
先定义一个在控制面板上可以拖拽的2个材质。
<span style="background-color: rgb(255, 153, 0);">private BKCurveEntity _bkCurveEntity;
[SerializeField] private Material bm;
[SerializeField] private Material tm;</span>
然后再start方法中我们生成4段方的网格。
[SerializeField] private Material tm;
<span style="background-color: rgb(255, 153, 0);"> private float _length;
private float _originx;
private float _x1, _x2, _x3, _x4;
private List<float> _posFloats;
private List<GameObject> _bkGameObjects;
void Start ()
{
_posFloats=new List<float>();
_bkCurveEntity=new BKCurveEntity();
List<Vector3> points=new List<Vector3>();
_bkGameObjects=new List<GameObject>();
_originx = -10;//起始位置的x值为-10
_length = 0;
_x1 = 0; _x2 = 2; _x3 = 6; _x4 = 5;
for (int i = 0; i < 3; i++)
{
points.Add(new Vector3(-10, _x1, 0));
points.Add(new Vector3(0, _x2, 0));
points.Add(new Vector3(10, _x3, 0));
points.Add(new Vector3(20, _x4, 0));
_bkCurveEntity.IntilizedBkEntity(points, bm, tm, 0.5f);
var ob= _bkCurveEntity.GenerateMeshGameObject(new Vector3(_originx+_length, -5, 0), 0);
_bkGameObjects.Add(ob);
_length= _bkCurveEntity.GetBkLength();
_originx=_bkCurveEntity.OriginVector3.x;
_posFloats.Add(_originx);
_x1 = _bkCurveEntity.P2.y;
_x2 = _bkCurveEntity.P3.y;
_x3 = _bkCurveEntity.P4.y;
_x4 = GetRandomValue(2,8,_x3);
points.Clear();
}
}</span>
这里第二段网格的最后一个点的y值我们用随机函数生成。
private float GetRandomValue(float min, float max, float referce)
{
while (true)
{
float endvalue = Random.Range(min, max);
if (Mathf.Abs(endvalue - referce) > 2)
{
return endvalue;
}
}
}
第二段网格的起始点就是第一段网格的终点。这样就轻松的搞定了一个方形的网格了,那么接下来我们想办法把一个方形的网格变成一个曲的网格了。这里我们可以用最常见的sin和cos函数来试试吧。
for (float i = P2.x; i < right; )
{
i += 0.5f;
if (i > right)
i = right;
<span style="background-color: rgb(255, 153, 0);"> var leftTopPoint = P2+new Vector3(i,Mathf.Sin(i),0);</span><span style="background-color: rgb(255, 204, 102);">
</span> _tempVector3S.Add(leftTopPoint);
}
这就是效果,很显然我觉得很糟糕,因为2段之间没有连接起来,当然想让他们无缝连接起来也很简单。只需保证所有网格的起始点和终点他们的y保持一致就行了。这样虽然保证了网格是曲面,发现整个网格是有规则的没有随机性可言。所以我们只有想别的办法然他保证随机性和曲面了。(逗比刘这都不知道么,我来告诉你吧,用采样曲线啊,这你都不知道啊。)哦 我们可以用采样曲线,首先让我想到的就是贝塞尔曲线,那我们来看看如果想让2条贝塞尔曲线连接起来,2段曲线有什么关系呢?从下图来分析:
private Vector3 CatmullRom(Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float t)
{
Vector3 c0 = P1;
Vector3 c1 = (P2 - P0) * _factor;
Vector3 c2 = (P2 - P1) * 3.0f - (P3 - P1) * _factor - (P2 - P0) * 2.0f * _factor;
Vector3 c3 = (P2 - P1) * -2.0f + (P3 - P1) * _factor + (P2 - P0) * _factor;
Vector3 curvePoint = c3 * t * t * t + c2 * t * t + c1 * t + c0;
return curvePoint;
}
采样曲线前面2个点就相当于我们的控制点,factor为曲线平滑因子范围是[0,1]。 t的范围是[0,1]为0时 该曲线取得p1。为1时该曲线取得p2。这样我们想让我们2段catmull曲线连接起来只需要,第一段曲线的p1,p2,p3等于第二段曲线的p0,p1,p2即可。这样给我们的曲线构建带来很大的方便。这里我们就将他改为该曲线线,效果就如最上面的图了。
for (float i = P2.x; i < right; )
{
i += 0.5f;
if (i > right)
i = right;
<span style="background-color: rgb(255, 153, 0);"> var t = GetCatmullRomT(P1, P2, P3, P4, i);
var leftTopPoint = CatmullRom(P1, P2, P3, P4, t);</span>
_tempVector3S.Add(leftTopPoint);
}
添加方法如下:
private float GetCatmullRomT(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float cur)
{
return 2 * (cur - p2.x) / (p3.x - p1.x);
}
</pre>最后给草地添加碰撞器。<pre name="code" class="plain"> private void AddColliderToChildMesh(Vector3 originVector3, Vector3 endVector3, Transform parent)
{
var ob = new GameObject("Collider");
ob.tag = "Ground";
ob.transform.parent = parent;
var length = Vector3.Distance(originVector3, endVector3);
var midX = (endVector3.x + originVector3.x) / 2f;
var midY = (originVector3.y + endVector3.y) / 2f;
var offset = (endVector3 - originVector3);
var eulerZ = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg;
ob.transform.localPosition = new Vector3(midX, midY, 0f);
ob.transform.rotation = Quaternion.Euler(0, 0, eulerZ);
var col = ob.AddComponent<BoxCollider2D>();
col.offset = new Vector2(0, -0.03f);
col.size = new Vector2(length, 0.06f);
}
这里给点建议,如果我们有时不是很特别想突出表现我们的山体,我们可以给曲线的点让他们分散的稀疏点,给我们的草地上的点添加密一点。如果有纰漏的话,大家可以把这个资源文件下载下来,如果有什么不懂的 可以加qq:185076149,大家可以一起讨论。资源链接:http://pan.baidu.com/s/1mh66kDq