如果把草当成一个一个的模型的话,我们在一个平面上铺满10000个草并且让他和一些物体进行交互的,如果用传统的做法,我们把每一个草上面挂载一个脚本的话,运行的时候你就会发现,这样帧率其实并不高的,在一个update里面每帧访问一个数组长度为10000的数组他的帧率其实都不会很高的,更何况我们还需要草动起来且还需要有一定的交互能力,如果纯粹用cpu来模拟的话可能会比较吃力,所以这里打算用GPU来模拟大量粒子运动和交互。unity提供了一种compute shader来处理大量计算。GPU在大量运算时候它的效率是远远比CPU高好多倍的,所以一般在做游戏的时候我们其实是大量的使用了CPU而GPU的使用效率其实比较低的往往一个游戏中GPU很闲CPU很忙,首先贴出我们需要做出来的效果。

因为截图的缘故 看着草没动 其实草会摆动的。这个就是我们最终的效果了,首先这个效果模型我们是没有的所以草的Mesh我们得重新定义

最终得网格就如图所示,蓝色代表顶点,绿色边构成得三角形代表网格顶点绘制得顺序。顶点的添加顺序是从下到上,从左到右,所以顶点的下标依次从0到6,所以顶点的添加顺序为

0,2,3 , 0,3,1, 2,4,5 , 2,5,3 ,4,6,5。接下来就是草的摆动的及擦除效果就用computer shader来处理

具体的shader只是来接受computer shader处理完了的数据来显示出来,所以先写shader在写compute shader。

Shader "Custom/Grass"{Properties{_Color("Color Tint",Color) = (1.0,1.0,1.0,1.0)}SubShader{Pass{Tags { "RenderType"="Opaque" }Cull OffCGPROGRAM#include "UnityCG.cginc"#pragma vertex vert#pragma fragment frag#pragma multi_compile_instancing#pragma instancing_options procedural:setup#pragma target 3.5#if SHADER_TARGET >= 35 && (defined(SHADER_API_D3D11) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_PSSL) || defined(SHADER_API_SWITCH) || defined(SHADER_API_VULKAN) || (defined(SHADER_API_METAL) && defined(UNITY_COMPILER_HLSLCC)))#define SUPPORT_STRUCTUREDBUFFER#endif#if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) && defined(SUPPORT_STRUCTUREDBUFFER)#define ENABLE_INSTANCING#endifuniform fixed4 _Color;float4x4 _LocalToWorld;float4x4 _WorldToLocal;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;float4 texcoord : TEXCOORD0;uint vid : SV_VertexID;UNITY_VERTEX_INPUT_INSTANCE_ID};struct v2f {float4 pos : SV_POSITION;fixed3 color : COLOR0;uint fid: UINT;};StructuredBuffer<float3> _PositionBuffer;StructuredBuffer<float3> _VerticlesBuffer;StructuredBuffer<int> _DiscardBuffer;void setup(){unity_ObjectToWorld = _LocalToWorld;unity_WorldToObject = _WorldToLocal;}v2f vert(a2v v,uint instanceID : SV_InstanceID) {v2f o;o.fid = instanceID;float3 offset = float3(0,0,0);offset = _PositionBuffer[instanceID];int id = instanceID * 7 + v.vid;float3 p = _VerticlesBuffer[id];o.pos = UnityObjectToClipPos(p + float4(offset.x,offset.y,offset.z,0));float4 bColor = float4(74.0/255.0,192.0/255.0,74.0/255.0,1);float4 uColor = float4(210.0/255.0,224.0/255.0,39.0/255.0,1);o.color = lerp(bColor,uColor,v.texcoord.y);return o;}fixed4 frag(v2f i) : SV_Target{if(_DiscardBuffer[i.fid] == 0){discard;}return fixed4(i.color,1.0);}ENDCG}}
}

shader中

StructuredBuffer<float3> _PositionBuffer;
StructuredBuffer<float3> _VerticlesBuffer;
StructuredBuffer<int> _DiscardBuffer;

StructuredBuffer这个定义可以理解为数组,_PositionBuffer主要是用来存储所有草的位置,_VerticlesBuffer主要用来存储所有所有草上的顶点位置(这个位置为本地坐标),_DiscardBuffer主要用来存储所有草的显示状态,为0代表不显示,为1代表显示。上图有一个顶点绘制顺序图 假定那张图上的顶点在unity坐标xy平面,那么现在弯曲的话可以想象除了1,2  3  4顶点外 5 6 7顶点会向着z轴弯曲,第一步 3 4 5 6 7所构成的一个大的三角面先向着z轴弯曲,然后 5 6 7构成的三角面再向着z轴弯曲。shader里面旋转的话得单独写,下面一段代码是旋转得。

float4 GetQuaternion(float3 rotateAxis,float angle)
{float thetaOver2 = angle * 0.5;float sinthetaOver2 = sin(thetaOver2);float w = cos(thetaOver2);float x = rotateAxis.x * sinthetaOver2;float y = rotateAxis.y * sinthetaOver2;float z = rotateAxis.z * sinthetaOver2;return float4(x,y,z,w);
}float3 QuaternionXDir(float3 p,float3 axis,float angle)
{float4 rotation = GetQuaternion(axis,angle);float num1 = rotation.x * 2;float num2 = rotation.y * 2;float num3 = rotation.z * 2;float num4 = rotation.x * num1;float num5 = rotation.y * num2;float num6 = rotation.z * num3;float num7 = rotation.x * num2;float num8 = rotation.x * num3;float num9 = rotation.y * num3;float num10 = rotation.w * num1;float num11 = rotation.w * num2;float num12 = rotation.w * num3;float x =  ((1.0 - (num5 + num6)) * p.x + (num7 - num12) *  p.y + ( num8 + num11) *  p.z);float y =  ((num7 + num12) * p.x + (1.0 - (num4 + num6)) *  p.y + (num9 - num10) *  p.z);float z =  ((num8 - num11) * p.x + (num9 + num10) *  p.y + (1.0 - (num4 + num5)) *  p.z);return float3(x,y,z);
}

上面代码得功能主要是一个向量绕着一个轴旋转特定得角度。首先创建compute shader得时候会有一个内核,这里将其定义为

[numthreads(256,1,1)]
void Grass (uint id : SV_DispatchThreadID)

方法中的id可以理解为每棵草在一个草数组中的索引,在这个内核里面我们需要计算出每一个草的顶点相对位置这个位置是一个相对位置而不是绝对位置。首先假定最下面的坐标为a1和a2,假定a1和a2的距离为1,定义a3和a4的距离为0.5,定义dir12为a1-a2,dir34为a3-a4这里dir12和dir34向量是平行的 这里假定dir12到dir34向量的距离为0.1,dir12和dir34所在平面垂直z轴,所以我们额可以求出a3和a4顶点坐标。

那么后面的节点就依次类推了所以这里我就直接贴出这个后面顶点的计算方式了。

#pragma kernel Grass#include "UnityCG.cginc"RWStructuredBuffer<float3> PositionBuffer;
RWStructuredBuffer<float3> DirBuffer;
RWStructuredBuffer<float3> VerticlesBuffer;
RWStructuredBuffer<int> DiscardBuffer;CBUFFER_START(Params)float4 EraserPos;float  TotlaTime;
CBUFFER_ENDfloat4 GetQuaternion(float3 rotateAxis,float angle)
{float thetaOver2 = angle * 0.5;float sinthetaOver2 = sin(thetaOver2);float w = cos(thetaOver2);float x = rotateAxis.x * sinthetaOver2;float y = rotateAxis.y * sinthetaOver2;float z = rotateAxis.z * sinthetaOver2;return float4(x,y,z,w);
}float3 QuaternionXDir(float3 p,float3 axis,float angle)
{float4 rotation = GetQuaternion(axis,angle);float num1 = rotation.x * 2;float num2 = rotation.y * 2;float num3 = rotation.z * 2;float num4 = rotation.x * num1;float num5 = rotation.y * num2;float num6 = rotation.z * num3;float num7 = rotation.x * num2;float num8 = rotation.x * num3;float num9 = rotation.y * num3;float num10 = rotation.w * num1;float num11 = rotation.w * num2;float num12 = rotation.w * num3;float x =  ((1.0 - (num5 + num6)) * p.x + (num7 - num12) *  p.y + ( num8 + num11) *  p.z);float y =  ((num7 + num12) * p.x + (1.0 - (num4 + num6)) *  p.y + (num9 - num10) *  p.z);float z =  ((num8 - num11) * p.x + (num9 + num10) *  p.y + (1.0 - (num4 + num5)) *  p.z);return float3(x,y,z);
}[numthreads(256,1,1)]
void Grass (uint id : SV_DispatchThreadID)
{float length1 = 0.3;float width = 0.03;float3 upDir = float3(0,1,0);float3 p1 = - DirBuffer[id] * width *3;float3 p2 =  DirBuffer[id] * width *3;float3 p3 = - DirBuffer[id] * width*2 + upDir * length1;float3 p4 =  DirBuffer[id] * width*2 + upDir * length1;float3 p34 = (p3 + p4) / 2;float3 o = float3(-7, 0 ,7) - PositionBuffer[id];float d = o.x*o.x + o.y*o.y + o.z*o.z;float angle = 5 * (sin(TotlaTime+ d) + sin(2*TotlaTime+d));float3 upDir1 = QuaternionXDir(upDir, DirBuffer[id], angle * 0.01745329); float3 normal1 = cross(DirBuffer[id] , upDir1); float  dir1 = QuaternionXDir(upDir1, normal1, -90 * 0.01745329);float3 p5 = p34 - DirBuffer[id] * width  + upDir1 * length1 * 2;float3 p6 = p34 + DirBuffer[id] * width  + upDir1 * length1 * 2;float3 p56 = (p5 + p6) / 2;float3 upDir2 = QuaternionXDir(upDir1,dir1, 10 * 0.01745329); float3 p7 = p56 + upDir2 * length1 * 3;VerticlesBuffer[0+id * 7] = p1;VerticlesBuffer[1+id * 7] = p2;VerticlesBuffer[2+id * 7] = p3;VerticlesBuffer[3+id * 7] = p4;VerticlesBuffer[4+id * 7] = p5;VerticlesBuffer[5+id * 7] = p6;VerticlesBuffer[6+id * 7] = p7;float3 offset = float3(EraserPos.x,EraserPos.y,EraserPos.z) - PositionBuffer[id];float dis = offset.x * offset.x + offset.y * offset.y + offset.z*offset.z;if(dis < 0.5 * 0.5){DiscardBuffer[id] = 0;}}

最后一段代码是判断擦除的,计算方式就一定半径内擦除这种计算方式应该比较简单这里不做详细叙述了。最后就是渲染方法了——Graphics.DrawMeshInstancedIndirect,api具体的参数我就不做赘述了。比较简单这里就直接贴出脚本吧,脚本里面就是cpu和gpu交互的代码,compute计算出来一些数据然后传给shader,最后把它渲染出来,所以这个类就是一个统筹所有资源的一个类。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;public class Grass : MonoBehaviour
{[SerializeField] private float _width;[SerializeField] private float _height;[SerializeField] private Transform _eraser;[SerializeField] private Material _grassMaterial;[SerializeField] private ComputeShader _grassCompute;[SerializeField] private int _grassCount;private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };private ComputeBuffer _drawArgsBuffer;private ComputeBuffer _positionBuffer;private ComputeBuffer _dirBuffer;private ComputeBuffer _verticlesBuffer;private ComputeBuffer _discardBuffer;private Mesh _originMesh;private MaterialPropertyBlock _props;private bool _isSet;private int[] _discardInts;private Vector3[] _posList;private float _totalTime;private static readonly int KThreadCount = 256;private void Start(){_props = new MaterialPropertyBlock();_props.SetFloat("_UniqueID", Random.value);InitiMesh();InitiBuff();_isSet = false;}private void Update(){var kernel = _grassCompute.FindKernel("Grass");int groupX = _grassCount / KThreadCount;_totalTime += Time.deltaTime * 2f;if (_totalTime >= Mathf.PI * 2)_totalTime -= Mathf.PI * 2;if (!_isSet){_grassCompute.SetBuffer(kernel, "PositionBuffer", _positionBuffer);_grassCompute.SetBuffer(kernel, "DirBuffer", _dirBuffer);_grassCompute.SetBuffer(kernel, "VerticlesBuffer", _verticlesBuffer);_grassCompute.SetBuffer(kernel, "DiscardBuffer", _discardBuffer);args[0] = (uint)_originMesh.GetIndexCount(0);args[1] = (uint)_grassCount;args[2] = (uint)_originMesh.GetIndexStart(0);args[3] = (uint)_originMesh.GetBaseVertex(0);_drawArgsBuffer.SetData(args);_isSet = true;                         _grassMaterial.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);_grassMaterial.SetMatrix("_WorldToLocal", transform.worldToLocalMatrix);_grassMaterial.SetBuffer("_PositionBuffer", _positionBuffer);_grassMaterial.SetBuffer("_VerticlesBuffer", _verticlesBuffer);}Vector3 t = _eraser.position;_grassCompute.SetFloat("TotlaTime", _totalTime);_grassCompute.SetVector("EraserPos", new Vector4(t.x, t.y, t.z, 0));_grassCompute.Dispatch(kernel, groupX, 1, 1);_grassMaterial.SetBuffer("_DiscardBuffer", _discardBuffer);Graphics.DrawMeshInstancedIndirect(_originMesh, 0, _grassMaterial,new Bounds(transform.position, transform.lossyScale * 5),_drawArgsBuffer);}private void OnDestroy(){_drawArgsBuffer.Release();_positionBuffer?.Release();_dirBuffer?.Release();_verticlesBuffer?.Release();_discardBuffer?.Release();}private void InitiBuff(){_drawArgsBuffer = new ComputeBuffer(_grassCount, 5 * sizeof(uint), ComputeBufferType.IndirectArguments);if (_positionBuffer == null){_posList = new Vector3[_grassCount];float minWidht = -_width / 2f;float maxWidth = _width / 2f;float minHight = -_height / 2f;float maxHeight = _height / 2f;for (int i = 0; i < _grassCount; i++){float x = Random.Range(minWidht, maxWidth);float z = Random.Range(minHight, maxHeight);_posList[i] = new Vector3(x, 0, z);}_positionBuffer = new ComputeBuffer(_grassCount, 4 * 3);_positionBuffer.SetData(_posList);}if (_dirBuffer == null){List<Vector3> dirList = new List<Vector3>();for (int i = 0; i < _grassCount; i++){Vector2 t1 = Random.insideUnitCircle;dirList.Add(new Vector3(t1.x, 0, t1.y).normalized);}_dirBuffer = new ComputeBuffer(_grassCount, 4 * 3);_dirBuffer.SetData(dirList);}if (_verticlesBuffer == null){_verticlesBuffer = new ComputeBuffer(_grassCount * 7, 4 * 3);}if (_discardBuffer == null){_discardBuffer = new ComputeBuffer(_grassCount, 4);_discardInts = new int[_grassCount];for (int i = 0; i < _grassCount; i++){_discardInts[i] = 1;}_discardBuffer.SetData(_discardInts);}}private void InitiMesh(){_originMesh = new Mesh();_originMesh.vertices = new Vector3[7];_originMesh.SetUVs(0, new List<Vector2>(){new Vector2(0,0), new Vector2(1,0),new Vector2(1/6f,1/3f), new Vector2(5/6f,1/3f),new Vector2(2/6f,2/3f), new Vector2(4/6f,2/3f),new Vector2(0.5f,1f)});_originMesh.SetIndices(new []{0,2,3 , 0,3,1, 2,4,5 , 2,5,3 ,4,6,5},MeshTopology.Triangles,0);_originMesh.UploadMeshData(true);}}

好了草就已经做完了,后面有什么问题可以联系本人qq:1850761495

Unity 草的制作相关推荐

  1. Unity 4 3 制作一个2D横版射击游戏 2

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在上一篇 ...

  2. swiftui动画之tab自定义切换动画_Unity动画系统详解1:在Unity中如何制作动画?

    摘要:在场景中加入动态的物体,可以让整个场景更加生动.真实.Unity场景中的物体可以通过制作动画,让物体动起来.简单的动画如物体的移动.旋转(比如旋转的风扇.闪烁不定的灯泡等),复杂的动画如游戏中角 ...

  3. Unity动画系统详解1:在Unity中如何制作动画?

    摘要:在场景中加入动态的物体,可以让整个场景更加生动.真实.Unity场景中的物体可以通过制作动画,让物体动起来.简单的动画如物体的移动.旋转(比如旋转的风扇.闪烁不定的灯泡等),复杂的动画如游戏中角 ...

  4. Unity - 使用Winform制作简易登录器

    Unity - 使用Winform制作简易登录器 Resolution Dialog PC.Mac环境下,Unity提供了一个简易的设置分辨率.图形性能的对话框,叫Resolution Dialog, ...

  5. 【Unity 2017.3 制作安卓2D单机手游】2.这就开始做

    这就开始做 关于Unity的介绍以及如何创建项目,我就不多说了,因为百度很容易.现在就开始直接做,因为我也是要边做边学,所以必定会有很多不足.大佬们觉得哪里不对,欢迎指正! 开发环境 windows1 ...

  6. unity小游戏制作之见缝插针

    unity小游戏制作之见缝插针 先看效果图这个游戏是一个入门的小游戏,主要技术就是圆盘的旋转,针跟随圆盘旋转以及分数累加等 游戏先拖入一个圆,更改格式为2D然后建立脚本使其转动 public clas ...

  7. Unity教程之-制作闪亮的星星Star(二):创建Shader

    继续上篇文章<Unity教程之-制作闪亮的星星Star(一):动态的创建Mesh>我们建立一个新的Shader将它命名为Star,然后写入以下代码. 什么是CGPROGRAM? Basic ...

  8. 【Unity Editor工具制作-文本转UTF-8编码、用WPS表格打开表格、用WPS表格打开】

    Unity Editor工具制作 文本转UTF-8编码 用NotePad打开 用WPS表格打开 文本转UTF-8编码 [MenuItem("Assets/文本转UTF-8编码")] ...

  9. 使用Unity的UGUI制作带时间头的相册

    Unity  UGUI相册制作并且加上时间头的效果 在介绍排版之前需要先了解一下Unity中的排版布局方式的三个组件 1.Horizontal Layout Group(水平布局) 1.Padding ...

最新文章

  1. ssh_config sshd_config 详解ssh_config sshd_config 详解
  2. git maven 一键部署_Jenkins实现一键部署maven项目
  3. 苹果自带的清理软件_清理苹果Mac系统垃圾用什么软件?
  4. maven工程拆分与聚合的思想
  5. 安卓端/iOS端如何播放4K分辨率的RTMP/RTSP流
  6. 前端开发 什么是网页 什么是html
  7. 云服务器zabbix server报错:Lack of free swap on Zabbix server
  8. 区块链 以太坊 全局变量 msg包括哪些字段
  9. 一周信创舆情观察(8.30~9.5)
  10. C语言二级题库带答案+解析
  11. 条码扫描枪的连接与使用方法
  12. ThreadAbortException问题
  13. 隐私泄露中的人性剖析
  14. [转载]当猫忧郁的时候
  15. STM32之简易GUI(多级菜单进阶版)
  16. Fiddler抓包,并修改请求数据
  17. 轻量级工作流引擎snaker 2.0.0
  18. 附件上传在IE中的问题
  19. Android初学习
  20. 银河麒麟和鸿蒙系统,国产银河麒麟操作系统V10发布 兼容300万APP厉害了

热门文章

  1. opencv将图像处理之后显示在label上(Mat转化为qimage)转换之后label显示全黑
  2. 如果VxRail要发朋友圈
  3. Matlab——常用函数的用法总结(部分直接摘自mathwork,持续更新)
  4. CentOS7和CentOS8 FreeSWITCH 1.10.7 简单图形化界面18--内网的讯时FXO网关SIP对接到内网的FreeSWITCH
  5. Docker-核心篇(1)-CentOS7安装Docker社区版
  6. 海豚调度的安装和使用
  7. 三星公司的开会流程--绝对值得一看
  8. ZOJ 1138 Symbolic Derivation
  9. 不可小视的head标签
  10. 3DMAX渲染AO(白膜)图的三种方法