using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class ProceduralGrass : MonoBehaviour
{
#region Terrain Data
[Range(0, 1000)]
public int terrainSize = 250; //地形大小
[Range(0, 100f)]
public float terrainHeight = 10f; //地形高度
public Material terrainMat; //地形材质
private float xOffset;
private float zOffset;
[Range(1f, 100f)]
public float scaleFatter = 10f; //地形缩放
[Range(1f, 100f)]
public float offsetFatter = 10f; //地形偏移
List<Vector3> vertexs = new List<Vector3>(); //顶点列表,存储网格顶点信息
List<int> triangles = new List<int>(); //三角形面列表,存储网格三角形信息
float[,] perlinNoise; //存储每个顶点的高度值
Vector3[] terrainNormals; //存储地形的顶点法线的一维数组
Vector3[,] terrainNormals2D; //存储地形的顶点法线的二维数组
#endregion
//___________________________________________________________________________
#region Grass Data
[Range(0, 100)]
public int grassRowCount = 50; //草根集,定义草的广度
[Range(1, 1000)]
public int grassCountPerPatch = 100; //定义每一堆 草根集 的草密度
public Material grassMat; //草的材质
public Mesh grassMesh; //草的网格
List<Vector3> grassVerts = new List<Vector3>(); //存储草的顶点
Vector3[] grassNormals; //存储草的顶点法线
List<Vector3> grassNormalList = new List<Vector3>(); //存储草的顶点法线列表
#endregion
void Start()
{
xOffset = transform.position.x;
zOffset = transform.position.z;
terrainNormals = new Vector3[terrainSize * terrainSize];//顶点法线数组容量250*250
terrainNormals2D = new Vector3[terrainSize, terrainSize];//2D顶点法线数组容量250*250
perlinNoise = new float[terrainSize, terrainSize];//顶点高度数组容量250*250
GenerateTerrain();//生成地形
GenerateGrassArea(grassRowCount, grassCountPerPatch);//生成草地(广度,密度)
}
void CreateVertsAndTris()
{
//遍历每一个顶点,并用列表 vertexs 和 triangles 存储
for (int i = 0; i < terrainSize; i++)
{
for (int j = 0; j < terrainSize; j++)
{
float noiseHeight = GeneratePerlinNoise(i, j);//顶点随机高度
perlinNoise[i, j] = noiseHeight;//该坐标下的顶点高度存入数组
vertexs.Add(new Vector3(i, noiseHeight * terrainHeight, j));//顶点坐标(x,y,z)
//不算上坐标轴的顶点
if (i == 0 || j == 0)
continue;
//每三个顶点作为一个索引建立三角形
triangles.Add(terrainSize * i + j);
triangles.Add(terrainSize * i + j - 1);
triangles.Add(terrainSize * (i - 1) + j - 1);
triangles.Add(terrainSize * (i - 1) + j - 1);
triangles.Add(terrainSize * (i - 1) + j);
triangles.Add(terrainSize * i + j);
}
}
//清空grassVerts的数据
grassVerts.Clear();
}
//生成地形高度的随机 Perlin 值
float GeneratePerlinNoise(int i, int j)
{
float xCoord = (float)(i + xOffset) / terrainSize * scaleFatter + offsetFatter;
float zCoord = (float)(j + zOffset) / terrainSize * scaleFatter + offsetFatter;
return Mathf.PerlinNoise(xCoord, zCoord);
}
//生成地形网格数据
void GenerateTerrain()
{
CreateVertsAndTris();
//计算UV(二维向量-顶点坐标作为法线方向)
Vector2[] uvs = new Vector2[vertexs.Count];
for (int i = 0; i < vertexs.Count; i++)
{
uvs[i] = new Vector2(vertexs[i].x, vertexs[i].z);
}
//添加一个 MyTerrain 的物体,并添加 MeshFilter / MeshRenderer 两个组件
//MeshFilter 存储物体的网格信息
//MeshRenderer 负责接收这些信息并把这些信息渲染出来
GameObject Myterrain = new GameObject("Terrain0");
Myterrain.transform.position = this.gameObject.transform.position;
Myterrain.AddComponent<MeshFilter>();
MeshRenderer renderer = Myterrain.AddComponent<MeshRenderer>();
//开启地面的阴影投射和接受
renderer.receiveShadows = true;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
//添加Mesh Collider
MeshCollider collider = Myterrain.AddComponent<MeshCollider>();
//添加材质
renderer.sharedMaterial = terrainMat;
//创建一个 Mesh 网格
Mesh groundMesh = new Mesh//网格构造函数
{
//输入网格顶点数据
vertices = vertexs.ToArray(),
//输入网格三角面数据
triangles = triangles.ToArray(),
uv = uvs
};
//为了得到正确的光照需要重新计算得到正确的法线信息
groundMesh.RecalculateNormals();
terrainNormals = groundMesh.normals;
//将存储地形法线数组从一维数组转为二维,便于索引
for (int i = 0; i < terrainSize; i++)
{
for (int j = 0; j < terrainSize; j++)
{
terrainNormals2D[i, j] = terrainNormals[i * terrainSize + j];
}
}
//网格赋值到渲染器/碰撞体
Myterrain.GetComponent<MeshFilter>().mesh = groundMesh;
collider.sharedMesh = groundMesh;
}
void GenerateGrassArea(int rowCount, int perPatchSize)
{
//最大顶点数为 65000
List<int> indices = new List<int>();
for (int i = 0; i < 65000; i++)
{
indices.Add(i);
}
//初始位置
Vector3 currentPos = transform.position;
//草根集 每一次循环偏移的距离
Vector3 patchSize = new Vector3(terrainSize / rowCount, 0, terrainSize / rowCount);
//每一堆 草根集 进行循环
for (int i = 0; i < rowCount; i++)
{
for (int j = 0; j < rowCount; j++)
{
GenerateGrass(currentPos, patchSize, perPatchSize);
currentPos.x += patchSize.x;
}
currentPos.x = transform.position.x;
currentPos.z += patchSize.z;
}
//生成 GrassLayerGruop 来成为父级管理物理
GameObject grassLayerGroup1 = new GameObject("GrassLayerGroup1");
//生成 GrassLayer 物体来存储草数据
GameObject grassLayer;
MeshFilter grassMeshFilter;
//Mesh grassMesh;
MeshRenderer grassMeshRenderer;
int a = 0;
//当 grassVerts.Count 的数量即草的全部顶点数超过 65000 个时
//创立多个网格处理
while (grassVerts.Count > 65000)
{
Debug.Log("More65000");
grassMesh = new Mesh();
grassMesh.vertices = grassVerts.GetRange(0, 65000).ToArray();
//存储每个草顶点的法线(当顶点超过 65000个)
grassNormals = new Vector3[65000];
grassNormalList.GetRange(0, 65000);
for (int i = 0; i < 65000; i++)
{
grassNormals[i] = grassNormalList[i];
}
//设置子网格的索引缓冲区,相关官方文档:https://docs.unity3d.com/ScriptReference/Mesh.SetIndices.html
//每一个创建的网格的顶点数目不会超过 65000 个
grassMesh.SetIndices(indices.ToArray(), MeshTopology.Points, 0);
//创建一个新的 GameObject 来承载这些点
grassLayer = new GameObject("GrassLayer " + a++);
grassLayer.transform.SetParent(grassLayerGroup1.transform);
grassMeshFilter = grassLayer.AddComponent<MeshFilter>();
grassMeshRenderer = grassLayer.AddComponent<MeshRenderer>();
//关闭草地的阴影投射和接受
grassMeshRenderer.receiveShadows = false;
grassMeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
grassMeshRenderer.sharedMaterial = grassMat;
grassMesh.normals = grassNormals;
grassMeshFilter.mesh = grassMesh;
//移除前 65000 个顶点
grassVerts.RemoveRange(0, 65000);
grassNormalList.RemoveRange(0, 65000);
}
#region 草的数量少于6500时
//当 grassVerts.Count 的数量即草的全部顶点数没有超过 65000 个时
grassLayer = new GameObject("GrassLayer" + a);
grassLayer.transform.SetParent(grassLayerGroup1.transform);
grassMeshFilter = grassLayer.AddComponent<MeshFilter>();
grassMeshRenderer = grassLayer.AddComponent<MeshRenderer>();
//关闭草地的阴影投射和接受
grassMeshRenderer.receiveShadows = false;
grassMeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
grassMesh = new Mesh
{
vertices = grassVerts.ToArray()
};
//存储每个草顶点的法线(当顶点没有超过 65000个)
grassNormals = new Vector3[grassMesh.vertexCount];
grassNormalList.GetRange(0, grassMesh.vertexCount);
for (int i = 0; i < grassMesh.vertexCount; i++)
{
grassNormals[i] = grassNormalList[i];
}
grassMesh.normals = grassNormals;
//设立子网格数据
grassMesh.SetIndices(indices.GetRange(0, grassVerts.Count).ToArray(), MeshTopology.Points, 0);
grassMeshFilter.mesh = grassMesh;
grassMeshRenderer.sharedMaterial = grassMat;
#endregion
}
//生成草
void GenerateGrass(Vector3 vertPos, Vector3 patchSize, int grassCountPerPatch)
{
//每一堆 草根集 里的草进行循环
for (int i = 0; i < grassCountPerPatch; i++)
{
//Random.value 返回 0~1 之间的随机值
//得到在两个 草根集 之间的草的随机位置并用索引值
float randomX = Random.value * patchSize.x;
float randomZ = Random.value * patchSize.z;
int indexX = (int)((vertPos.x - transform.position.x) + randomX);
int indexZ = (int)((vertPos.z - transform.position.z) + randomZ);
//防止草种出地形
if (indexX >= terrainSize)
{
Debug.Log("Full");
indexX = (int)terrainSize - 1;
}
if (indexZ >= terrainSize)
{
Debug.Log("Full");
indexZ = (int)terrainSize - 1;
}
//添加每一个草的顶点位置到 grassVert 列表里
grassVerts.Add(new Vector3(vertPos.x + randomX, perlinNoise[indexX, indexZ] * terrainHeight, vertPos.z + randomZ));//包括草地Mesh网格顶点高度值
//添加每一个草的顶点法线到 grassNormalList 列表里
grassNormalList.Add(terrainNormals2D[indexX, indexZ]);//草地Mesh网格顶点法线,与同坐标的地面网格法线相同
}
}
}
———————————草地Shader——————————————
Shader "Unlit/Grass+Wind"
{
Properties
{
[HDR]_GrassColor("GrassColor", Color) = (1,1,1,1) //控制草的颜色
_SpecualarColor("SpecularColor", Color) = (1,1,1,1) //控制草的高光颜色
_Specular("_Specular", Range(0, 1)) = 1 //控制草的高光程度
_Gloss("Gloss", Range(0,20)) = 1 //控制草的高光
[HDR]_FresnelColor("FresnelColor", Color) = (1,1,1,1) //控制草的边缘高光颜色
_FresnelPower("FresnelPower", Range(0, 5)) = 1 //控制草的边缘高光程度
_MainTex ("Texture", 2D) = "white" {} //草的颜色贴图
_AlphaTex("AlphaTexture", 2D) = "white" {} //草的透明贴图
_GrassHeight("GrassHeight", Range(0.5, 5)) = 2.5 //控制草的高度
_GrassWidth("GrassWidth", Range(0.001, 0.5)) = 0.05 //控制草的宽度
_BladeForward("BladeForward", Range(0, 2)) = 1 //控制草的弯曲程度
_WindTex("WindTex", 2D) = "white" {} //风的采样贴图
_WindVector("WindVector", Vector) = (1,1,1,0) //控制风的方向
_WindTimeScale("WindTimeScale", float) = 1 //控制风的速度
_WindTexMapSize("WindTexMapSize", float) = 80 //控制风的采样贴图大小
_WindXZStrength("WindXZStrength", float) = 10 //控制风在草的XZ轴上的偏移
_WindYStrength("WindYStrength", float) = 10 //控制风在草的Y轴上的偏移 */
}
SubShader
{
//存在顶点动画,所以要关闭批处理,DisableBatching 设置为 True
Tags {
"RenderType" = "TransparentCutout"
"IgnoreProjector" = "True"
"Queue" = "AlphaTest"
"DisableBatching" = "True"
}
//设置为双面渲染,关闭背面剔除
Cull Off
LOD 100
Pass
{
//设置为前向渲染模式
Tags { "LightMode" = "ForwardBase" }
Cull Off
AlphaToMask On
CGPROGRAM
//引入头文件
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
//要应用几何着色器必须要将编译目标设置为 4.0
#pragma target 4.0
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
//定义几何着色器
#pragma geometry geom
fixed4 _GrassColor;
fixed4 _SpecualarColor;
fixed _Specular;
float _Gloss;
fixed4 _FresnelColor;
half _FresnelPower;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _AlphaTex;
fixed _GrassHeight;
fixed _GrassWidth;
fixed _BladeForward;
sampler2D _WindTex;
float4 _WindTex_ST;
half4 _WindVector;
half _WindTimeScale;
float _WindTexMapSize;
half _WindXZStrength;
half _WindYStrength;
struct a2v {
float4 pos : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
//顶点着色器传给几何着色器的数据结构
struct v2g {
float4 pos : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
//几何着色器传给片元着色器的数据结构
struct g2f {
float4 pos : SV_POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
static const float oscillateDelta = 0.05;
//顶点着色器
//直接将从网格得到的数据传给传入几何着色器的结构体 v2g
v2g vert(a2v v) {
v2g o;
o.pos = v.pos;
o.normal = v.normal;
o.uv = v.uv;
return o;
}
//创建 CreatG2fOut() 函数
//初始化从几何着色器传入片元着色器的结构体 g2f
g2f CreatG2fOut() {
g2f output;
output.pos = float4(0, 0, 0, 0);
output.normal = float3(0, 0, 0);
output.uv = float2(0, 0);
output.worldPos = float3(0, 0, 0);
return output;
}
g2f GetVertex(float4 pos, float3 normal, float2 uv) {
g2f output;
output.pos = UnityObjectToClipPos(pos);
output.normal = UnityObjectToWorldNormal(normal);
output.uv = uv;
output.worldPos = UnityObjectToWorldDir(pos);
return output;
}
//几何着色器
[maxvertexcount(30)]//限制几何着色器输出的最大顶点数目。每当输入一个图元,几何着色器可以输出 0~N 个图元。不论是什么结构的图元都是由顶点构成的,而这个语句就是用来限制输出最大的顶点数量(只要小于等于这个数量就可以,多余的顶点会被剔除)
void geom(point v2g points[1], inout TriangleStream<g2f> triStream) {
//顶点着色器输入的顶点位置
float4 root = points[0].pos;
//生成一个伪随机数
float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
//给每根草的长宽加上这个随机值,我们希望草的宽度不要太宽或者太窄
_GrassWidth = _GrassWidth + (random / 50);
_GrassHeight = _GrassHeight + (random / 5);
//设置草的网格顶点一共有12个
const int vertexCount = 12;
//创建12个 g2f 输出数组
g2f v[vertexCount] = {
CreatG2fOut(), CreatG2fOut(), CreatG2fOut(), CreatG2fOut(),
CreatG2fOut(), CreatG2fOut(), CreatG2fOut(), CreatG2fOut(),
CreatG2fOut(), CreatG2fOut(), CreatG2fOut(), CreatG2fOut()
};
//初始化每个顶点的位置 pos 和 uv
float4 pos = float4(0, 0, 0, 0);
float2 uv = float2(0, 0);
//顶点的 UV 在竖直方向上的当前值和偏移值
float currentV = 0;
float offsetV = 1.0 / (vertexCount / 2 - 1);
//顶点的 y 坐标在竖直方向上的当前值的偏移值
float currentVertexHeight = 0;
float currentHeightOffset = 0;
float verticalEff = 0;
//BlendCood
//让草绕着自身的 y 轴进行旋转
//生成一个随机角度
fixed randomAngle = frac(sin(root.x)*10000.0) * UNITY_HALF_PI;
//根据矩阵旋转的定理,分别创建旋转矩阵
//平移矩阵,先将所有点平移到原点
float4x4 firstTransformMatrix = float4x4(
1.0, 0.0, 0.0, -root.x,
0.0, 1.0, 0.0, -root.y,
0.0, 0.0, 1.0, -root.z,
0.0, 0.0, 0.0, 1.0
);
//旋转矩阵
float4x4 rotateMatrix = float4x4(
cos(randomAngle), 0, sin(randomAngle), 0,
0, 1, 0, 0,
-sin(randomAngle), 0, cos(randomAngle), 0,
0, 0, 0, 1
);
//再平移回去
float4x4 lastTransformMatrix = float4x4(
1.0, 0.0, 0.0, root.x,
0.0, 1.0, 0.0, root.y,
0.0, 0.0, 1.0, root.z,
0.0, 0.0, 0.0, 1.0
);
//BlendCood
//进行生成全部草顶点的循环
for (int i = 0; i < vertexCount; i++)
{
//fmod(a,b) 返回 a 除 b 的余数
//如果返回值为偶数,顶点 UV 坐标均为(0,V)
if (fmod(i, 2) == 0) {
pos = float4(root.x - _GrassWidth, root.y + currentVertexHeight, root.z, 1);
uv = fixed2(0, currentV) * _MainTex_ST.xy + _MainTex_ST.zw;
}
else {
pos = float4(root.x + _GrassWidth, root.y + currentVertexHeight, root.z, 1);
uv = fixed2(1, currentV) * _MainTex_ST.xy + _MainTex_ST.zw;
currentV += offsetV;
currentVertexHeight += currentV * _GrassHeight;
}
//对顶点 XZ 轴进行偏移
float2 randomDir = float2(sin((random * 15)), sin((random * 10)));
float2 forward = (sin((root.x * 10 + root.z / 5) * random)* verticalEff + randomDir * sin((random * 15)))* verticalEff;
pos.xz += forward * _BladeForward;
if (fmod(i, 2) == 1) {
verticalEff += offsetV;
}
//对顶点 Y 轴进行旋转
//pos = mul(lastTransformMatrix, mul(rotateMatrix, mul(firstTransformMatrix, pos)));
//对顶点 Y 轴进行旋转
pos = mul(lastTransformMatrix, mul(rotateMatrix, mul(firstTransformMatrix, pos)));
/*/风随机偏移
float2 wind = float2(sin(_Time.x * UNITY_PI * 5), sin(_Time.x * UNITY_PI * 5));
wind.x += (sin(_Time.x + root.x / 25) + sin((_Time.x + root.x / 15) + 50)) * 0.5;
wind.y += cos(_Time.x + root.z / 80);
wind *= lerp(0.7, 1.0, 1.0 - random);
float oscillationStrength = 2.5f;
float sinSkewCoeff = random;
float lerpCoeff = (sin(oscillationStrength * _Time.x + sinSkewCoeff) + 1.0) / 2;
float2 leftWindBound = wind * (1.0 - oscillateDelta);
float2 rightWindBound = wind * (1.0 + oscillateDelta);
wind = lerp(leftWindBound, rightWindBound, lerpCoeff);
float randomAngle = lerp(-UNITY_PI, UNITY_PI, random);
float randomMagnitude = lerp(0, 1., random);
float2 randomWindDir = float2(sin(randomAngle), cos(randomAngle));
wind += randomWindDir * randomMagnitude;
float windForce = length(wind);
pos.xz += wind.xy * verticalEff;
pos.y -= windForce * verticalEff * 0.8;
pos = UnityObjectToClipPos(pos);
if (fmod(i, 2) == 1) {
verticalEff += offsetV;
}*/
//--风
//将世界坐标下的风的方向转化为局部坐标
float4 localWindDir = normalize(mul(unity_WorldToObject, _WindVector));
//控制风速(实际上为采样 uv 的移动速度)
float time = (_Time.y)*(_WindTimeScale);
//对风贴图进行采样
half4 rootWorldPos = mul(unity_ObjectToWorld, root);
//windmutation 一直在 0~1 之间变化
float windmutation = 1 - tex2Dlod(_WindTex, float4(rootWorldPos.x / _WindTexMapSize + time, rootWorldPos.z / _WindTexMapSize, 0, 0)).g;
//sin(time + windmutation * 10) * cos(time * 2 / 3 + 1 + windmutation * 10) -1~1
//localWindDir.xz 控制风的方向
//clamp(uv.y - 0.1, 0, 1) UV.y 范围在 0.1 以下的顶点不发生移动
//xz 轴上的偏移
half2 xzOffset = sin(time + windmutation * 10) * cos(time * 2 / 3 + 1 + windmutation * 10) * localWindDir.xz * clamp(uv.y - 0.1, 0, 1);
pos.xz += xzOffset * _WindXZStrength;
//根据 XZ 轴上的偏移算出在 Y 轴上
//直角三角形定理
//Y轴的偏移
half yOffset = pos.y - sqrt(pos.y * pos.y - (xzOffset.x * xzOffset.x + xzOffset.y * xzOffset.y));
pos.y -= yOffset * _WindYStrength * clamp(uv.y - 0.35, 0, 1);
//pos.xz += sin(_Time.y * _WindTimeScale) * uv.y;
v[i] = GetVertex(pos, points[0].normal, uv);
}
//inout TriangleStream<g2f> triStream 输出三角形,即三个顶点数据
//将每三个顶点转化为三角形输出到片元着色器
for (int p = 0; p < (vertexCount - 2); p++)
{
//triStream.Append(vertex); 该方法将输入的三个顶点自动构建成三角形
triStream.Append(v[p]);
triStream.Append(v[p + 2]);
triStream.Append(v[p + 1]);
}
}
//片元着色器
//简单的 Blin-Phong 光照模型
float4 frag(g2f i) : SV_Target{
//对_MainTex纹理和_AlphaTex纹理进行采样
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed alpha = tex2D(_AlphaTex, i.uv).a;
//将法线归一化
float3 worldNormal = normalize(i.normal);
float3 worldSpecNormal = worldNormal;
worldNormal = worldNormal * 0.5 + 0.5;
//得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//得到世界空间下光照方向
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
//Diffuse 漫反射颜色
fixed NdotL = saturate(dot(worldNormal, worldLightDir));
fixed3 diffuse = _LightColor0.rgb * NdotL;
//Specular 高光颜色
//得到世界空间下的视线方向
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
//得到半角向量
fixed3 halfDir = normalize(worldLightDir + worldViewDir);
fixed3 NdotH = saturate(dot(worldSpecNormal, halfDir));
float spec = pow(NdotH, _Specular * 128.0) * _Gloss;
fixed3 specular = _SpecualarColor * _LightColor0.rgb * spec;
//Fresnel
fixed fresnel = saturate(1 - dot(worldSpecNormal, worldViewDir));
//fresnel = clamp(fresnel - 0.2, 0, 1);
fresnel = pow(fresnel, _FresnelPower) * clamp(i.uv.y - 0.5, 0, i.uv.y);
fixed3 fresnelColor = fresnel * _FresnelColor;
//得到并输出最终颜色
fixed3 finalColor = ambient + diffuse + specular;
return fixed4(texColor * _GrassColor.rgb * finalColor, alpha);
//return fixed4(worldNormal, alpha);
}
ENDCG
}
}
FallBack "Diffuse"
}