基于Unity的植被刷工具

最近接手的一个需求是给美术同学提供一个刷植被工具,类似Unity地形的Paint Details.
之所以没有选择Unity自己的地形工具,是因为策划需求中需要动态让植被消失和显示,另外大批量草的优化自己控制相对好处理一些,当然还有Unity地形广受诟病的性能问题.

这里把实现过程做个简单的记录.
内容包括:编辑器开发,植被shader, 生成优化,显隐

先上结果
编辑器长这样:

刷完草后的效果:

动态显示消失:

编辑器部分:
编辑器部分逻辑比较简单,掌握一些基础的EditorAPI,然后根据自己设计的面板去罗列代码就可以。

面板核心代码:

public class PaintDetailsEW : EditorWindow
{//打开窗口[MenuItem("Window/Paint Details %g")]static void Open(){var window = (PaintDetailsEW) EditorWindow.GetWindowWithRect(typeof(PaintDetailsEW), new Rect(0, 0, 386,320), false, "Paint Detail");window.Show();Enable = true;       }void OnInspectorUpdate(){Repaint();}void OnGUI(){CurrentSelect = Selection.activeTransform;GUILayout.Space(20);GUILayout.BeginHorizontal();GUILayout.FlexibleSpace();GUILayout.BeginVertical("box", GUILayout.Width(347));GUILayout.BeginHorizontal();GUILayout.Label("Add Assets", GUILayout.Width(125));AddObject = (GameObject)EditorGUILayout.ObjectField("", AddObject, typeof(GameObject), true, GUILayout.Width(160));if (GUILayout.Button("+", GUILayout.Width(40))){for (int i = 0; i < 6; i++){ if (Plants[i] == null){Plants[i] = AddObject;break;}}}GUILayout.EndHorizontal();GUILayout.EndVertical();GUILayout.FlexibleSpace();GUILayout.EndHorizontal();for (int i = 0; i < 6; i++){if (Plants[i] != null)TexObjects[i] = AssetPreview.GetAssetPreview(Plants[i]) as Texture;else TexObjects[i] = null;}GUILayout.BeginHorizontal();GUILayout.FlexibleSpace();GUILayout.BeginVertical("box", GUILayout.Width(347));PlantSelect = GUILayout.SelectionGrid(PlantSelect, TexObjects, 6, "gridlist", GUILayout.Width(330), GUILayout.Height(55));GUILayout.BeginHorizontal();for (int i = 0; i < 6; i++){if (GUILayout.Button("—", GUILayout.Width(52))){Plants[i] = null;}}GUILayout.EndHorizontal();GUILayout.EndVertical();GUILayout.FlexibleSpace();GUILayout.EndHorizontal();GUILayout.BeginHorizontal();GUILayout.FlexibleSpace();GUILayout.BeginVertical("box", GUILayout.Width(347));GUILayout.BeginHorizontal();GUILayout.Label("Setting", GUILayout.Width(145));        GUILayout.EndHorizontal();brushSize = (int)EditorGUILayout.Slider("Brush Size", brushSize, 1, 36);scaleRandom = EditorGUILayout.Slider("Scale Random(+/-)", scaleRandom, 0.05f, 1f);density = (int)EditorGUILayout.Slider("Density", density, 1,10);GUILayout.EndVertical();GUILayout.FlexibleSpace();GUILayout.EndHorizontal();GUILayout.BeginHorizontal();GUILayout.FlexibleSpace();GUILayout.BeginVertical(GUILayout.Width(347));string btnText = IsHome ? "二级地图" : "领地地图";if (GUILayout.Button(btnText)){IsHome = btnText.Equals("领地地图");Editor = !IsHome;}......}
}

刷草核心代码:

[CustomEditor(typeof(PaintDetails))]
public class PaintDetailsExtends : Editor
{//Scene面板回调函数void OnSceneGUI(){if (PaintDetailsEW.Enable){Planting();}}void Planting(){//使用射线取地面交点Event e = Event.current;                        RaycastHit raycastHit = new RaycastHit();Ray terrain = HandleUtility.GUIPointToWorldRay(e.mousePosition);if (Physics.Raycast(terrain, out raycastHit, Mathf.Infinity, layerMask)){  //根据鼠标划过位置和编辑器面板设置的密度等参数实例化植被 并打上标记}}
}
[RequireComponent(typeof(MeshCollider))]
public class PaintDetails : MonoBehaviour
{}

增 删

刷完后点击保存数据,将数据存入本地,这里选择使用的是protobuff

着色器部分:
顶点函数:

//草的风吹顶点动画o.worldPos = mul(unity_ObjectToWorld,v.vertex);               float3 windVector = UnpackNormal(tex2Dlod(_WindVector , float4(o.worldPos.xz*0.01+_TimeX*_WindSpeed,0,0)));o.wind = windVector;//用于模拟风浪反光float3 windDir = float3(windVector.x,0.0,windVector.y);float3 windDir1 = lerp(windDir,0,1-v.color.r);o.vertex = UnityWorldToClipPos(o.worldPos.xyz+float3(windDir1.x,0,windDir1.z)*_WindPower);//采样地表颜色 用于草根与地表的混合fixed4 screenPos = ComputeScreenPos (o.vertex);fixed2 suv = screenPos.xy/screenPos.w;o.grabCol = tex2Dlod(_CameraColorTexture,float4(suv,0,0));
//环境光o.ambient = _Color.rgb * glstate_lightmodel_ambient + _LightColor0 * 0.125;

片元函数:

 fixed4 color = tex2D(_MainTex, i.uv);clip(color.a - _Cutout);color.rgb *= i.ambient;color.rgb *=2;UNITY_LIGHT_ATTENUATION(atten,   i, i.worldPos);fixed3 shadowColor = lerp(fixed3(1,1,1), lerp(fixed3(1,1,1), _ShadowColor.rgb, 1 - atten), _ShadowColor.w);float3 Windcol = saturate(i.wind.x*i.wind.y*_LightColor0*shadowColor);color.rgb += Windcol*color.rgb*;color.rgb = lerp(i.grabCol.rgb,color.rgb, saturate(i.color.r));color.rgb *= shadowColor;

着色器部分可优化空间还很大,比如用高度取代顶点色,做LOD分级,高配保留效果,中配取消风吹顶点动画+阴影,低配取消动画+阴影+地表拾色等。

草的加载:
前期把数据存入本地时,分成了若干个1023的组,便于在这里使用unity提供的DrawMeshInstanced 进行草的加载渲染.
关于GPUInstancing 网上的资料也比较多了,优点很明显,在渲染大数量对象时,效率很高,同时可以省下大量DC,内部也做了很多性能的优化. 缺点是目前仍然有15%左右的低配机型(opengl es2.0)不支持.我们的方案是收集这些设备做好宏定义,在不支持的机型上就直接让草地隐藏掉了.

GPUInstancing代码比较简单,主要是前期要把数据准备好,参数:mesh,mat,和一个矩阵队列,矩阵队列里包含每个对象的位置,缩放和旋转

var data = mGrassDatas[i];
if (data.draw)
{for (int j = 0; j < data.grassMatrixs.Count; j++){Graphics.DrawMeshInstanced(data.mesh, 0, data.mat, data.grassMatrixs[j]);}
}

这里只做了第一步,后续会利用Culling Group实现分区剔除逻辑,大幅度减少GPUInstancing循环绘制的植被数量.
Culling Group的空间划分,内部实现的效率很高,他需要你先注册好包围球大小和位置,然后他会动态把进入视野的区域索引队列通过回调函数返回.
仅在返回的区域中去绘制,就可以让每次绘制的数量大幅减少. 这里要配合之前的数据,在编辑阶段保存数据的时候就要考虑到包围盒的划分. 根据地图的大小,可以让包围盒的注册数量尽量少,毕竟Culling Group判断包围盒是否在视空间内也是有一定消耗的, 但是往往项目做到后来,优化的重灾区都在渲染上面,每一帧CPU等待GPU的比例很高,所以在CPU做裁剪节省GPU的消耗,还是利大于弊的.

显隐:
显隐部分主要操作的还是本地数据,由于我们之前功能开发中已经将地面分好了逻辑格,所以我只要将草的数据根据逻辑格做好映射就可以了,当移动建筑时动态的获取建筑所占的逻辑格索引,通过索引取得植被范围,然后操作数组就可以了. 数组要提前申请好,避免产生GC.

后续优化:
第一版结束,能做的优化其实还有很多,比如把每个草的预制体做大一些,让他包含原来的两份或三份,这样批次还能近一步减少,只在绿色地表上刷草的话,是否可以取消对地表的拾色混合,改为把贴图底部涂成地表的颜色,这样就可以节省一次采样,还有alphatest的性能问题等等.

Unity批量刷草工具及优化相关推荐

  1. unity 批量导入模型工具_零基础的Unity图形学笔记3:使用多模型UV与优化模型导出...

    前文所说,贴图多UV,直接命名对应贴图就可以. 模型的多套UV,则需要在3DMAX里编辑. 这篇文章主要解决两个问题: 如何正确使用多模型UV? 从3DMAX导出,到shader使用 如何优化模型导出 ...

  2. unity 批量导入模型工具_如何将VMD舞蹈导入桌面萌娘MMD

    [视频教程] https://www.zhihu.com/video/1246107348335976448 图文教程 概览 MMD的动作数据文件 .VMD 文件导入到DesktopMMD很简单,只需 ...

  3. 闲云野鹤:吃鸡(四)之场景制作—用unity内置草功能制作草

    先上最终效果图吧: 第一种方式:Add Grass Texture,只需要选择草的图片即可 选择Add Grass Texture此种方式unity默认mesh为一个矩形面.基本制作过程没什么值得多说 ...

  4. Unity地形 使用Mesh网格刷草刷不上的解决方案

    刷草的时候,使用Texture纹理,没有问题,但使用Mesh,就会刷不全 貌似刷到一定数量之后,之前刷的草就会消失,并且不可在游戏视图中看见 如果再把后面刷的草移除掉,之前刷的草又会再次显现出来 根据 ...

  5. unity导出fbx模型_Unity批量合并Animation工具/根据已有的Animation文件批量生成带FBX动画工具...

    由于本人现有项目的项目素材大部分都需要继续沿用旧项目的模型与动画,但在接受旧模型动画的时候发现,模型动画由于外包已经丢失了3dmax的源文件,只剩下了一堆AnimationCilp(.anim)文件与 ...

  6. Unity修改批量修改名字工具

    Unity修改批量修改名字工具 using System.Collections; using System.Collections.Generic; using UnityEngine; using ...

  7. Unity制作批量配音制作工具

    最近一直在忙项目,都没有时间和大家分享文章了.今天是来送福利的,送个大家一个语音合成音频工具,当然这也是用Unity制作的.看到讯飞官网有个配音制作,还需要收费,我就不能忍啊,就把之前之前做的批量配音 ...

  8. k3note Android8,联想乐檬K3 Note官方稳定版 最新VIBE刷机包 精简优化 完美加入Root权限...

    联联想乐檬K3 Note官方稳定版 最新VIBE刷机包 精简优化 完美加入Root权限 ROM介绍: 1.基于官方最新发布的VIBEUI_V3.1_1622_5.440.1_ST_K50-T5稳定版固 ...

  9. 电脑刷机重装系统_一键刷机工具

    手机刷机一般来说可能会比较繁琐,一旦操作不当极有可能会使手机变砖头,而今天小编带来了一款操作极其简单的一键刷机工具百度云os,该工具是由百度官方基于Android 4.0研发的,并且采用了百度云计算核 ...

最新文章

  1. nyoj 715 Adjacent Bit Counts
  2. 【Java面试题视频讲解】字符个数统计
  3. 鸿蒙系统被烧毁,华为鸿蒙操作系统再次被质疑 国产是原罪
  4. python画子图_Python使用add_subplot与subplot画子图操作示例
  5. 计算机用户里dell占c盘太多,我的本是戴尔,用的是Win732位旗舰版系统,C盘里面的用户总是爆满是为什么,求解...
  6. docker项目部署 php_docker部署php的web项目
  7. PHP判断手机横向,H5横竖屏检测的方法
  8. java生成数据库设计说明书(excel)
  9. itext汇总 生成pdf
  10. 斐波那契字符串_KMP
  11. js字符串转日期类型
  12. win10运行C语言的程序,win10运行游戏时出现程序无法正常启动0xc0000142解决方法介绍...
  13. 转:天下互联CEO张向宁:傻目录不是搜索引擎
  14. VirtualBox虚拟机几种网络连接方式介绍
  15. 基于CentOS7操作cobbler批量装机-(centos7和redhat8)
  16. 零基础转行大数据怎么学习?大数据学习路线
  17. 分享一下前几个月我做的超炫的登录页面
  18. 想考阿里云acp证书,报哪个机构好?
  19. Vijos P1794 文化之旅
  20. 扩展卡尔曼滤波的理解与对加入高斯噪声的正弦信号进行滤波实例

热门文章

  1. 品牌数字化转型|借势营销节点,3 招解锁品牌营销力
  2. 物理机ping通Centos虚拟机,但虚拟机ping不通物理机的解决方法
  3. 《嵌入式 – GD32开发实战指南》第3章 GPIO流水灯的前世今生
  4. 实时进销存如何帮助企业从销售、采购到库存实现一体化管理?
  5. 一篇读懂springboot用echarts实现实时柱状图和饼状图查询
  6. 【懒懒的Python学习笔记九】
  7. PDF转word之后的结果事图片格式,如何改成.doc或.docx格式
  8. 学习arduino esp32相关例程(1)深度睡眠与唤醒
  9. AI未来是什么样子,这些科幻电影里已经有了答案
  10. 数据集成-5-批数据集成