闲云野鹤:吃鸡(一)之场景制作:使用GPU instancing方式制作刷草插件

GPU instancing方式制作刷草插件(unity版本8.2.2)

 
先上最终效果图(欢迎加我qq交流:358641634):
十种草 混刷生成比较自然的场景(带阴影、风力、草可见距离可调)
    
 
插件编辑界面(草密度、范围、体积大小等等可调)
 
一键快刷(带空白过滤更自然)
 
视椎体剪裁(提高效率)
 
Profiler效率测试(满屏1920X920:63787株草、167帧、21Batches、21setPass calls )
   
        为何要制作这个刷草插件?因为吃鸡类的次时代场景要求真实且效率要高,但感觉unity自带的刷草功能不太满足需要,例如,场景较大,达到十几平方公里,草的有效视距也较大,质量要求较高,但unity自带的刷草功能感觉效果不真实,也无阴影等效果,也不满足大视距要求,在开镜下的最大视距也只有250米。至于unity引擎自带的树木实测只要不是大片树木效率勉强还行。
 
        也许使用一些unity付费插件的话可能可以很好解决问题,但我的想法还是自己动手做一个插件,主要目的还是为了学习和研究而不是做一款商用游戏。
 
        于是决定:1、树木:使用unity引擎自带的树木,因为实测只要不是大片森林,效率还过得去。其实对战的场景如果树木遮挡太多对可玩性会有影响。2、地形:仍然使用unity自带的地形。因为自unity8.3版本以后地形已经升级了,据官方称效率可以提高50%,想必应该满足要求了,不过我现在用的是8.2的版本,以后升级就ok,不必再费心思。3、花草:自己做插件。花草是对效率影响比较大的,也是感觉unity引擎的不足之处,先是想到用unity的制作树木方式来制作花草,但是毕竟花草和树木不一样,数量大了肯定卡得不行。于是决定自己先做一个插件,使用GPU instancing方式,这种方式刷草很符合大量相似小物体的渲染情景,不需要实例化草物体和多次从CPU提交GPU数据,相当于将大片草合批后整体提交给GPU,然后一次性渲染,减少了CPU和GPU之间的信息交换,且GPU的运算速度较CPU要高得多,能轻松搞定同屏100万面渲染而帧率保持在100帧以上,关于GPU instancing技术参考官网 https://docs.unity3d.com/Manual/GPUInstancing.html
 

        目标:用GPU instancing方式实现刷草功能,能实现阴影、视椎体裁剪、自定义shader、自定义mesh、风力效果、自定义草可见距离、大量草一次性渲染。

        思路:在编辑状态下(非运行状态)对Scene视图的Terrain刷草,先将地形划成很多小块,刷草时自动将每块草的位置(旋转等)数据保存在磁盘上,然后在运行状态下读出这些位置数据,同时将草的材质、网格数据一并提交给GPU进行运算,并根据视椎体剔除看不见的草数据以提高渲染效率,最后再渲染出来。

        希望:由于本人是第一次做插件,诚盼高手和大佬指点,特别是关于思路和实现细节,先谢谢了!

先贴出一些具体问题的解决方式,供大家一起探讨:

 
1、屏蔽鼠标在Scene视图的框选作用。也就是说鼠标左键按下并拖动时是种草而不是框选物体,所以要屏蔽鼠标对物体的选择功能。
主要代码:
    public void OnEnable()
    {
        SceneView.onSceneGUIDelegate -= OnSceneGUI;
        SceneView.onSceneGUIDelegate += OnSceneGUI;
     }
    void OnSceneGUI(SceneView sceneView)
    {
        int controlID = GUIUtility.GetControlID(FocusType.Passive);//使scene视图不能选择物体
        HandleUtility.AddDefaultControl(controlID);
     }

 

2、在刷草的时候首先将地形自动成多块以便提高数据处理以及存储的速度,也就是说仅更新所刷那一块的数据并保存,没有刷到的就不处理,也不更新储存的数据,提高了效率。 

主要代码:

for (int i = 0; i < n; i++)//每帧都绘制
        {
            UpdateBuffers(i);
            if (virtuTerrain.perPiece[i].pos.Count == 0 || viewAble == false)
                continue;//仅渲染有数据的草类
            instanceMaterial[i].SetFloat("_WindSpeed", windSpeed);
            Graphics.DrawMeshInstancedIndirect(currentMesh, subMeshIndex, instanceMaterial[i],
                new Bounds(Vector3.zero, new Vector3(2000.0f, 2000.0f, 2000.0f)), argsBuffers[i]);
        }
        SceneView.RepaintAll();
    }
    void UpdateBuffers(int number)//某块有草的数量变化时只更新那一块的数据
    {
        if (virtuTerrain.perPiece[number].pos.Count == 0) return;
        if (cachedInstanceCount[number] == virtuTerrain.perPiece[number].pos.Count)//判断数据有无更新
        {
            instanceMaterial[number].SetBuffer("positionBuffer", positionBuffer[number]);
            return;//数据无更新则返回
        }
        subMeshIndex = Mathf.Clamp(subMeshIndex, 0, currentMesh.subMeshCount - 1);
        if (positionBuffer[number] != null)
            positionBuffer[number].Release();
        positionBuffer[number] = new ComputeBuffer(virtuTerrain.perPiece[number].pos.Count, 16);
        instanceMaterial[number].SetBuffer("positionBuffer", positionBuffer[number]);
        positionBuffer[number].SetData(virtuTerrain.perPiece[number].pos);
        args[0] = (uint)currentMesh.GetIndexCount(subMeshIndex);
        args[1] = (uint)virtuTerrain.perPiece[number].pos.Count;
        args[2] = (uint)currentMesh.GetIndexStart(subMeshIndex);
        args[3] = (uint)currentMesh.GetBaseVertex(subMeshIndex);
        argsBuffers[number].SetData(args);
        cachedInstanceCount[number] = virtuTerrain.perPiece[number].pos.Count;
    }

 

3、在编辑状态下(非运行状态)对Scene视图的Terrain刷草,需要用到编辑器模式,但在编辑模式下对scene的操作,其刷新性能不佳很卡,于是想到了一个方法,但是这个方法需要scene场景中有运动物体才会不断更新,因此就有了编辑器放置一个物体在scene中并旋转,解决了scene场景更新慢的问题。(代码较简单,略)

4、渲染多种草:播放状态时unity每个时间周期内最多可同时渲染10种草。  

主要代码:      

for (int i = 0; i < KindOfGrass; i++)
        {
            if (xyzwsList[i].Length < 1) continue;
            bs = grassDataCount[i] / 64;
            bs = bs == 0 ? 1 : bs;
            positionAppendBuffer[i].SetCounterValue(0);
            positionComputeShader.SetFloat("viewDistance", viewDistance * viewDistance);
            if (lastAspect != camera.aspect)
            {
                 w = Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * farClipPlane * camera.aspect;
                lastAspect = camera.aspect;
            }
            Vector3 vR =farClipPlane * transform.forward + w * transform.right;
            Vector3 vL =farClipPlane * transform.forward - w * transform.right;
            Vector3 RightPlane_N = Vector3.Cross(vR,transform.up);//视椎体右平面法线
            Vector3 LeftPlane_N = Vector3.Cross(transform.up,vL);//视椎体左平面法线
            positionComputeShader.SetVector("Centerposition", transform.position-transform.forward*3);//视椎体顶点(退后3米)
            positionComputeShader.SetVector("RightPlane_N", RightPlane_N);
            positionComputeShader.SetVector("LeftPlane_N", LeftPlane_N);
            positionComputeShader.SetBuffer(positionComputeKernelId, "positionListBuffer", positionListBuffer[i]);
            positionComputeShader.SetBuffer(positionComputeKernelId, "positionBuffer", positionAppendBuffer[i]);
            instanceMaterial[i].SetBuffer("positionBuffer", positionAppendBuffer[i]);
            positionComputeShader.Dispatch(positionComputeKernelId, bs, 1, 1);
            ComputeBuffer.CopyCount(positionAppendBuffer[i], argsBuffers[i], 4);
            Graphics.DrawMeshInstancedIndirect(instanceMesh[i], 0, instanceMaterial[i], instanceMesh[i].bounds, argsBuffers[i], 0, null, castShadows, receiveShadows);
        }

 

5、视椎体裁剪提高效率:简化视椎体裁剪,只计算水平方向不计算竖直方向,毕竟竖直方向在视线外的情况非常少如仰望天空和俯瞰下方。方法是求出视椎体左右面的法线,然后计算草像素的向量与法线的投影是否为正,取二者的交集即可判断是否应该渲染该像素。

主要代码:

//cs关键代码
            Vector3 vR =farClipPlane * transform.forward + w * transform.right;
            Vector3 vL =farClipPlane * transform.forward - w * transform.right;
            Vector3 RightPlane_N = Vector3.Cross(vR,transform.up);//视椎体右平面法线
            Vector3 LeftPlane_N = Vector3.Cross(transform.up,vL);//视椎体左平面法线
            positionComputeShader.SetVector("Centerposition", transform.position-transform.forward*3);//视椎体顶点(退后3米)
            positionComputeShader.SetVector("RightPlane_N", RightPlane_N);
            positionComputeShader.SetVector("LeftPlane_N", LeftPlane_N);
//computer关键代码
void CSPositionKernel (uint3 id : SV_DispatchThreadID)
{
       float3 V3=float3(positionListBuffer[id.x].x-Centerposition.x,positionListBuffer[id.x].y-Centerposition.y,positionListBuffer[id.x].z-Centerposition.z);
       float fR=dot(RightPlane_N,V3);
       float fL=dot(LeftPlane_N,V3);
       float Sqr_Distence=(positionListBuffer[id.x].x -Centerposition.x)*(positionListBuffer[id.x].x -Centerposition.x)+
                          (positionListBuffer[id.x].y -Centerposition.y)*(positionListBuffer[id.x].y -Centerposition.y)+
                                     (positionListBuffer[id.x].z -Centerposition.z)*(positionListBuffer[id.x].z -Centerposition.z);
       if(Sqr_Distence < viewDistance && fR > 0 && fL > 0 )//限定在左右视椎体面内且小于可视距离
       {
       float4 v4=float4(positionListBuffer[id.x].x,positionListBuffer[id.x].y,positionListBuffer[id.x].z,positionListBuffer[id.x].w);
       positionBuffer.Append(v4);
       }
}

 

 
6、延伸视椎体边界,防止视椎体边缘处草和阴影突然出现和消失。采用简单处理:在计算视椎体的时候将视椎体顶点向后退几米。
关键代码:positionComputeShader.SetVector( "Centerposition", transform.position-transform.forward*3); //视椎体顶点(退后3米)
 

7、一键快速刷草:  

主要代码: 

 public void SpeedCreadeGrass()//一键快速刷草
    {
        //  if (currentMaterial == null) return;
        for (int i = 0; i < 10000; i++)
        {
            float x = UnityEngine.Random.Range(0, TerrainWidth);//随机刷
            float z = UnityEngine.Random.Range(0, TerrainLength);
            Ray ray = new Ray(new Vector3(x, 3000, z), Vector3.down);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 5000))//刷草的地方必须在地形范围内
            {
                if (hit.transform.name.CompareTo(terrainName) == 0)
                {
                    float _x = hit.point.x;
                    float _z = hit.point.z;
                    if (GrassNoise.GetPixel((int)x, (int)z).r > 0.05f) continue;//按噪声图像素颜色值过滤,留白更自然
                    float _y = terrain.SampleHeight(new Vector3(_x, 0, _z));//取此点对应的地形高度,草随高度相应改变
                    virtuTerrain.addPos(new Vector3(_x, _y, _z), UnityEngine.Random.Range(0.3f, scale), true);//加入旋转,避免雷同
                }
            }
        }
    }

 

8、地形划块:   

主要代码:

 public float _widthPix, _heightPix;//地形宽高
    public int _row;//地形行
    public int _column;//地形列
    public PerPiece[] perPiece;//每片
    public virtualTerrain(float widthPix, float heightPix, int row, int column)//初始化虚拟地形长宽(保持与地形相同)、行列等分
    {
        if (widthPix * heightPix * row * column == 0)
        {
            Debug.Log("地形的宽高、行列均不能为0!");
            return;
        }
        _widthPix = widthPix;
        _heightPix = heightPix;
        _row = row; _column = column;
        perPiece = new PerPiece[_row * _column];
        for (int h = 0; h < _column; h++)
        {
            for (int w = 0; w < _row; w++)
            {
                perPiece[h * _row + w] = new PerPiece();
                perPiece[h * _row + w].center.x = (w + 0.5f) * _widthPix / _row;//每片的中心坐标
                perPiece[h * _row + w].center.y = (h + 0.5f) * _heightPix / _column;
                perPiece[h * _row + w].coord.x = w * _widthPix / _row;//每片的网格坐标(左下)
                perPiece[h * _row + w].coord.y = h * _heightPix / _column;
                perPiece[h * _row + w].ID = h * _row + w;//每片的ID(从左下0起始)
                perPiece[h * _row + w].pos = new List<xyzwPos>();
            }
        }
    }

 

除了以上问题其实在制作插件过程中还遇到很多细节问题,如:mesh的制作、shader的调试、一些bug的排除、、、、都一一解决了,不过还有很多可以继续改进的地方,比如在播放状态下必须关闭编辑器,再次编辑又必须重新打开编辑器等,希望大佬能指点一下。

 由于脚本较多,下面将核心脚本贴出来,其它就不一一贴出来了,等用一段时间稳定后再放到下载区供大家测试。

几个主要的核心脚本如下:

1、InstancedIndirectCompute.cs(功能见脚本中的标注)

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
using UnityEditor;
/// <summary>
/// 在 Start()函数中一次性读取硬盘所有类别的草位置数据,并传递给computerBuffer,由于数据含有位置信息,
/// 所以可以在gpu的computerBuffer中根据这些信息过滤掉视线外的草, 仅传递可视部分数据给Materail显示。
///每帧中cpu只需提供当前需要显示的中心坐标即可,其他全由gpu完成,效率较高
/// 注意:1、判断视线内的块使用了简化的计算方法,不做商用效率优先:只判断每个需要渲染的像素是否在视椎体左右面之间,忽略上下判断。
///       2、在传递给Materail时使用的是computerBuffer的Append方式。
///       3、实验在同时显示5种草、开启实时阴影、约一百万面(视线内估计二十万),在关掉垂直同步时全屏显示可以达到200帧。
/// </summary>
/// 
public class InstancedIndirectCompute : MonoBehaviour
{
    Mesh[] instanceMesh;
    Material[] instanceMaterial;
    public ShadowCastingMode castShadows = ShadowCastingMode.Off;
    public bool receiveShadows = false;
    public ComputeShader positionComputeShader;
    private int positionComputeKernelId;
    private ComputeBuffer[] positionAppendBuffer;
    private ComputeBuffer[] positionListBuffer;
    private ComputeBuffer[] argsBuffers;
    private int[] grassDataCount;
    private List<xyzwPos[]> xyzwsList = new List<xyzwPos[]>();
    private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
    private uint[][] argsList = new uint[10][];
    private int bs = 64;
    GameObject TempObj;
    PlayerData OffsetData;
    int viewDistance = 1500;//视距
    int windSpeed = 2;
    Camera camera;
    int InViewCount=0;
    bool shadow = false;
    int KindOfGrass;
    float farClipPlane;
   // float viewAngleCos;
    float lastAspect=1;
    float w;//远视口宽度*0.5
    GrassRW grassRW;
    void Start()
    {
        grassRW = new GrassRW();
        viewDistance=(int)grassRW.ReadPlayDate().viewDistance;
        shadow = grassRW.ReadPlayDate().shadowIsEnable;
        if (shadow)
        {
            castShadows = ShadowCastingMode.On;
            receiveShadows = true;
        }
        else
        {
            castShadows = ShadowCastingMode.Off;
            receiveShadows = false;
        }
        xyzwsList = grassRW.readGrassDate();
        TempObj = new GameObject("TempObj");
        TempObj.transform.position = Vector3.one * -20000;
        OffsetData = (PlayerData)PlayerDataOperator.LoadPlayerData("myvars.data");
        camera = Camera.main;
        farClipPlane = camera.farClipPlane;
        if (OffsetData != null)
        {
            //viewDistance = OffsetData.ViewDistance;
            //camera.farClipPlane = viewDistance;
            windSpeed = OffsetData.WindSpeed;
        }
        KindOfGrass = xyzwsList.Count;
        instanceMesh = new Mesh[KindOfGrass];
        for (int i = 0; i < KindOfGrass; i++)
            instanceMesh[i] = (Instantiate(Resources.Load("grassPrb" + i.ToString()), TempObj.transform) as GameObject).GetComponent<MeshFilter>().mesh;
        instanceMaterial = new Material[KindOfGrass];
        for (int i = 0; i < KindOfGrass; i++)
        {
            instanceMaterial[i] = (Instantiate(Resources.Load("grassMat" + i), TempObj.transform) as GameObject).GetComponent<Renderer>().material;
            instanceMaterial[i].SetFloat("_WindSpeed", windSpeed);
        }
        positionComputeKernelId = positionComputeShader.FindKernel("CSPositionKernel");
        positionAppendBuffer = new ComputeBuffer[KindOfGrass];
        positionListBuffer = new ComputeBuffer[KindOfGrass];
        argsBuffers = new ComputeBuffer[KindOfGrass];
        grassDataCount = new int[KindOfGrass];
        TempObj.hideFlags = HideFlags.HideInHierarchy;
        CreateBuffers();
    }

    void CreateBuffers()
    {
        for (int i = 0; i < KindOfGrass; i++)
        {
            if (xyzwsList[i].Length < 1) continue;
            grassDataCount[i] = xyzwsList[i].Length;
            if (positionAppendBuffer[i] != null) positionAppendBuffer[i].Release();
            positionAppendBuffer[i] = new ComputeBuffer(grassDataCount[i], 16, ComputeBufferType.Append);
            positionAppendBuffer[i].SetCounterValue(0);
            if (positionListBuffer[i] != null) positionListBuffer[i].Release();
            positionListBuffer[i] = new ComputeBuffer(grassDataCount[i], 16);
            instanceMesh[i].bounds = new Bounds(Vector3.zero, Vector3.one * 10000f);
            uint numIndices = (instanceMesh[i] != null) ? (uint)instanceMesh[i].GetIndexCount(0) : 0;
            argsList[i] = args;
            argsList[i][0] = numIndices;
            argsList[i][1] = (uint)grassDataCount[i];
            argsBuffers[i] = new ComputeBuffer(5, sizeof(uint), ComputeBufferType.IndirectArguments);
            argsBuffers[i].SetData(argsList[i]);
            positionListBuffer[i].SetData(xyzwsList[i]);
        }
    }
    void Update()
    {
        for (int i = 0; i < KindOfGrass; i++)
        {
            if (xyzwsList[i].Length < 1) continue;
            bs = grassDataCount[i] / 64;
            bs = bs == 0 ? 1 : bs;
            positionAppendBuffer[i].SetCounterValue(0);
            positionComputeShader.SetFloat("viewDistance", viewDistance * viewDistance);
            if (lastAspect != camera.aspect)
            {
                 w = Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad) * farClipPlane * camera.aspect;
                lastAspect = camera.aspect;
            }
            Vector3 vR =farClipPlane * transform.forward + w * transform.right;
            Vector3 vL =farClipPlane * transform.forward - w * transform.right;
            Vector3 RightPlane_N = Vector3.Cross(vR,transform.up);//视椎体右平面法线
            Vector3 LeftPlane_N = Vector3.Cross(transform.up,vL);//视椎体左平面法线
            positionComputeShader.SetVector("Centerposition", transform.position-transform.forward*15);//视椎体顶点(退后15米,避免阴影突然消失)
            positionComputeShader.SetVector("RightPlane_N", RightPlane_N);
            positionComputeShader.SetVector("LeftPlane_N", LeftPlane_N);
            positionComputeShader.SetBuffer(positionComputeKernelId, "positionListBuffer", positionListBuffer[i]);
            positionComputeShader.SetBuffer(positionComputeKernelId, "positionBuffer", positionAppendBuffer[i]);
            instanceMaterial[i].SetBuffer("positionBuffer", positionAppendBuffer[i]);
            positionComputeShader.Dispatch(positionComputeKernelId, bs, 1, 1);
            ComputeBuffer.CopyCount(positionAppendBuffer[i], argsBuffers[i], 4);
            Graphics.DrawMeshInstancedIndirect(instanceMesh[i], 0, instanceMaterial[i], instanceMesh[i].bounds, argsBuffers[i], 0, null, castShadows, receiveShadows);
        }
    }

        void OnDisable()
    {
        for (int i = 0; i < KindOfGrass; i++)
        {
            if (xyzwsList[i].Length < 1) continue;
            if (positionListBuffer[i] != null)
            {
                positionListBuffer[i].Release();
                positionListBuffer[i] = null;
            }
            if (positionAppendBuffer[i] != null)
            {
                positionAppendBuffer[i].Release();
                positionAppendBuffer[i] = null;
            }
            if (argsBuffers[i] != null)
            {
                argsBuffers[i].Release();
                argsBuffers[i] = null;
            }
        }
        //Debug.Log("OK");
        grassRW.SavePlayerData(viewDistance, shadow);
    }

#if UNITY_EDITOR
    void OnGUI()
    {
        GUILayout.Label( "阴影");
        shadow = GUILayout.Toggle(shadow, "");
        GUILayout.Label("可见距离");
        viewDistance=(int)GUILayout.HorizontalSlider(viewDistance,15,2000,GUILayout.MaxWidth(200));
        if (shadow)
        {
            castShadows = ShadowCastingMode.On;
            receiveShadows = true;
        }
        else
        {
            castShadows = ShadowCastingMode.Off;
            receiveShadows = false;
        }
        for (int i = 0; i < KindOfGrass; i++)
            GUI.Label(new Rect(5,100+ 12 * i, 200, 30), "草"+i.ToString ()+"数量:  "+ grassDataCount[i].ToString("N0"));
    }
#endif
}

2、EditorWindow控制面板代码(控制所有草)

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System;

/// <summary>
/// 放一物体到场景并旋转
/// </summary>
#if UNITY_EDITOR

public class Inspector_Edit : EditorWindow
{
    GameObject go;
    Texture[] textures_kind; //待扩充为草、石头、树3种
    Texture[] textures_grass; //草的主贴图
    public int materIndex = 10;
    public int materIndex1 = 10;
    public GameObject ParentTmp;
    GameObject[] GrassOBJS;
    Terrain terrain;
    private bool[] toggleDraw;
    private bool[] toggleView;
    bool[] foldout;
    Mesh[] meshes;
    Material[] material;
    Mesh[] lastMeshObj = null;
    Material[] lastMatObj = null;
    Texture2D texture2D;
    GameObject gameObject;
    Camera camera;
    bool help = false;
    public Vector2 scrollPosition;
    GameObject grassCus;//光标
    RaycastHit HitPos;
    float grassCusRadius = 0;//光标半径
    float grassCusdensity = 0;//光标透明度
    int choose = 0;//勾选的草
    Material ma;
    PlayerData OffsetData;
    string warning;
    string warn1;
    string warn2;
    string warn3;
    GUIStyle gUIStyle;
    string str;
   // Transform CamTrs;
   // Transform LastCamTrs;

    [MenuItem("GrassEdit/GrassEdit")]
    public static void OpenWindow()
    {
        if (Application.isPlaying)//播放状态禁止编辑草
            return;
        else
        {
            Inspector_Edit InsEdit = EditorWindow.GetWindow<Inspector_Edit>();
            InsEdit.maxSize = new Vector2(320, 530);
            InsEdit.minSize = new Vector2(320, 530);
            InsEdit.Show();
        }
    }

    private void OnEnable()
    {
        gUIStyle = new GUIStyle();
        gUIStyle.normal.textColor = Color.red;
        warn1 = "   请先建立地形terrain!   无地形不能刷草。";
        warn2 = "  请选择规定的Material(类似名为grassMaterail的),\n若要自定义Material请点击下面的说明";
        warn3 = "";
        terrain = Terrain.activeTerrain;  //获得真实地形
       // CamTrs = Camera.main.transform;
        //LastCamTrs = SceneView.lastActiveSceneView.camera.transform;
        if (terrain == null) return;
        /// Instantiate(Resources.Load("TerrainExample"));
        foldout = new bool[materIndex];
        material = new Material[materIndex];
        ParentTmp = new GameObject("ParentTmp");
        meshes = new Mesh[materIndex];
        lastMeshObj = new Mesh[materIndex];
        lastMatObj = new Material[materIndex];
        GrassOBJS = new GameObject[materIndex];
        toggleDraw = new bool[materIndex];
        toggleView = new bool[materIndex];
        gameObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
        gameObject.transform.parent = ParentTmp.transform;
        grassCus = Instantiate(Resources.Load("GrassCus"), -Vector3.one * 1000, Quaternion.identity, ParentTmp.transform) as GameObject;
        grassCus.layer = 2;
        OffsetData = new PlayerData();
        OffsetData.density = new int[materIndex];
        OffsetData.radius = new int[materIndex];
        OffsetData.scale = new int[materIndex];
        for (int i = 0; i < materIndex; i++)
        {
            OffsetData.density[i] = 2; OffsetData.radius[i] = 2; OffsetData.scale[i] = 4;
        }
        object tmp = PlayerDataOperator.LoadPlayerData("myvars.data");
        if (tmp != null)
            OffsetData = (PlayerData)tmp;
        texture2D = new Texture2D(100, 100);
        for (int i = 0; i < materIndex; i++)
            toggleView[i] = true;
        for (int i = 0; i < materIndex; i++)
        {
            GrassOBJS[i] = Instantiate(Resources.Load("SceneCtrlObj"), -Vector3.one * 1000, Quaternion.identity, ParentTmp.transform) as GameObject;//每个GrassOBJS管理一种草
            GrassOBJS[i].GetComponent<EditInWindow>().OnEnableA();
            toggleDraw[i] = false;
        }
        for (int i = 0; i < materIndex; i++)
        {
            meshes[i] = (Instantiate(Resources.Load("grassPrb" + i.ToString()), ParentTmp.transform) as GameObject).GetComponent<MeshFilter>().sharedMesh;
            GrassOBJS[i].GetComponent<EditInWindow>().currentMesh = meshes[i];
            lastMeshObj[i] = meshes[i];
            material[i] = (Instantiate(Resources.Load("grassMat" + i), ParentTmp.transform) as GameObject).GetComponent<Renderer>().sharedMaterial;
            GrassOBJS[i].GetComponent<EditInWindow>().currentMaterial = material[i];
            lastMatObj[i] = material[i];
        }
        toggleDraw[0] = true;//开始至少能绘制一种草
        textures_kind = new Texture[3];
        textures_kind[0] = (Texture)Resources.Load("kind0") as Texture;
        textures_kind[1] = (Texture)Resources.Load("kind1") as Texture;
        textures_kind[2] = (Texture)Resources.Load("kind2") as Texture;
        textures_grass = new Texture[materIndex];
        for (int i = 0; i < materIndex; i++)
            textures_grass[i] = material[i].mainTexture;
        go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
        go.transform.parent = ParentTmp.transform;
        ParentTmp.transform.position = Vector3.one * -20000;
        readGrassDate();//读取上次保存的草数据,在开始编辑草之前显示出来
        ParentTmp.hideFlags = HideFlags.HideInHierarchy;//在Hierarchy面板中隐藏ParentTmp
        ma = grassCus.GetComponent<Renderer>().sharedMaterial;
        camera = Camera.main;
        if (camera == null)
        {
            camera = (Camera)FindObjectOfType(typeof(Camera));
            camera.tag = "MainCamera";
            camera.fieldOfView = 45;
        }
        else camera.fieldOfView = 45;
        Transform gameTrs = camera.transform.Find("InstancedIndirectComputeTest(Clone)");
        if (gameTrs == null)
            Instantiate(Resources.Load("InstancedIndirectComputeTest"), camera.transform);

        str = "1、刷草---鼠标左键,删除草---SHIFT+鼠标左键.\n" +
    "2、mesh可以任选,material只能选择特定的(类似名为grassMaterail的),但也可自定义material,只需要将material的shader选择为Instanced/InstancedIndirectCompute,然后把主贴图和噪声贴图选择好即可。\n" +
    "3、目前暂时只能刷草,待日后增加刷石头和树的功能.\n" +
    "4、草阴影在编辑时可能没有,在运行时会有(Game屏幕左上角可控制),关闭阴影可提高帧率约25%";
        ParentTmp.transform.position = new Vector3(-20000, -20000, -20000);
        //CamTrs.position = LastCamTrs.position - LastCamTrs.forward;//使GameCamera跟随SceneCamera,避免误判无显示
        //CamTrs.rotation = LastCamTrs.rotation;
    }

    private void OnInspectorUpdate()
    {
        if (terrain == null)
            return;
        if (go != null)
            go.transform.Rotate(Vector3.up, 1);
        EditorApplication.playmodeStateChanged = delegate ()
        {
            Close();
        };
        for (int i = 0; i < materIndex; i++)
        {
            if (GrassOBJS[i] != null)
                GrassOBJS[i].GetComponent<EditInWindow>().bol = toggleDraw[i];//控制是否绘制该种草?
        }
        for (int i = 0; i < materIndex; i++)
        {
            GrassOBJS[i].GetComponent<EditInWindow>().scale = OffsetData.scale[i];//控制草体积
            GrassOBJS[i].GetComponent<EditInWindow>().density = OffsetData.density[i];//控制草密度
            GrassOBJS[i].GetComponent<EditInWindow>().radius = OffsetData.radius[i];//控制草范围大小
            GrassOBJS[i].GetComponent<EditInWindow>().windSpeed = OffsetData.WindSpeed;//控制草范围大小
        }
        camera.farClipPlane = OffsetData.ViewDistance;
    }
    private void OnDestroy()
    {
        if (terrain == null) return;
        PlayerDataOperator.SavePlayerData("myvars.data", OffsetData);
        for (int i = 0; i < materIndex; i++)
        {
            List<xyzwPos> listPos = new List<xyzwPos>();
            listPos.Clear();
            GrassOBJS[i].GetComponent<EditInWindow>().virtuTerrain.GetTotalAddPos(listPos);
            BinaryFormatter bf = new BinaryFormatter();
            if (File.Exists(Application.streamingAssetsPath + "/" + System.Enum.GetName(typeof(Grass_name.grassName), i) + ".data"))
            {
                File.Delete(Application.streamingAssetsPath + "/" + System.Enum.GetName(typeof(Grass_name.grassName), i) + ".data");
            }
            FileStream file = File.Create(Application.streamingAssetsPath + "/" + System.Enum.GetName(typeof(Grass_name.grassName), i) + ".data");
            bf.Serialize(file, listPos);
            file.Close();
        }
        for (int i = 0; i < materIndex; i++)
            GrassOBJS[i].GetComponent<EditInWindow>().OnDisableA();//
        DestroyImmediate(go);
        DestroyImmediate(ParentTmp);
    }

    void readGrassDate()//读取上次保存的草数据,在开始编辑草之前显示出来
    {
        for (int j = 0; j < Grass_name.grassNameCount; j++)
        {
            EditInWindow editInWindow = GrassOBJS[j].GetComponent<EditInWindow>();
            if (!File.Exists(Application.streamingAssetsPath + "/" + System.Enum.GetName(typeof(Grass_name.grassName), j) + ".data"))
                continue;
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.streamingAssetsPath + "/" + System.Enum.GetName(typeof(Grass_name.grassName), j) + ".data", FileMode.Open);
            List<xyzwPos> listPos = (List<xyzwPos>)bf.Deserialize(file);
            Vector3 vector3 = new Vector3();
            for (int i = 0; i < listPos.Count; i++)
            {
                vector3.x = listPos[i].x;
                vector3.y = listPos[i].y;
                vector3.z = listPos[i].z;
                editInWindow.virtuTerrain.addPos(vector3, listPos[i].w, false);
            }
            file.Close();
        }
    }

    void OnGUI()
    {
        //CamTrs.position = LastCamTrs.position - LastCamTrs.forward;//使GameCamera跟随SceneCamera,避免误判无显示
        //CamTrs.rotation = LastCamTrs.rotation;
        GUILayout.TextArea(warning, gUIStyle);
        if (terrain == null)
        {
            warning = warn1;
            return;
        }
        else warning = warn3;
        HitPos = GrassOBJS[0].GetComponent<EditInWindow>().hit;
        grassCus.transform.position = HitPos.point + Vector3.up * 0.2f;
        grassCus.transform.LookAt(HitPos.point - HitPos.normal.normalized);
        EditorGUILayout.Space();
        EditorGUILayout.Space();
        help = EditorGUILayout.Foldout(help, "说明");//可折叠标签
        int _help = help == true ? 1 : 0;
        if (EditorGUILayout.BeginFadeGroup(_help))
            GUILayout.TextArea(str);
        EditorGUILayout.EndFadeGroup();
        scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, false, GUILayout.Width(320), GUILayout.Height(400));

        for (int i = 0; i < materIndex; i++)
        {
            EditorGUILayout.Space();
            EditorGUILayout.BeginHorizontal(GUILayout.Width(2), GUILayout.MinHeight(2));//开始水平布局
            EditorGUILayout.Space();
            foldout[i] = EditorGUILayout.Foldout(foldout[i], "草" + i.ToString());//可折叠标签
            toggleDraw[i] = GUILayout.Toggle(toggleDraw[i], textures_grass[i], GUILayout.Width(50), GUILayout.MaxWidth(50), GUILayout.Height(50), GUILayout.MaxHeight(50));//单选开关(绘制)
            EditorGUILayout.Space();
            EditorGUILayout.Space();
            toggleView[i] = GUILayout.Toggle(toggleView[i], "可见");//单选开关(可见性)
            GrassOBJS[i].GetComponent<EditInWindow>().viewAble = toggleView[i];
            EditorGUILayout.Space();
            // GUILayout.Label("一键种草");
            if (GUILayout.Button("一键种", GUILayout.Width(40), GUILayout.MaxWidth(100), GUILayout.Height(18), GUILayout.MaxHeight(18)))
                GrassOBJS[i].GetComponent<EditInWindow>().SpeedCreadeGrass();
            //GUILayout.Label("一键删除");
            if (GUILayout.Button("一键删", GUILayout.Width(40), GUILayout.MaxWidth(100), GUILayout.Height(18), GUILayout.MaxHeight(18)))
                GrassOBJS[i].GetComponent<EditInWindow>().virtuTerrain.deleteAllPos();
            EditorGUILayout.EndHorizontal();//结束水平布局
            if (foldout[i])//折叠
            {
                int m = foldout[i] == true ? 1 : 0;
                if (EditorGUILayout.BeginFadeGroup(m))
                {
                    meshes[i] = (Mesh)EditorGUILayout.ObjectField("   选择Mesh", meshes[i], typeof(Mesh), false);
                    if (meshes[i] == null)
                        meshes[i] = lastMeshObj[i];
                    if (!meshes[i].Equals(lastMeshObj[i]))
                    {
                        GrassOBJS[i].GetComponent<EditInWindow>().currentMesh = meshes[i];
                        gameObject.GetComponent<MeshFilter>().mesh = meshes[i];
                        PrefabUtility.CreatePrefab("Assets/Resources/grassPrb" + i.ToString() + ".prefab", gameObject);
                        lastMeshObj[i] = meshes[i];
                    }
                    material[i] = (Material)EditorGUILayout.ObjectField("   选择Material", material[i], typeof(Material), false);
                    if (material[i] == null)
                        material[i] = lastMatObj[i];
                    if (material[i].shader.name.CompareTo("InstancedIndirectCompute") == 1)
                    {
                        material[i] = lastMatObj[i];
                        warning = warn2;
                    }
                    else warning = warn3;
                    if (!material[i].Equals(lastMatObj[i]))
                    {
                        GrassOBJS[i].GetComponent<EditInWindow>().currentMaterial = material[i];
                        texture2D = (Texture2D)material[i].mainTexture;
                        textures_grass[i] = texture2D;
                        gameObject.GetComponent<Renderer>().material = material[i];
                        PrefabUtility.CreatePrefab("Assets/Resources/grassMat" + i.ToString() + ".prefab", gameObject);
                        lastMatObj[i] = material[i];
                    }
                    EditorGUILayout.BeginHorizontal(GUILayout.Width(300), GUILayout.MinHeight(10));//开始水平布局
                    GUILayout.Label("    刷草密度(/删草)", GUILayout.MinWidth(136));
                    OffsetData.density[i] = EditorGUILayout.IntSlider(OffsetData.density[i], 1, 10);//刷草密度
                    EditorGUILayout.EndHorizontal();//结束水平布局
                    EditorGUILayout.BeginHorizontal(GUILayout.Width(300), GUILayout.MinHeight(10));//开始水平布局
                    GUILayout.Label("    刷草范围(/删草)", GUILayout.MinWidth(136));
                    OffsetData.radius[i] = EditorGUILayout.IntSlider(OffsetData.radius[i], 1, 20);//刷草半径
                    EditorGUILayout.EndHorizontal();//结束水平布局
                    EditorGUILayout.BeginHorizontal(GUILayout.Width(300), GUILayout.MinHeight(10));//开始水平布局
                    GUILayout.Label("    草体积", GUILayout.MinWidth(136));
                    OffsetData.scale[i] = EditorGUILayout.IntSlider(OffsetData.scale[i], 1, 5);//草缩放
                    EditorGUILayout.EndHorizontal();//结束水平布局choose
                }
                EditorGUILayout.EndFadeGroup();
            }
            if (toggleDraw[i])//控制草光标颜色和半径
            {
                grassCusRadius += OffsetData.radius[i];
                grassCusdensity += OffsetData.density[i];
                choose++;
            }
        }
        if (choose != 0)
        {
            float G = grassCusRadius / choose;
            grassCus.transform.localScale = new Vector3(G, G, 0) * 3;
            G = grassCusdensity / choose;
            ma.SetFloat("_AlphaScale", G * 0.08f);
        }
        grassCusRadius = 0;
        grassCusdensity = 0;
        choose = 0;
        GUILayout.EndScrollView();
        EditorGUILayout.BeginHorizontal(GUILayout.Width(5), GUILayout.MinHeight(5));//开始水平布局
        GUILayout.Label("                   ");
        if (GUILayout.Button("全部种", GUILayout.Width(50), GUILayout.Height(25)))
            for (int i = 0; i < materIndex; i++)
                GrassOBJS[i].GetComponent<EditInWindow>().SpeedCreadeGrass();
        if (GUILayout.Button("全部删", GUILayout.Width(50), GUILayout.Height(25)))
            for (int i = 0; i < materIndex; i++)
                GrassOBJS[i].GetComponent<EditInWindow>().virtuTerrain.deleteAllPos();
        EditorGUILayout.EndHorizontal();//结束水平布局
        EditorGUILayout.Space();
        EditorGUILayout.Space();
        EditorGUILayout.Space();
        EditorGUILayout.BeginHorizontal(GUILayout.Width(300), GUILayout.MinHeight(10));//开始水平布局
        EditorGUILayout.Space();
        GUILayout.Label("风力大小", GUILayout.MinWidth(80), GUILayout.Width(60));
        OffsetData.WindSpeed = EditorGUILayout.IntSlider(OffsetData.WindSpeed, 1, 5);//风大小
        EditorGUILayout.EndHorizontal();//结束水平布局
        EditorGUILayout.BeginHorizontal(GUILayout.Width(300), GUILayout.MinHeight(10));//开始水平布局
        EditorGUILayout.Space();
        GUILayout.Label("视野(米)", GUILayout.MinWidth(80), GUILayout.Width(60));
        OffsetData.ViewDistance = EditorGUILayout.IntSlider(OffsetData.ViewDistance, 50, 2000);//视距
        EditorGUILayout.EndHorizontal();//结束水平布局
        Repaint();
    }
}
#endif

3、EditInWindow.cs(将地形划分为多块,某块有草的数量变化时只更新那一块的数据,提高效率。)

using UnityEngine;
using UnityEditor;
/// <summary>
/// 将地形划分为多块,某块有草的数量变化时只更新那一块的数据,提高效率。
/// </summary>
#if UNITY_EDITOR
[ExecuteInEditMode]
public class EditInWindow : MonoBehaviour
{
    private int terraimRow = 12;        //虚拟地形竖直分块
    private int terraimColumn = 12;      //虚拟地形水平分块
    public int subMeshIndex = 0;
    private int[] cachedInstanceCount;
    private ComputeBuffer[] positionBuffer;
    private ComputeBuffer[] argsBuffers;
    private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
    public xyzwPos[] v4 = new xyzwPos[1000];
    private Terrain terrain;
    private bool bo = false;
    public bool bol = false;//控制是否绘制草(Inspector传入)
    public virtualTerrain virtuTerrain;
    Material[] instanceMaterial;
    GameObject go;
    int n;//总块数
    string terrainName;
    float TerrainWidth;
    float TerrainLength;
    Texture2D GrassNoise;
    GameObject grassData;
    public bool viewAble = true;
    public Material currentMaterial = null;
    Material lastMaterial = null;
    public Mesh currentMesh = null;
    public int scale;//草大小(来自inspector_Edit.cs)
    public int density = 5;//刷草密度(来自inspector_Edit.cs)
    public int radius = 3;//刷草的半径(来自inspector_Edit.cs)
    public int windSpeed = 3;//风大小(来自inspector_Edit.cs)
    private GameObject GrassCus;
    public RaycastHit hit;

    public void OnEnableA()
    {
        terrain = Terrain.activeTerrain;                                         //真实地形
        if (terrain != null)
            terrainName = terrain.name;
        TerrainWidth = terrain.terrainData.size.x;
        TerrainLength = terrain.terrainData.size.z;
        virtuTerrain = new virtualTerrain(TerrainWidth, TerrainLength, terraimRow, terraimColumn);//虚拟地形
        n = terraimRow * terraimColumn;//总块数
        GrassNoise = Resources.Load("grassNoise256") as Texture2D;
        cachedInstanceCount = new int[n];
        SceneView.onSceneGUIDelegate -= OnSceneGUI;
        SceneView.onSceneGUIDelegate += OnSceneGUI;
        positionBuffer = new ComputeBuffer[n];
        argsBuffers = new ComputeBuffer[n];
        for (int i = 0; i < n; i++)
            argsBuffers[i] = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
        currentMesh = new Mesh();
        instanceMaterial = new Material[n];
        UpdateBuffers(0);

    }
    private void Update()
    {
        if (!Equals(lastMaterial, currentMaterial))
        {
            for (int i = 0; i < n; i++)
                instanceMaterial[i] = Instantiate(currentMaterial);
            lastMaterial = currentMaterial;
        }

        for (int i = 0; i < n; i++)//每帧都绘制
        {
            UpdateBuffers(i);
            if (virtuTerrain.perPiece[i].pos.Count == 0 || viewAble == false)
                continue;
            instanceMaterial[i].SetFloat("_WindSpeed", windSpeed);
            Graphics.DrawMeshInstancedIndirect(currentMesh, subMeshIndex, instanceMaterial[i],
                new Bounds(Vector3.zero, new Vector3(2000.0f, 2000.0f, 2000.0f)), argsBuffers[i]);
        }
        SceneView.RepaintAll();
    }
    void UpdateBuffers(int number)//某块有草的数量变化时只更新那一块的数据
    {
        if (virtuTerrain.perPiece[number].pos.Count == 0) return;
        if (cachedInstanceCount[number] == virtuTerrain.perPiece[number].pos.Count)
        {
            instanceMaterial[number].SetBuffer("positionBuffer", positionBuffer[number]);
            return;
        }
        subMeshIndex = Mathf.Clamp(subMeshIndex, 0, currentMesh.subMeshCount - 1);
        if (positionBuffer[number] != null)
            positionBuffer[number].Release();
        positionBuffer[number] = new ComputeBuffer(virtuTerrain.perPiece[number].pos.Count, 16);
        instanceMaterial[number].SetBuffer("positionBuffer", positionBuffer[number]);
        positionBuffer[number].SetData(virtuTerrain.perPiece[number].pos);
        args[0] = (uint)currentMesh.GetIndexCount(subMeshIndex);
        args[1] = (uint)virtuTerrain.perPiece[number].pos.Count;
        args[2] = (uint)currentMesh.GetIndexStart(subMeshIndex);
        args[3] = (uint)currentMesh.GetBaseVertex(subMeshIndex);
        argsBuffers[number].SetData(args);
        cachedInstanceCount[number] = virtuTerrain.perPiece[number].pos.Count;
    }

    void OnSceneGUI(SceneView sceneView)//绘制和删除草
    {
        int controlID = GUIUtility.GetControlID(FocusType.Passive);//使scene视图不能选择物体
        HandleUtility.AddDefaultControl(controlID);
        if (Event.current.button != 1 && Event.current.button != 2 && Event.current.rawType == EventType.MouseDown)
            bo = true;
        if (Event.current.button != 1 && Event.current.button != 2 && Event.current.rawType == EventType.MouseUp)
            bo = false;
        Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
        bool _bool1 = false;
        bool _bool = false;
        if (_bool = Physics.Raycast(ray, out hit))
        {
            if (hit.transform.name.CompareTo(terrainName) == 0)
                _bool1 = true;
        }
        if (Event.current.shift && bo && bol && _bool && _bool1)//清除草
        {
            virtuTerrain.minusPos(hit.point, radius * radius, density * 0.4f);
            return;
        }
        if (bo && bol && _bool && _bool1)    //增加草
        {
            for (int i = 0; i < density; i++)
            {
                Vector2 v2 = UnityEngine.Random.insideUnitCircle * radius;
                v4[i].x = v2.x + hit.point.x;
                v4[i].z = v2.y + hit.point.z;
                v4[i].y = terrain.SampleHeight(new Vector3(v4[i].x, 0, v4[i].z));//取此点对应的地形高度
                virtuTerrain.addPos(new Vector3(v4[i].x, v4[i].y, v4[i].z), UnityEngine.Random.Range(0.3f, scale), true);
            }
        }
        sceneView.Repaint();
    }

    public void SpeedCreadeGrass()//一键快速刷草
    {
        //  if (currentMaterial == null) return;
        for (int i = 0; i < 2000; i++)
        {
            float x = UnityEngine.Random.Range(0, TerrainWidth);
            float z = UnityEngine.Random.Range(0, TerrainLength);
            Ray ray = new Ray(new Vector3(x, 3000, z), Vector3.down);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 5000))
            {
                if (hit.transform.name.CompareTo(terrainName) == 0)
                {
                    float _x = hit.point.x;
                    float _z = hit.point.z;
                    if (GrassNoise.GetPixel((int)x, (int)z).r > 0.05f) continue;//按噪声图像素颜色值过滤
                    float _y = terrain.SampleHeight(new Vector3(_x, 0, _z));//取此点对应的地形高度
                    virtuTerrain.addPos(new Vector3(_x, _y, _z), UnityEngine.Random.Range(0.3f, scale), true);//加入旋转
                }
            }
        }
    }

    public void OnDisableA()
    {
        SceneView.onSceneGUIDelegate -= OnSceneGUI;
        for (int i = 0; i < n; i++)
        {
            if (positionBuffer[i] != null)
                positionBuffer[i].Release();
            positionBuffer[i] = null;
            if (argsBuffers[i] != null)
                argsBuffers[i].Release();
            argsBuffers[i] = null;
        }
    }
}
#endif

4、InstancedIndirectCompute.shader(草专用shader,不能用unity自带shader)

Shader "Instanced/InstancedIndirectCompute" 
{
	Properties{
		_MainTex("Albedo (RGB)", 2D) = "white" {}
		_WindTex ("WindMap", 2D) = "white" {}
		_Glossiness("Smoothness", Range(0,1)) = 0.0
		_Metallic("Metallic", Range(0,1)) = 0.0
        [HDR]_Color ("Color", Color) = (0,1,0,1)
        _Height("Height",Float)=1
		_ScaleVH("Scale",Float)=1
		 _WindSpeed("WindVelocity",Float)=0
        _WindSize("WinSize",Float)=10
         _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
	}
		SubShader{
				Tags{"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"} 

		LOD 200
		 Cull Off

		CGPROGRAM
		  #pragma surface surf Standard addshadow alphatest:_Cutoff  vertex:vert
		 // #pragma surface surf Standard addshadow  vertex:vert
		#pragma multi_compile_instancing 
		#pragma instancing_options procedural:setup
        #include "UnityPBSLighting.cginc"

		sampler2D _MainTex;
		 sampler2D _WindTex;
        float _Height;
        float _WindSpeed;
        float _WindSize;
        float _ScaleVH;
		
		struct Input {
			float2 uv_MainTex;
		};
	    half _Glossiness;
	    half _Metallic;
	   float4 _Color;

		#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
			StructuredBuffer<float4> positionBuffer;
			StructuredBuffer<float4> colorBuffer;
		#endif
		      void rotate2D(inout float2 v, float r)
        {
            float s, c;
            sincos(r, s, c);
            v = float2(v.x * c - v.y * s, v.x * s + v.y * c);
        }
	void setup()
	{
#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
		/// Positions are calculated in the compute shader.
		/// here we just use them.
		float4 position = positionBuffer[unity_InstanceID];
		float scale = position.w*_ScaleVH;

		unity_ObjectToWorld._11_21_31_41 = float4(scale, 0, 0, 0);
		unity_ObjectToWorld._12_22_32_42 = float4(0, scale, 0, 0);
		unity_ObjectToWorld._13_23_33_43 = float4(0, 0, scale, 0);
		unity_ObjectToWorld._14_24_34_44 = float4(position.xyz, 1);
		unity_WorldToObject = unity_ObjectToWorld;
		unity_WorldToObject._14_24_34 *= -1;
		unity_WorldToObject._11_22_33 = 1.0f / unity_WorldToObject._11_22_33;
#endif
	}

	      void RotateFixed(inout appdata_full v,float rote){
            float2 rv2=float2(v.vertex.x,v.vertex.z);
            rotate2D(rv2,rote);
            v.vertex.x=rv2.x;
            v.vertex.z=rv2.y;
        }
	 float GetWindWave(float2 position,float height){
//每个顶点摆动大小受风力图采样、高度、位置同时影响而变得随机
            float4 p=tex2Dlod(_WindTex,float4(position/_WindSize+float2(_Time.x*_WindSpeed+height*.01,0),0.0,0.0)); 
            return (height*(p.r-.5));
}
      void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
            #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
            float4 data = positionBuffer[unity_InstanceID];
            //旋转 
            RotateFixed(v,(data.x+data.y+data.z+data.w*10000));
            //获取风
            float w=GetWindWave(data.xz,v.vertex.y);
            //顶点水平移动
            v.vertex.x+=w;
            v.vertex.y+=w*.4;            
            #endif
        }
	void surf(Input IN, inout SurfaceOutputStandard o) 
	{
		float4 col = 1.0f;

#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
		col = colorBuffer[unity_InstanceID];
#else
		col = float4(0, 0, 1, 1);
#endif
		fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
		o.Albedo = c.rgb*_Color;
		o.Metallic = _Metallic;
		o.Smoothness = _Glossiness;
		o.Alpha = c.a;
	}
	ENDCG
	}
	FallBack "Diffuse"
}

 

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值