【Unity】移动端草海解决方案

      草海是开放大世界渲染的必不可少的因素,Unity 原生的 Terrain 草海效率较低,而且无法与 RVT 结合起来,无法在移动端上实现。因此我们自己搓出来一套草海系统,使用 C# 多线程辅助运算,并能支持割草、烧草等进阶玩法。草的位置、密度、类型还需要根据美术和地形的信息进行自适应。

        最终效果可以看这个演示视频(虽然这个视频美术表现力不行,但是已经可以看到 GpuTerrain基础下的实时运算的草海效果了。对于游戏开发者而言,显然能明白这个演示已经实现了既定目标)

GpuTerrain+草海_哔哩哔哩_bilibili地形用GpuTerrain渲染;草海是自定义实时生成运算+Gpu渲染。, 视频播放量 103、弹幕量 0、点赞数 0、投硬币枚数 0、收藏人数 0、转发人数 0, 视频作者 魔术师dix, 作者简介 我们这么努力,不是为了改变世界,而是为了不让世界改变我们。,相关视频:Island_G效果演示(8月版本),原型工程1.0演示视频,玲珑岛12贴图版本_1.0,Unity3D 战斗系统核心技术: 高度可扩展的技能与多Buff系统(收藏级),【中配】A*寻路算法:算法解释 - Sebastian Lague,很多3a游戏的马还不如塞尔达的真实生动,新动作又被虐了,Skyrim Special Edition-定海神针-金箍棒,【Unity3D/资产发布】拟真向URP海面渲染系统——HigWaterSystem,游戏,还是现实?icon-default.png?t=N7T8https://www.bilibili.com/video/BV1nj411D7tm

方案设计

        这里我按照 1*1 的范围将地图进行分块,每一块视为一整片草,进行整体剔除和计算。

        如上图的示例,将草分块之后,进行AOI剔除,最后得到所有可能在视野中的草(注意:这里没有进行视锥体剔除),之后再使用生成式算法得到最终的草数据(精确到每一株草的实体)。然后再直接将数据打包传入 GPU,使用之前提到过的 GPU 驱动的渲染上屏,完成草海渲染。

        这里就涉及到之前的一些实现过的架构了:

  • 需要得到地面的高度和法线,与 开放大世界的 GpuTerrain + RVT-CSDN博客 共用一张高度法线图。
  • 上屏方案 : GPU驱动的大规模静态物件渲染-CSDN博客

        除此之外,草海还需要一张密度图(表示每个格子的草的密度和类型),和 GpuTerrain 的高度法线图每个像素一一对应。

        有了上述数据之后,草海就能正常运转了。在项目前期,我们是将草海的生成式算法放到C#多线程中的,目的是为了方便调试和交互。后续功能稳定了,可以将这个算法移动到 ComputeShader 中,效率可以更高(这也是最终成型方案)。

框架与数据管理

        制作地图时,生成棋盘格,每个格子 1*1 米,每512 *512 个棋盘生成视为一个地块。统一棋盘格的尺寸,对应高度图、植被密度图的数据。

        一般来讲,对于视野远端的地块,不需要生成格子,也不需要计算。对于近处的地块,才需要生成草海,当然距离越近性能越好。按照一些文章的说法,80米的视野范围就能满足需求,如果加上视锥体剔除,视角设置为60度,那么算下来大概需要 3696 个格子参与生成计算(后面按照4000个格子计算)。

        4000 个格子,每个格子生成 64 棵草(密度最大),每颗草的数据(Transfrom数据)大约为 30 byte,算下来总共占用约 7.5 MB 内存,即便是移动端也可以接受。(为了减少对GPU的数据传输,我们其实使用了4个80*80的区域,内存占用更多,原因下文有解释)

草海系统的生命周期管理

        对于 Patch 而言,最高运算的部分在生成(以及根据玩法对数据的修改部分),因此在运行时并没有复杂逻辑,就只是在原地不动。生成之后最大的性能瓶颈就是在玩法(例如割草)上对实体的遍历。但因为能实现限制 Patch 进行刷新,所以遍历的量并不会很大。

草海编辑工具

        因为草海已经脱离了 Unity 的Terrain,所以需要一编辑工具。

  • 根据 GpuTerrain + RVT 数据直接还原地形的工具,方便美术开发。
  • 类似于 Terrain 的刷子工具,可以绘制草海密度图。
  • 草海类型配置:配置不同类型的草,以及其出现频率,可以实时预览。
  • 运行时调试工具,方便运行时查找问题。
草海密度图示例

        

其他细节问题        

        这里记录一些细节问题,可以帮助大家实现时做参考。

面片草还是网格草?

  • 面片草顶点数少,但是依赖 AlphaTest,这一步在很多移动设备上非常昂贵。
  • 网格草顶点数高,但因为是 实体渲染,性能表现远远优于 AlphaTest。

        此外,对于某些表现效果(例如高光、碰撞弯曲)上,面片制作起来困难;在视角上,面片草存在穿帮死角,大致上只适合固定视角。所以还是建议使用网格草。

草海密度图存的什么数据?

        草海密度图我这里将每个像素的 rgba 各设置为一种类型的草的密度,每一个草的密度取值从 0~256表示其密度。在生成草海数据时,查询到某个位置有颜色,则根据通道(例如 R通道 表示杂草,G表示花、B表示地衣、小石头)密度算出需要生成特定类型、特定数量的实体。生成完成之后,将数据收集起来统一传给 GPU。

        实际上,每一个通道的256的值对于密度还是太多了,本来一个地块最多64个实体,精度肯定超标了。后续如果要优化,完全可以将密度的精度降低到8,再将其他位记录其他地图数据。

生成式算法

        成式算法其实是根据项目相关,所以不必去网上特意查找。一般来讲,只需要设置一个随机方式(噪声图、或者固定随机种子),根据算法进行随机。只要满足一个要求就行:每一次生成的位置、旋转、缩放都是相同的结果。

        同时,在生成时可能需要根据地形信息有所变化,这点也需要考虑到。

草海实时重建的频率

        在计算哪些草块(Pitch)需要计算时,为了减少对 GPU 交互的频率,改为周围 80m 的棋盘格都计算了一起扔给 GPU。因此这样占用的内存大了4倍,但数据交互频率显著降低,就不用每帧都生成一次数据了。否则,一旦加上视锥体剔除,那么一旦相机旋转了,每帧都要实时计算一次、传值,数据大了还是很不划算的(实际上,视锥体剔除我们也试过,数据量少了很多,但传输数据的频率就大幅提升了)。

        当然这是妥协,如果在 GPU 里计算草海就没这个问题了。所以,最终的完整版本还是要在 ComputeShader 里生成草海。

图片数据读取

        GPU端直接通过 Texture.GetPixel 也太蠢了,效率还低。这里我们的做法是将图片转换成 NativeArray 然后再在多线程读取。这里需要注意的时,如果是多线程读取 NativeArray ,需要一点点操作:

NativeArray<Color32> HeightMap = texture.GetPixelData<Color32>(0);//获取图片的原始数据//根据已有的数据生成不安全的只读 NativeArray,否则多线程无法使用。
NativeArray<Color32> ConvertedHeightMap= NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<Color32>(HeightMap.GetUnsafeReadOnlyPtr(), HeightMap.Length, Allocator.Invalid);#if ENABLE_UNITY_COLLECTIONS_CHECKS 
//只有编辑器下才存在 SetAtomicSafetyHandle 
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref ConvertedHeightMap, AtomicSafetyHandle.Create());
#endif

        按照上图示例,就可以在多线程中使用 Unity 的 NativeArray 了。要注意的是,SetAtomicSafetyHandle 只在编辑器下使用,在真机上是会报错的。参考:NativeArrayUnsafeUtility seems to not work in builds - Unity Engine - Unity Discussions

割草烧草如何实现

        类似于塞尔达的割草、烧草效果,如果是在 CPU 端是比较好弄的。直接可以将技能(或者玩家交互)的计算结果记录下来,然后调用一次草海刷新,然后就能根据新的数据计算出结果,再传给 GPU 即可。

        如果是在 GPU 端实现,有一个简单的方案:从头顶照一个相机下来使其能覆盖所有的草。然后所有的技能一般都会有一些特效显示,这个相机就专门照这个特效。然后 GPU 直接就能读取到这个相机生成的图片,就可以直接拿来计算了,根本不用写 CPU 和 GPU 的交互,非常地方便。

模型上种草如何实现

        目前只实现了地形上种草,而模型上种草没有实现。

        模型上种草其实也有过设计,需要提前将模型进行烘培,且需要让美术指定模型的哪些地方是可以种草的(类似于刷 UV 图)。然后在渲染模型时,根据模型的草皮图、法线等直接生成草海,后续的步骤和地皮生成草海类似了。

参考文章

移动端草海的渲染方案(一)_terrain detail 交互-CSDN博客

移动端草海的渲染方案(二)_nature renderer-CSDN博客

移动端草海的渲染方案(三)-CSDN博客

移动端草海的渲染方案(四)_恶毒的狗 草海-CSDN博客

移动端草海的渲染方案(五)-CSDN博客

TA实践分享:材质与渲染——植物与风(Unity+UE)

【C#】并行编程实战:序章_并行编程实战基于c#8-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/52330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

突破编程:深入理解C++中的组合模式

突破编程&#xff1a;深入理解C中的组合模式 在C及众多面向对象编程语言中&#xff0c;设计模式是解决问题的经典方案&#xff0c;它们帮助开发者在面对复杂系统设计时&#xff0c;能够遵循一套经过验证的最佳实践。组合模式&#xff08;Composite Pattern&#xff09;是这些设…

数据库内容保密检查系统:及时发现“潜在”安全威胁

日前&#xff0c;国内专注于保密与非密领域的分级保护、等级保护、业务连续性安全和大数据安全产品解决方案与相关技术研究开发的领军企业——国联易安自主研发的国联数据库内容保密检查系统V1.0通过国保局涉密检测&#xff0c;获得涉密信息系统产品检测证书。其主要具备以下主…

JS数据类型——【Map】精讲

JavaScript 中的 Map 是一种用于存储键值对的集合&#xff0c;它与传统的对象&#xff08;Object&#xff09;类似&#xff0c;但提供了更丰富的功能和灵活性。以下是 Map 相关的主要知识点&#xff1a; 1. 基础概念 键值对存储&#xff1a;Map 使用键值对的形式存储数据&…

输入名字转换成对象

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>输入名字转换成对象</title> </head>…

力扣算法总结

直接放别人简单易懂的总结&#xff0c;比我自己描述得好 代码随想录 (programmercarl.com) 崔添翼 (Tianyi Cui) 背包问题九讲 2.0 beta1.2 - jggnice! - 博客园 (cnblogs.com) 1.01背包 优化成一维就是利用之前上一维计算出来的数据&#xff0c;在不损害后面还要用的上一维…

在IAR开发环境下将尽可能多的代码重定向到RAM中执行的方法

今天给大家分享的是在IAR开发环境下将尽可能多的代码重定向到RAM中执行的方法。 最近和同事在讨论一个客户案例&#xff0c;客户 APP 工程是基于 IAR 开发环境&#xff0c;客户希望将工程里尽可能多的代码都重定向到 RAM 里执行&#xff0c;仅留必要或者指定的源文件代码在 Fl…

HTML5 浏览器支持

HTML5 浏览器支持 HTML5是最新版本的HTML&#xff0c;它引入了许多新特性&#xff0c;旨在更好地适应丰富的网络内容和服务。然而&#xff0c;由于浏览器的更新和兼容性问题&#xff0c;HTML5的浏览器支持情况可能会有所不同。本文将探讨HTML5的主要特性以及在不同浏览器上的支…

RabbitMQ(面试篇)

目录 MQ是什么 MQ的优点 消息队列有什么缺点 什么是RabbitMQ&#xff1f; rabbitMQ的使用常见 RabbitMQ基本概念 大家一起加油 &#xff01;&#xff01;&#xff01; MQ是什么 MQ是消息队列&#xff0c;是软件和软件之间同行的中间件产品 MQ的优点 异步处理&#xff0c…

【前端基础篇】JavaScript之BOM介绍

文章目录 浏览器对象模型&#xff08;BOM&#xff09;介绍1. 什么是BOM&#xff1f;2. Window 对象2.1 弹出框2.1.1 警告框2.1.2 确认框2.1.3 提示框 2.2 定时事件2.2.1 延时器2.2.2 定时器 2.3 Window 对象其他常用属性与方法2.3.1 获取窗口尺寸2.3.2 打开新窗口与关闭窗口2.3…

【习题】构建更加丰富的页面

判断题 1. Tabs组件可以通过接口传入一个TabsController&#xff0c;该TabsController可以控制Tabs组件进行页签切换。 A、正确(True) B、错误(False) 2. WebviewController提供了变更Web组件显示内容的接口&#xff0c;例如可以使用loadData来加载一个网页链接地址改…

【JS|第25期】探索HTTP POST请求:请求体的演变与应用

日期&#xff1a;2024年8月16日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

iOS RunLoop

一:什么是Runloop Runloop&#xff0c;正如其名&#xff0c;loop表示某种循环&#xff0c;和run放在一起就表示一直在运行着的循环 二:Runloop的创建? iOS并没有提供Runloop的创建方法,因为创建了现场自然会有一个Runloop. 所以只有获取Runloop的方法: NSRunLoop * runloo…

Leetcode 3267. Count Almost Equal Pairs II

Leetcode 3267. Count Almost Equal Pairs II 1. 解题思路2. 代码实现 题目链接&#xff1a;3267. Count Almost Equal Pairs II 1. 解题思路 这一题同样是题目3265. Count Almost Equal Pairs I的进阶版本。 它主要的区别在于说&#xff1a; 最大的操作次数增加到两次&am…

C# 循环访问目录树详解与示例

文章目录 一、目录树遍历的概念二、使用System.IO命名空间三、DirectoryInfo和FileInfo类四、递归遍历目录树五、示例&#xff1a;列出目录树中的所有文件和文件夹六、异常处理七、迭代方法八、总结 在C#中&#xff0c;访问文件系统是常见的需求之一。有时我们需要遍历目录树以…

kafak集群搭建-基于zookeeper方式

kafak集群搭建-基于zookeeper方式 1、准备3个kafka2、修改配置文件2.1、修改配置文件 3、启动测试3.1、先启动zookeeper3.2、再启动三台kafka 4、SpringBoot集成kafka集群4.1、项目配置文件application.yml4.2、kafka配置类4.3、启动SpringBoot程序 5、kafka集群架构分析6、查看…

学习能力与研究能力

摘要: 学习就像搭金字塔, 研究就像挖井. 1. 什么是学习 学习 (study) 是获得技能、知识、方法的过程. 学习有两种模式: 一是直接获得前人总结的经验; 二是从数据中总结规律. 2. 什么是研究 研究 (research) 是对已有或新的问题进行探索, 获得新技能、新知识、新方法的过程.…

【kubernetes】相关pod的创建和命令

【书写方法】&#xff1a; 管理使用k8s集群时&#xff0c;创建资源的Yaml文件非常重要&#xff0c;如何快速手写呢&#xff1f; 根据命令提示书写&#xff1a; kubectl explain [资源名称]例如打算写pod资源文件时&#xff0c;可查看如下&#xff1a; # 查看pod下所有字段 …

IO多路复用,select、poll和epoll简介

文章目录 前言1、select2、poll3、epoll4、总结 前言 select、poll 和 epoll 是 Linux 下用于多路复用 I/O&#xff08;Input/Output&#xff09;的系统调用&#xff0c;它们用于监视多个文件描述符&#xff0c;以查看哪个文件描述符上有可读、可写或发生了异常的事件。 1、sel…

python之多线程和多进程以及threading和multiprocessing模块

在 Python 中&#xff0c;多线程和多进程是实现并发编程的两种主要方式。多线程适用于 I/O 密集型任务&#xff0c;而多进程适用于 CPU 密集型任务。Python 提供了 threading 模块用于多线程编程&#xff0c;提供了 multiprocessing 模块用于多进程编程。 多线程 基本用法 使…

深度学习100问9-什么是word2vec模型

Word2vec 模型是一种用于将词语转换为向量表示的工具。 想象一下&#xff0c;我们有很多很多的词语&#xff0c;就像一个个不同的小盒子。Word2vec 模型的作用就是给每个小盒子都找到一个对应的位置&#xff0c;这个位置用一个向量来表示。这样&#xff0c;意思相近的词语在这…