先贴出一些具体问题的解决方式,供大家一起探讨:
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;
}
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);
}
}
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();
}
}
}
除了以上问题其实在制作插件过程中还遇到很多细节问题,如:mesh的制作、shader的调试、一些bug的排除、、、、都一一解决了,不过还有很多可以继续改进的地方,比如在播放状态下必须关闭编辑器,再次编辑又必须重新打开编辑器等,希望大佬能指点一下。
1、InstancedIndirectCompute.cs(功能见脚本中的标注)
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
using UnityEditor;
///
/// 在 Start()函数中一次性读取硬盘所有类别的草位置数据,并传递给computerBuffer,由于数据含有位置信息,
/// 所以可以在gpu的computerBuffer中根据这些信息过滤掉视线外的草, 仅传递可视部分数据给Materail显示。
///每帧中cpu只需提供当前需要显示的中心坐标即可,其他全由gpu完成,效率较高
/// 注意:1、判断视线内的块使用了简化的计算方法,不做商用效率优先:只判断每个需要渲染的像素是否在视椎体左右面之间,忽略上下判断。
/// 2、在传递给Materail时使用的是computerBuffer的Append方式。
/// 3、实验在同时显示5种草、开启实时阴影、约一百万面(视线内估计二十万),在关掉垂直同步时全屏显示可以达到200帧。
///
///
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 xyzwsList = new List();
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().mesh;
instanceMaterial = new Material[KindOfGrass];
for (int i = 0; i < KindOfGrass; i++)
{
instanceMaterial[i] = (Instantiate(Resources.Load("grassMat" + i), TempObj.transform) as GameObject).GetComponent().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;
///
/// 放一物体到场景并旋转
///
#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();
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().OnEnableA();
toggleDraw[i] = false;
}
for (int i = 0; i < materIndex; i++)
{
meshes[i] = (Instantiate(Resources.Load("grassPrb" + i.ToString()), ParentTmp.transform) as GameObject).GetComponent().sharedMesh;
GrassOBJS[i].GetComponent().currentMesh = meshes[i];
lastMeshObj[i] = meshes[i];
material[i] = (Instantiate(Resources.Load("grassMat" + i), ParentTmp.transform) as GameObject).GetComponent().sharedMaterial;
GrassOBJS[i].GetComponent().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().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().bol = toggleDraw[i];//控制是否绘制该种草?
}
for (int i = 0; i < materIndex; i++)
{
GrassOBJS[i].GetComponent().scale = OffsetData.scale[i];//控制草体积
GrassOBJS[i].GetComponent().density = OffsetData.density[i];//控制草密度
GrassOBJS[i].GetComponent().radius = OffsetData.radius[i];//控制草范围大小
GrassOBJS[i].GetComponent().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 listPos = new List();
listPos.Clear();
GrassOBJS[i].GetComponent().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().OnDisableA();//
DestroyImmediate(go);
DestroyImmediate(ParentTmp);
}
void readGrassDate()//读取上次保存的草数据,在开始编辑草之前显示出来
{
for (int j = 0; j < Grass_name.grassNameCount; j++)
{
EditInWindow editInWindow = GrassOBJS[j].GetComponent();
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 listPos = (List)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().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().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().SpeedCreadeGrass();
//GUILayout.Label("一键删除");
if (GUILayout.Button("一键删", GUILayout.Width(40), GUILayout.MaxWidth(100), GUILayout.Height(18), GUILayout.MaxHeight(18)))
GrassOBJS[i].GetComponent().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().currentMesh = meshes[i];
gameObject.GetComponent().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().currentMaterial = material[i];
texture2D = (Texture2D)material[i].mainTexture;
textures_grass[i] = texture2D;
gameObject.GetComponent().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().SpeedCreadeGrass();
if (GUILayout.Button("全部删", GUILayout.Width(50), GUILayout.Height(25)))
for (int i = 0; i < materIndex; i++)
GrassOBJS[i].GetComponent().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;
///
/// 将地形划分为多块,某块有草的数量变化时只更新那一块的数据,提高效率。
///
#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 positionBuffer;
StructuredBuffer 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"
}