Unity大面积草地渲染——4、对大面积草地进行区域剔除和显示等级设置

本文主要是介绍Unity大面积草地渲染——4、对大面积草地进行区域剔除和显示等级设置,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录
1、Shader控制一棵草的渲染
2、草地的动态交互
3、使用GPUInstancing渲染大面积的草
4、对大面积草地进行区域剔除和显示等级设置

Unity使用GPU Instancing制作大面积草地效果

大家好,我是阿赵。
这里开始讲大面积草地渲染的第四个部分,对大面积草地进行区域剔除和显示等级设置。
在上一篇文章里面,我使用了GPU Instancing来渲染草,渲染的合并是比较的成功,但如果草地的面积太大,会导致同屏渲染100多万的面。这个数量级比较高,对显卡有一定的要求。所以这一篇文章来想办法解决这个问题。

一、一个区域内的草的多种等级渲染

我这里在一个5X5的范围内,分别生成了不同数量的草:
等级1,总共有1000棵草
在这里插入图片描述

等级2,总共有500棵草
在这里插入图片描述

等级3,有100棵草
在这里插入图片描述

这三个等级,有点类似于LOD的概念,我打算在不同的距离观察这个5X5区域时,会显示不同等级的草。当然,如果有需要,继续增加等级,比如等级4 、5之类。

二、九宫等级显示

刚才我们已经有了三个等级的草,我现在先忽略了地形的区别,假设有一片无限大的草地,都是平的。然后我从顶视图把这个草地划分为每5X5为一格。然后不同深度的格子,是不同显示等级的草。纯绿色的是1级,然后颜色渐渐变浅等级就越低。
假设中间红色的小球是一个人,他走到了这个格子的时候,如果按照标准的九宫格来显示草的疏密等级,就有以下这个图:
在这里插入图片描述
直观的显示成刚才的3种等级的草,会是这样的:
在这里插入图片描述

但这个九宫的设计会有一个问题,当角色走到格子边缘的时候,会看到明显的接缝,然后跨越格子的时候,会明显的看到草地的疏密变化,效果不是很好:
在这里插入图片描述

于是,我在这个基础上,把最里面的等级1,增加多一圈,变成了下图这样:
在这里插入图片描述

这个情况下,当人走到一个格子的边缘时,人周围一圈的格子草地都保证不会发生疏密变化,只有在更远一圈的地方才会发生变化。
在这里插入图片描述

从顶视图看的效果是这样的:

开放世界大地图草地算法

三、草地LOD信息的存储

由于我们的草是用同一个网格模型渲染出来的,所以原则上我们只需要记录同一个区域内,每一颗草的位置就行了。位置有xyz三个信息,我这里是使用了图片的格式加一个配置文件来存储。比如下面这个区域的草地信息,我保存如下:
在这里插入图片描述
在这里插入图片描述

从配置文件看,这是一个5X5区域的草地,总共有250棵草,所有草的坐标的最小XYZ值,还有从最小值到最大值的偏移值,都记录下来了。
然后每棵草的坐标,其实就是图片里面的rgb颜色。
这里有250棵草,一张16X16的贴图,就有256个像素,就已经够存储了。
具体的算法是,每个坐标减去最小值再除以偏移值,就是它的颜色值。还原的时候同样的算法。

接下来思考几个扩展问题:
1、加入一个山坡有不同的高度变化,不再是一片平底,该怎样处理呢?这里有3个做法可以作为参考:

1.每一片5X5的区域生成一个贴图和配置信息,配置信息里面加上这块5X5区域的中心点坐标
2.还是用同一片5X5区域信息,在首次生成某个5X5区域的草地时,使用碰撞的方式获取一下当前草地的高度,并存起来,作为生成单棵草时的高度。
3.还是用同一片5X5区域信息,给山坡的大区域生成一个高度图,然后当需要生成某个坐标的草时,通过高度图来获取草的实际高度。 反正做法有很多。

2、假如我希望显示的草的旋转和缩放有不一样,该怎么办呢?

其实非常简单,还是用rgb把它们存储起来就行了,根据需要,同一个等级的图片增加一张单独旋转的和一张缩放的就行。

3、假如我希望显示的草不止一种,而是有很多种,该怎么办?

注意我们刚才使用了图片的rgb而已,还有alpha没有用。所以如果我们用上的话,0-255的alpha值保存在图片的每个像素上,我们最多可以支持256种草,按道理怎么都够用了吧?

刚才的例子,我生成了4个级别的LOD草信息,总共是这么多文件,容量加起来只有5k,就能渲染无限大面积的草地了:
在这里插入图片描述

值得注意的是,读取这个图片,我们有可能会发现读取出来的颜色值和实际保存的值不一样。
如果是直接放在Unity内部的图片,要记得取消所有压缩设置
在这里插入图片描述

四、使用API渲染策略

上面我们已经有了数据了,那么我们怎样才能实现LOD渲染呢?是不是应该把草都摆在场景里面,然后使用Unity的LOD Group呢?
这里我的做法是这样的:
1、先获取角色当前的坐标,如果我们是以5X5做为一格,那么算出当前角色所在的5X5区域的横竖Index
2、通过区域的坐标,算出当前5X5区域的中心点,根据实际需要显示的圈数,通过遍历,找到相邻多圈的其他5X5区域
3、计算每个区域应该显示的LOD等级,然后获取每个区域的坐标矩阵
4、如果想中间的区域显示多少圈等级1的草,可以再加多一个参数控制
5、得到所有需要显示的区域的坐标矩阵之后,按照列表长度不超过1023,重新把它们存放到一个或者多个矩阵数组里面。
6、通过API渲染:Graphics.DrawMeshInstanced(mesh, 0, mat, matrixs, matrixs.Length);
从上面的算法看,好像有很复杂的计算,其实并没有。因为当第1步计算当前角色所在的区域Index时,可以和上一次计算的值做对比,如果没有发生变化,证明角色并没有跨区域,所以2-5步都可以省略掉,直接执行第6步就可以了。

然后我看了一些网上介绍大面积草地做法的文章,他们比我做得更精细,还通过各种算法,去剔除摄像机背面的草,剔除被物体挡住的草,等等。我并没有这么做。因为我觉得,现在这一套策略,在GPU不是太差的情况下,真正的瓶颈是在CPU的计算上,而那些剔除算法,基本上都是需要额外增加CPU的计算的,而且还是每一帧都需要计算。我现在的CPU压力只会在角色跨过一个5X5区域时,稍微有一次计算,对比起来,其实是省了很多计算的。
如果真的担心GPU承受不了,我们可以通过游戏内提供显示质量等级让玩家来选择。

五、不同质量等级的显示控制

留意看刚才的渲染策略,可以发现,我留了2个参数控制草地显示的范围:
1.实际显示的圈数
2.中间显示多少圈等级1
我再加多一个参数,
3.最高等级显示到多少级
通过这三个参数的组合,我们可以组合出很多种显示质量等级了,比如我这个例子里面设置了高中低三个等级:
在这里插入图片描述

最高等级,有300多万面需要渲染,不过效果很好,一望无际,移动的过程中也完全看不出草地的接缝变化。
在这里插入图片描述

中等等级,有一百多万面需要显示,稍微可以看出远处有草的疏密变化
在这里插入图片描述

低等级,有30万面,草看起来比较稀疏,在移动过程中疏密变化比较明显。

这个demo我发到手机和模拟器上面跑过,就算调到最高显示质量,实际上都是60帧满帧的,所以看着好像很可怕的几百万面,在使用了GPU Instancing合并渲染之后,其实效率还是比较高的。如果实在是害怕有手机跑不动,其实还可以进一步的调整草的模型面数,我现在这一束草的面数有一百多面,其实也偏高了一些。

这篇关于Unity大面积草地渲染——4、对大面积草地进行区域剔除和显示等级设置的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/155882

相关文章

QT进行CSV文件初始化与读写操作

《QT进行CSV文件初始化与读写操作》这篇文章主要为大家详细介绍了在QT环境中如何进行CSV文件的初始化、写入和读取操作,本文为大家整理了相关的操作的多种方法,希望对大家有所帮助... 目录前言一、CSV文件初始化二、CSV写入三、CSV读取四、QT 逐行读取csv文件五、Qt如何将数据保存成CSV文件前言

C#TextBox设置提示文本方式(SetHintText)

《C#TextBox设置提示文本方式(SetHintText)》:本文主要介绍C#TextBox设置提示文本方式(SetHintText),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录C#TextBox设置提示文本效果展示核心代码总结C#TextBox设置提示文本效果展示核心代

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Java中使用Hutool进行AES加密解密的方法举例

《Java中使用Hutool进行AES加密解密的方法举例》AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个,下面:本文主要介绍Java中使用Hutool进行AES加密解密的相关资料... 目录前言一、Hutool简介与引入1.1 Hutool简介1.2 引入Hutool二、AES加密解密基础

Pyserial设置缓冲区大小失败的问题解决

《Pyserial设置缓冲区大小失败的问题解决》本文主要介绍了Pyserial设置缓冲区大小失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录问题描述原因分析解决方案问题描述使用set_buffer_size()设置缓冲区大小后,buf

SpringSecurity6.0 如何通过JWTtoken进行认证授权

《SpringSecurity6.0如何通过JWTtoken进行认证授权》:本文主要介绍SpringSecurity6.0通过JWTtoken进行认证授权的过程,本文给大家介绍的非常详细,感兴趣... 目录项目依赖认证UserDetailService生成JWT token权限控制小结之前写过一个文章,从S

Feign Client超时时间设置不生效的解决方法

《FeignClient超时时间设置不生效的解决方法》这篇文章主要为大家详细介绍了FeignClient超时时间设置不生效的原因与解决方法,具有一定的的参考价值,希望对大家有一定的帮助... 在使用Feign Client时,可以通过两种方式来设置超时时间:1.针对整个Feign Client设置超时时间

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意