Unity草地/草坪案例分享(完整代码)

老规矩先上图
在这里插入图片描述
最近又开始继续操刀我的新独立游戏啦,网上看了很多草地的案例受益匪浅。但是嘛最近还是选择用自己的方式去实现。
主要是因为这种方式可以更好的贴合我前后的需求。还有很多有意思的技术点,有时间一点点拿来和大家一起分享吧。
这次先来说说草地的实现方式:
由于草地需要贴合模型表面,所以这里是从附着的模型表面开始的:
一、循环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
}

草地的贴图大概是这样:
在这里插入图片描述
中间有个间隔,因为要换面了…嘻嘻.

这或许不是个高明的方式,但是一个符合我项目需求的方式,分享给大家希望对大家有帮助,谢谢。

猜你喜欢

转载自blog.csdn.net/ww1351646544/article/details/119875969