Unity挑战大世界刷草(五)

接口简介

介绍完基本方案和原理,也许你会发现很多东西都是概念上的和思路上的体现,那么我们再来过一下实际工程中用到的接口和参数,也许能有更加直观的感受。接口参数主要分为5个部分:系统基本参数配置,模型资源的输入,密度信息的输入,高度信息的输入,以及一些其他附加项。

首先是 基本参数`

      var param = new RuntimeRenderParam()
      {
            mRadius = 100f,        //视距半径
            mThreshold = 5f,       //摄像机移动多少距离会触发刷新视距内草海缓存
            mTexWindWave = "wind_Wave",    //风噪纹理
            mTexNoise = "noise",   //全局噪声贴图
            mGrassAssetPath = new string[] { "RawGrassMeshFileName" },   //草海中草的资源路径
            mGrassDensity = new int[] { 25 },   //草对应的密度信息
            mStrategy = BuildVegeStrategy.GeneratedLOD,   //如何处理草资源
        };
        //下面创建了一个用于渲染草海的Render实例
        mVegeRender = new RuntimeRender(probe, Camera.main, densMgr, heightMgr, assetProvider, param, 0);

可以看到为了创建Render实例,我们在构造时传了 RuntimeRenderParam 结构体用于包装复数个入参,详细可以参考代码中的注释,其中在模拟风动时我使用了一张全局风噪贴图用于消除正弦函数的“人造”感;此外还用了一张全局噪声纹理来营造植被间位置和朝向的微小差异。后面3条和草的Mesh有关,简单说这几个参数决定了当前Render能渲染出的草海基本模型长什么样,最高密度是多少,多株模型间如何合并成一个密度单元。处理自定义的构造体外,我们还传入了我们设想中的视场中心点对象 probe 用于判断视场何时向何处移动;同时也需要把摄像机传入其中以便采集到其管理的一些参数(如变换矩阵);最后是三个陌生的对象:densMgr、heightMgr 和 assetProvider,其实很好理解,它们分别代表了之前提到的另外3个接口部分:密度信息的输入高度信息的输入模型资源的输入

再来看看 密度信息接口`
这部分有2层嵌套,底层是面向原始数据的存取和Chunk级别的索引:

    public interface IDensityDB<T> where T : IComparable
    {
        //已知索引和偏移,找到并构建出QuadTreeDenisty结构体
        QuadTreeDensity<T> GetQuadTreeById(int aId, Vector2 aOffset);

        //同上,但是返回的不是四叉树,而是二维数组对象 Array2DDensity  
        Array2DDensity<T> GetArray2DById(int aId, Vector2 aOffset);

        //编辑时使用,从普通纹理中解码出密度,再编码为四叉树 QuadTreeDensity 
        QuadTreeDensity<T> GetQuadTreeFromTex(Texture2D aTex, Vector2 aOffset, int aId);
    }

正如前文所述,密度图可以简单的表示为一张二维数组,也可以编码成四叉树格式。如果用树,其原始数据可以是已经序列化好的C#对象,也可以直接从一张密度纹理上构建起来,分布对于不同的接口。

原始数据层之上则是之前我们提到的9宫格地块管理器,负责提供更加精细的搜索和修改密度服务:

public class GlobalDensityMgr<T> where T : IComparable
{
    //对一块由Min和Max圈定的世界范围内的空间进行搜索,返回其上所有关联的密度信息
    public void SearchRange(Vector2 aMin, Vector2 aMax, ref T[] aD, ref Vector2[] aP){...}

    //将一小块被修改的密度信息回写到原始数据结构的对应位置中
    public void InsertRange(Vector2 aMin, Vector2 aMax, T[] aDensityArr){...}
}

其中类型变量 T 表示的是密度,由于四叉树构建的需要,T需要是能够互相比较的类型,一般项目使用int型即可,但若有特殊需求,也可对T进行拓展。而InsertRange方法存在的意义是为了方便玩家与草海的交互。比如塞尔达-旷野之息中的烧草和割草都需要实施的修改地面密度信息,而我们的多层数据结构又决定了必须将这些修改信息及时同步到底层的数据区块上,以免玩家在切出切入游戏场景后,原来对草地造成的变动因为缓存的更替而消失。

还有是 高度信息接口`
与密度信息类似,高度也是分为两个部分管理:底层数据接口(通过接口定义),以及上层对高度信息的管理器。

    public interface IHeightDB
    {
        //通过地块上的锚点(索引点)获取对应地块的高度图
        UnityEngine.Object GetHeightmapByPosition(Vector2Int aPos, Type aType);

        //获取高度图的Size信息
        Vector3 GetSizeOfTerrain(Vector2Int aPos);
    }

管理器的内容比较复杂,本身不提供接口,只是维护好一张可以在shader中随时取用的环绕在角色周围的高度图。这里有必要补充一点细节:我们使用密度单位在尺寸上是1平方米,它上面的分布着的每一株草在GPU顶点渲染过程中都需要知道确切的世界空间高度。这个高度值显然同合并后的草模型没啥关系,没法附带到模型顶点数据中,而且也不方便通过 Instance buffer 带入(数据量太大),所以合理的路径是采样高度图。如果所有的草都在一个独立地块上,那么只需要传入这个地块对应的高度纹理即可,但是如果待渲染的草分布在周围4个拼接地块上,就需要想办法分批渲染或者合并高度图了。

当然有的项目地形系统可能会用到Clipmap或者Virtual Texture等技术,那么就能很方便的在GPU内虚拟一张世界范围内的超大高度纹理,会大大简化我们的工作量。

下面还有的是 模型资源输入接口`

    public enum BuildVegeStrategy
    {
        DefinedLOD = 0,    //由美术合并模型资源
        GeneratedLOD = 1,  //由程序随机合并美术单株资源
    }

    public interface IVegeCommonAssets
    {
        //加载指定路径下的模型草资源
        Object LoadVegeMeshWithPathSync(string aPath);
        //程序合并一套模型草资源到一个密度单位上,使用给的的LOD级别
        Object BuildVegeMesh(string[] aPrototypePath, int[] aNumberPerSquare, BuildVegeStrategy aStrategy, int aLOD = 0);
    }

这里所谓和合并草资源也是仁者见仁,理论上效果最好的应该是美术脑海中的那个,但是很多时候需要大量的“多样性”,那么设计一套合理的方式去随机合并多株不同模型的草也是一种不赖的选择。

说来草的Mesh合并过程中也是有不少值得推敲的细节,这里主要是说如何安排好每个顶点上的额外数据:我们知道一般Mesh除了 verticesnormals 外还会附带几组 uvs,为了能正确在GPU中展开这些被合并了的单株草Mesh,必须合理利用这多出的几组额外uv纹理。

            Vector3[] vertices = new Vector3[verticesSize];   //origin vertex
            int[] subMesh = new int[subMeshSize];             //origin mesh index(triangles)
            Vector3[] normals = new Vector3[normalsSize];     //will use [0,1,0] 
            Vector2[] uv1s = new Vector2[uv1sSize];     //origin uv1
            Vector2[] uv2s = new Vector2[uv2sSize];     //position:x,z
            Vector2[] uv3s = new Vector2[uv3sSize];     //position:lod + density

其实我这边处理得比较简单,只使用了额外的uv2和uv3用来存放单株草在1平米见方的空间中的相对便宜<x,z>,以及该株草所属的lod级别(lod)和密度级别(density)。存偏移很好理解,是为了能在GPU中获取到这对向量,从而通过叠加上这个密度单位在世界空间中的坐标,来得到一株草在世界空间中的坐标。存密度级别则是为了实现我们在上一篇文章中提到的技术方案:通过裁切掉某些密度级别的草来达到分化密度的目的。而另一个lod级别则是为了方便我们优化一些多次调用 GPUInstance 过程中产生的损耗,此处不再赘述。

最后则是一些 其他附加项`
可以想象,为了实现丰富的草海交互功能,我们还需要提供诸如

  • 角色压草
  • 草随风动,风向可调
  • 烧草
  • 割草

等等基本需求,这些实现起来并不困难,大多是在shader中通过代码控制草Mesh的变化或顶点色的lerp,有时间我会和大家详细聊聊。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容

  • Instancing 首先承接上一篇的内容,我们来算一算余下的草海信息大概有多少: 假设视距是100m,乘以2得2...
    bbccyy阅读 1,693评论 0 2
  • 阶段整理 到这里,知识点已经积累了不少,在这里我想做个简单的总结,梳理下眼下的信息,串一串流程: 一开始先让变化少...
    bbccyy阅读 1,190评论 0 1
  • 草海信息的存储 我们要求记录草海中草的位置信息是为了能够创造出一个尽可能"确定"和"可控"的世界,试想如果没有这条...
    bbccyy阅读 1,294评论 1 1
  • 前言 大家好,作为博客开通后的第一篇技术类博文,我决定将前不久总结的一些关于如何在移动端开放大世界中高效渲染草海的...
    bbccyy阅读 1,903评论 0 3
  • 书接上文 前文介绍了Unity内置 Terrain 刷草的一些缺陷,并且介绍了3款插件: uNature Adva...
    恶毒的狗阅读 2,494评论 0 2