老规矩先上图
最近又开始继续操刀我的新独立游戏啦,网上看了很多草地的案例受益匪浅。但是嘛最近还是选择用自己的方式去实现。
主要是因为这种方式可以更好的贴合我前后的需求。还有很多有意思的技术点,有时间一点点拿来和大家一起分享吧。
这次先来说说草地的实现方式:
由于草地需要贴合模型表面,所以这里是从附着的模型表面开始的:
一、循环Mesh三角型数值,以三角型为单位记录一组数据,内容为三个顶点位置及其法线信息。
代码如下:
private List<GlassPoint> myData;
private RaycastHit Hit;
private List<GameObject> objGlass;
private void __CreateGlass(MeshFilter myTarget)
{
if (myTarget != null && myTarget.mesh != null)
{
int myLength = myTarget.mesh.triangles.Length;
Vector3[] vector = myTarget.mesh.vertices;
Vector3[] normal = myTarget.mesh.normals;
int[] triangles = myTarget.mesh.triangles;
myData = new List<GlassPoint>();
for (int i = 0; i < myLength; i += 3)
{
//取得索引
int index = triangles[i];
int index2 = triangles[i + 1];
int index3 = triangles[i + 2];
//修改为以第一个点为中心的相对值
Vector3 offset1 = vector[index];
Vector3 offset2 = vector[index2] - offset1;
Vector3 offset3 = vector[index3] - offset1;
//顶点沿法线偏移出去,再随机一定位置后再反射回来找位置
Vector3 startPos = offset1 + (normal[index].normalized*50) + (Vector3.one * UnityEngine.Random.Range(-10.0f, 10.0f));
if (Physics.Raycast(startPos, -normal[index], out Hit, 100))
{
GlassPoint point = new GlassPoint();
//通过射线取第一个点,并算出另外两个点
point.pos = Hit.point;
point.pos2 = point.pos + offset2;
point.pos3 = point.pos + offset3;
//记录法线
point.norm = normal[index];
point.norm2 = normal[index2];
point.norm3 = normal[index3];
myData.Add(point);
}
}
}
}
以上即是每组数据记录了三个点顶及其法线的信息。
值得一提的由于所在位置和法线这不相同,这里用的是顶点沿法线偏移出去,再随机一定位置后再反射回来找位置的方式找到基点。
二、位置和信息找完后,然后就是创建Mesh将每个草的信息写进去,由于数量可能非常多,所以可能需要分开几个Mesh,代码如下:
private void CreateGlassMesh(List<GlassPoint> data)
{
for (int i = 0; i < 100; i++)
{
CreateGlass(i, data);
}
}
private void CreateGlass(int index, List<GlassPoint> myGlassData)
{
int step = 9000;
int startIndex = index * step;
if (startIndex > myGlassData.Count) return;
int endIndex = Mathf.Min(startIndex + step, myGlassData.Count);
Mesh mesh = new Mesh();
List<Vector3> vector = new List<Vector3>();
List<int> triangle = new List<int>();
List<Vector2> uv = new List<Vector2>();
int length = endIndex - startIndex;
for (int i = 0; i < length; i++)
{
SingleGlass(i, ref vector, ref triangle, ref uv, myGlassData[startIndex + i], UnityEngine.Random.Range(0.8f, 1.2f), UnityEngine.Random.Range(0.4f, 1f));
}
mesh.SetVertices(vector);
mesh.SetIndices(triangle.ToArray(), MeshTopology.Triangles, 0);
mesh.uv = uv.ToArray();
GameObject ObjGlass = new GameObject();
ObjGlass.name = "MyGlass" + index;
MeshFilter meshFilter = ObjGlass.AddComponent<MeshFilter>();
MeshRenderer ren = ObjGlass.AddComponent<MeshRenderer>();
meshFilter.mesh = mesh;
ren.material = Com.m_matGlass;
}
这里重点讲每个单草是怎么生成的,如图所示一共四个点顶点型的三个倒三角面,重点关注下UV,上面的X分别为0-0.5和0.5-1的范围:
代码如下:
private void SingleGlass(int index, ref List<Vector3> vert, ref List<int> triangle, ref List<Vector2> uv, GlassPoint data, float w, float h)
{
Vector3 normal = ((data.norm + data.norm2 + data.norm3) / 3).normalized;
Vector3 offset = normal * h;
Vector3 pos4 = (data.pos + data.pos2 + data.pos3) / 3 - normal * 0.3f;//防止因误差出现飞天草
Vector3 pos1 = data.pos + offset;
Vector3 pos2 = data.pos2 + offset;
Vector3 pos3 = data.pos3 + offset;
vert.Add(pos1);
vert.Add(pos2);
vert.Add(pos3);
vert.Add(pos4);
uv.Add(new Vector2(0, 1));
uv.Add(new Vector2(0.5f, 1));
uv.Add(new Vector2(1, 1));
uv.Add(new Vector2(0.5f, 0));
index *= 4;
triangle.Add(index);
triangle.Add(index + 1);
triangle.Add(index + 3);
triangle.Add(index + 1);
triangle.Add(index + 2);
triangle.Add(index + 3);
triangle.Add(index + 2);
triangle.Add(index);
triangle.Add(index + 3);
}
下面是材质部分,使用透明度测试的方式裁剪处草的形状,再用摆动上端UV做出风吹的效果,上代码:
Shader "Custom/Glass" {
Properties{
[Header(Ground_Base)]
_MainTex("RGB:基础色 A:透明通道",2D) = "white"{}
_MainCol("基本色",Color) = (0.5,0.5,0.5,1.0)
_Speed("速度 ",Range(0,10)) = 5
_Scope("幅度 ",Range(0,1)) = 0.3
}
SubShader{
Tags{"RenderType" = "Opaque"}
Pass{
Tags{"LightMode" = "ForwardBase"}
//ZWrite off
Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityCG.cginc"
#pragma target 3.0
//地面参数
uniform sampler2D _MainTex; uniform half4 _MainTex_ST;
uniform half3 _MainCol;
uniform half _Speed;
uniform fixed _Scope;
//输入结构
struct a2v {
float4 vertex: POSITION; //顶点信息
float2 uv0: TEXCOORD0; //UV信息
};
//输出结构
struct v2f {
float4 pos:SV_POSITION; //屏幕定点位置
float2 uv0:TEXCOORD0; //UV
};
v2f vert(a2v v) {
v2f o; //新输出结构
o.pos = UnityObjectToClipPos(v.vertex); //顶点位置 OS>CS
o.uv0 = v.uv0 *_MainTex_ST.xy + _MainTex_ST.zw; //传弟UV
o.uv0.x = o.uv0.x + (sin(_Time.x*_Speed)*_Scope * o.uv0.y); //摆动
return o;
}
float4 frag(v2f i) :SV_TARGET{
//纹理采样
half4 var_MainTex = tex2D(_MainTex, i.uv0);
//裁剪
clip(var_MainTex.a - 0.1);
//最终混合
half3 finalRGB = var_MainTex * _MainCol;
return half4(finalRGB ,1);
}
ENDCG
}
草地的贴图大概是这样:
中间有个间隔,因为要换面了…嘻嘻.
这或许不是个高明的方式,但是一个符合我项目需求的方式,分享给大家希望对大家有帮助,谢谢。