下载下来的游戏登不上去显示警告0217长春大学

想玩高质量联机游戏?体积小不吃配置,低配福音,进来看看想玩高质量联机游戏?体积小不吃配置,低配福音,进来看看安当RS百家号现在的大型网络游戏是越来越多,大家会不也有玩腻的时候呢 ,其实有很多游戏比那些网络游戏好玩的多的多,这里小编就给大家推荐十款可联机的高质量体积小配置低游戏。联机平台在最后,喜欢小编的多多关注小编NO.1 战地2(Battlefield 2)《战地2》(Battlefield 2)是一款DICE工作室开发、由美国艺电于2005年发布的第一人称射击游戏。《战地2》是“战地”系列游戏中的第3款作品,背景首次由真实的历史战役转移到了虚构的现代军事战争,而玩家在游戏中使用的武器、载具等装备则来源于现实。相比前作,游戏使用了高度修改的引擎,画面、声音效果上有了很大的进步。该作添加了指挥官系统和小队系统,令团队合作更加紧密,竞技性更强。《战地2》有多种移动载具,每种载具都有1个驾驶位和若干个乘客位,只要位子上没人,玩家就可以自由切换位置。固定载具则可以乘1人。载具车身涂装有自己军队的标志,迷彩颜色和士兵相近。NO.2 逃脱者2(The Escapists2)逃脱者是一款和监狱建筑师差不多的策略类游戏,不过在逃脱者中,玩家的任务并不是建造一座牢不可破的监狱,而是扮演囚犯们,想尽一切办法逃离监狱。Mouldy Toof Studios制作,百战天虫发行商Team17发行的又一款监狱题材模拟经营类游戏,当然和《监狱建筑师》不同,在《逃脱者》中,囚犯们可是要想尽办法,用各种工具和狱警斗智斗勇,最后成功逃离监狱。[3]游戏中你同其他犯人和警卫的互动将影响他们如何对待你的态度,支持你的人可以在战斗中帮助你,也会在你做一些不寻常的事情的时候选择不举报,或者给你一些特殊的帮助。你可以在健身房提高你的敏捷和强壮,或者在放风的时候在图书馆提高智力,所有这些技能都在逃跑的时候有作用。监狱的生活可以说十分枯燥,完全是按作息表上的流程生活。尽量准时集合、吃饭等等,否则引起狱警注意会被殴打。玩家可以通过洗衣服、木材厂工作、金属加工获取金钱,也可以通过帮助囚犯获取金钱(任务一般是从某人手中夺回什么物品、在集合时间制造混乱等等)。右键点击某些囚犯可以购买物品,包括武器、服装等。餐厅的柜台上可以获取一些餐刀和叉子,这些小道具可以作为武器使用。每天午饭后的健身时间角色可以通过器材提高自己的力量或速度,力量提升后血量会有一定的提高。可以到阅览室读书来提高智力。锻炼和阅读会消耗精力。和囚犯斗殴时要挑好地点,狱警的攻击力非常高,很容易被干掉。击倒囚犯后右键点击搜刮物品。血量为零后会躺倒床上,可以通过睡觉恢复体力和精力。通过吃饭和洗澡也可以很快恢复精力。游戏界面右侧的那一列显示了拥有的物品,点击人像可以查看角色状态、装备武器或者更换服装,下面的记事本中记载了还没完成的任务。NO.3 未转变者(Unturned)《Unturned》是Nelson Sexton开发的一款第一人称沙盒生存游戏,于日在steam发售。Unturned是一款丧尸生存的多人沙盒类游戏,角色在废墟之城内不断的搜刮,强化自己的装备和建造设施来抵御丧尸的无休止攻击,记住,这是一款团队合作游戏,你面对的丧尸随时可能夺走你的性命!游戏中一共会为玩家们提供3个游戏模式分别大逃杀、PVE、PVP,每一种模式都是了一种游戏的体验,而且他的PVP设计的十分具有创意。在游戏中你活下去不仅是需要食物和水,更多的时候你是在于丧尸以及其他玩家对抗,玩家可以抢劫其他玩家以及偷他们领地里的任何物品,当然土匪还是需要更好的武器支持。也可以可以相互组队成一个势力,并建造领地来和其他势力对抗。也能住在丧尸稀少的森林采伐树木获取木头,石块以及其他建筑材料建造属于自己的大型堡垒、建造路障或者其他的小型建筑,更可以建造出陷阱等更有意思的东西,打猎、种植作物、搜刮各色各样的物资、过着平静的生活.........什么样的生存方式完全由玩家决定。NO.4 猛兽之地(roguelands)喜欢rogue-like随机地下城元素,喜欢8位怀旧像素画面和音乐,喜欢收集材料并打造、升级装备,喜欢极易上手的横版动作模式的玩家应该会喜欢这款叫做《Roguelands》的独立游戏。流窜宇宙的吞噬者毁灭一个又一个星球,正义的银河少年为了保卫家园联合起来,踏上了魔幻世界中充满乐趣的刷刷刷旅程。总之这个游戏就是用死亡来换取人物属性,不停的刷资源做装备,跟朋友一起联机还是很好玩的NO.5 实况足球8(Winning Eleven 8)《实况足球8》是由日本最具影响力的游戏软件商之一科乐美(KONAMI)公司所制作的一款模拟足球类的电子游戏系列。于2004年发行,平台为PC,PS2,游戏分为欧版和日版,Pro Evolution Soccer(职业进化足球)为欧版,而Winning Eleven(实况足球)为日版。但不论是日版还是欧版,中国玩家习惯将其译为实况足球。实况足球8是由玩家去操作一支球队去与另一支球队进行比赛,其中可以切换相应的队员进行传递配合,最终攻破对方的球门。游戏可与PC进行切磋,同时也可联机对战,亦可用同一PC进行PVP对战。PC版本的《实况足球8》与前作《实况足球7》相比,《实况足球8》加入大量新的足球技术和情景细节,比如令人耳目一新的间接任意球罚球、换人时的击掌、罚角球时球的放置、比赛一段时间后球员队服上出现明显的污迹、受伤治疗后再入场。游戏画面Konami 欧洲公司宣布,他们已经为其游戏《实况足球8》取得了丹麦、西班牙和意大利三国足球联盟完全授权,也就是说,《实况足球8》将取得这三国共56家俱乐部的授权。有了这些授权,Konami可以真实地表现这些队的队衣和上面的广告,当然最主要的是可以真是表现球员的真实形象。目前,Konami共计已经取得50支国家队,136家俱乐部、4500名球员的授权。画面进化!选手面部,头发细致部位大副改良。追加裁判记忆设定!例如:A对B犯规,那裁判可能只会对A进行警告而不出示红黄牌。但若A再对B犯规则会加重判罚力度,直接出示红牌!同理,如B对A进行报复性犯规,裁判出于防止爆发冲突的理由,可能会对双方各出示黄红牌的处罚!NO.6 浓浓宇宙汤(Nom Nom Galaxy)该游戏主要就是收集资源,打怪,积攒食物,回到基地后建造火箭将食物做成罐头发射到别的星球,从而你获得金币收益,与基友一起将罐头发满全宇宙吧NO.7 帝国2:帝王世纪(Age of Empires II: The Age of Kings)《帝国时代2:帝王世纪》是由全效工作室开发,微软游戏工作室发行的一款即时战略游戏,于1999年9月开始发行。该游戏也是《帝国时代》(Age of Empires)的续作,涵盖了从罗马帝国灭亡直到文艺复兴期间的中世纪大部分战争史实,共有13个文明供玩家选择。亮丽的游戏界面,方便的控制操作,以及良好的人工智能,使其获得《今日美国》评出的“2000年最佳年度游戏”称号。第一部资料片《征服者》(The Conquerors)发行十多年之后,高清版《帝国时代2》于2013年4月在Steam平台上独家发布 ,其画面效果与游戏性能都有大幅度的提高,到目前为止已有3部资料片在此基础上诞生。《帝国时代2》和大多数的即时战略游戏一样,玩家必须控制村民收集资源、建造各种建筑,研究各项科技和训练军事单位,并最终将敌人打败。在单人游戏中,玩家可选择与电脑AI在标准游戏中进行对战,或是参与基于真实历史的战役并完成其中的各项任务。而在多人模式中,玩家可与最多七个对手在网络上进行战斗。本游戏最出色的地方在于:相对于战术,经济和战略调控显得更为重要,如何拥有良好的经济状态和城镇基础是第一任务,这与其他以战术为主、强调兵种的即时战略游戏有很大不同。NO.8 火炬之光2(Torchlight 2)《火炬之光2》是一部ARPG游戏,是《火炬之光》的续作游戏,由位于美国西雅图的Runic Games开发,游戏于日上市,简体中文版于日在国内上市。《火炬之光2》有非常独特的艺术风格,这些在《火炬之光》中就已经形成,经过升级将使这款游戏适合的玩家群体更为广泛,玩家在游戏中可以单人游戏、多人局域网游戏和在线链接服务器游戏。该游戏开发组曾经领导了《暗黑破坏神》、《暗黑破坏神2》、《神话》等著名游戏。多人模式可以在局域网或者互联网上与朋友一起游戏。支持点对点匹配服务使得你即使没有朋友也能寻找到合适的伙伴与你进行合作模式游戏,局域网模式最多4人。可定制的角色玩家可以从现有的四个职业中选择并进行创造和定制,并且可以选择一个宠物。通过种类、性别、装饰的选择,技能路线决定还有他们获得的珠宝,使得每一个角色都能适应玩家的需求。自定义mod《火炬之光2》的发行将会伴随着一个更新版的TorchEd,即《火炬之光》编辑器。玩家们可以创造自己的mod,甚至在游戏世界中增加更多的东西。你和你的朋友们可以下载相同的mod,然后一起玩。新手界面《火炬之光2》拥有一个全新的用户界面。这样设计使得游戏对于新手比之前任何一个版本都容易上手。多亏了这个直观的界面,玩家可以迅速进入,而并不需要丰富的游戏经验。随机地图——《火炬之光2》对于未知区域的探索,将有昼夜及天气的循环,随机事件使得玩家可以体验更多的内容。随机副本为了获得更多经验和稀有的战利品,玩家在任何时间都可进入到游戏随机生成的副本当中。《火炬之光2》中的副本甚至拥有更多的分支路线,从而你可以和朋友一起探索,当然,这些将会伴随着随机事件,奖品和危险。宠物玩家要选择一只陪伴他们的宠物。宠物将随着玩家升级而升级,并且将会在战斗中,学习法术中,运送物品中提供帮助,它们将会执行各种各样的支援任务。钓鱼钓鱼又回到了后续作品当中。玩家们可以任选到众多钓鱼处的某处,从高能耗的冒险中跳出,放松一下,并且来看看自己将会抓到些什么。鱼对于玩家和宠物来说都具有特殊的利益,同时,其他的一些奖品也将会被发现。NO.9 传送门骑士(Portal Knights)《传送门骑士/Portal Knights》是由Keen Games制作、505 Games发行的3D魔幻卡通风格沙盒冒险游戏,通过传送门在随机生成且地形独特的各个岛屿间冒险,融合动作、采集、打造元素的高自由度角色扮演游戏。扮演战士、法师或游侠,然后开始四处打怪、采矿、完成各种NPC的任务,收集资源来定制你的装备、建造你的建筑,提升等级来增强你的能力。在特定的地图有守关的BOSS以及特殊的属性BOSS,游戏支持4人联机合作,一起建造、探险、闯关。游戏于日发行,在Valve开发的Steam平台上放出预览版。《传送门骑士》是一款融合了自由沙盒和动作冒险RPG元素的3D动作游戏,在《传送门骑士》的世界中,玩家扮演战士、法师或游侠,在像素世界里开启冒险之旅,收集资源、挑战BOSS,充分发挥自己的想象,建造房屋、打造装备。玩家需要以各式各样的方法生存下来,不断升级自己的武器和人物等级,并提升人物自身的属性。收集各样精美的家具来组建自己的建筑,打败强大的关卡BOSS成为真正的传送门骑士。离开熟悉的传送门走进神奇的未知世界的骑士,3D以及多人合作的的动作RPG游戏!你的职业和工艺水平还有强大的工具武器在实时战术来击败你的敌人作战。探索许多随机生成的岛屿和恢复和平的世界。成就你的冒险。升级你的英雄。击败守护门的怪物。成为最终的传送门骑士!NO.10 星界边境(Starbound)《星界边境》是由Chucklefish制作发行的动作冒险游戏,在游戏中玩家需要扮演一个角色进行自己的冒险之旅。游戏采用了星球为背景,玩家要开始自己的逃生,在宇宙中你需要不断的去探索,发现危险的物品需要灵敏的躲避,同时玩家还可以利用你手上的道具将敌人杀害,否则玩家将会受到他们的袭击。游戏的场景会跟随游戏而不断更换,会遇到稀奇古怪的生物以及环境带来的困扰,穿越银河去到另外个星球。《starbound》故事开始就是克苏鲁毁灭了地球,你乘坐着一艘飞船穿梭在宇宙之中。后来,由于燃料用尽,你被迫停在一颗星球上。在星际漂流中,主角了解到泰坦古代文明同克苏鲁之间的战斗,克苏鲁被封印在时空之门后面。在各个星球上能够找到许多散落的书籍,介绍了很多克苏鲁神话风格的短文,以及某些星球已经完全被腐化(星球布满了腐肉和触手)。最后,在多方协助下,集齐各个种族所保管的钥匙后,主角前往时空之门,来到了腐化之星(源头),同克苏鲁(形状为触手怪,多眼,巨大)战斗。Starbound里面包含着各种主线、支线任务,这些都隐藏在它辽阔的沙盘宇宙之中,等着你来探索。一场穿梭于太空之中的伟大冒险就此开始了!Starbound 的空间站是一个既庞大又富于挖掘度的东西。在旅途中,你会发现升级和修复的它方法,逐渐恢复它昔日的辉煌!你需要像路飞那样慢慢凑出一套船员班底,进行研究,捕捉珍稀动物,并解锁其众多的设施。你可以建造一座能够生产机甲的工厂,或者是一所能够研究、驯化捉到的怪物的实验室。空间站包含你需要探索宇宙的一切。以上所有游戏都可在《游侠对战平台联机》,如果喜欢这篇文章的小伙伴请点击关注,多多支持小编。本文由百家号作者上传并发布,百家号仅提供信息发布平台。文章仅代表作者个人观点,不代表百度立场。未经作者许可,不得转载。安当RS百家号最近更新:简介:。当你遇到一个人对你好的时候一定要珍惜作者最新文章相关文章Access denied | www.xksyxw.com used Cloudflare to restrict access
Please enable cookies.
What happened?
The owner of this website (www.xksyxw.com) has banned your access based on your browser's signature (e998ad-ua98).&figure&&img src=&https://pic1.zhimg.com/v2-480ce5b01fba1b667b1a96d7efa498aa_b.jpg& data-rawwidth=&1920& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&https://pic1.zhimg.com/v2-480ce5b01fba1b667b1a96d7efa498aa_r.jpg&&&/figure&&h2&前言&/h2&&p&我们之前讨论过的 &a href=&https://link.zhihu.com/?target=https%3A//indienova.com/tag/roguelike/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Roguelike&/a& 地图生成算法(&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/tag/pcg/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&PCG&/a&),基本上都是完全随机生成,缺点就是无法实现一些精准的关卡设计。于是大家会采用各种辅助方法,比如在生成过程中塞入特制的区块,来实现某些特定的设计。例如,我之前做过的一款 Roguelike,就会在生成过程中特别插入一个某种尺寸的房间来放置祭坛,或者形状特别的房间来让玩家有个惊喜。&/p&&p&而对于平台动作(Platformer)类型的 Roguelike 来说,精妙的关卡设计显得更加重要了,于是大家也在考虑其它方式,比如预生成大量关卡模块再随机选择、整合,或者混合两种方式来做。预生成关卡的例子大家耳熟能详的应该是《盗贼遗产(&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/game/rogue-legacy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rogue Legacy&/a&)》和《以撒的燔祭(&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/game/the-binding-of-isaac& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The Binding of Isaac&/a&)》这些,玩家每通过一个房间都会整体切换到另一个全新的房间,而这些房间都是开发者事先设计好的,只是排列按照开发者的控制来进行随机并调整参数。&/p&&p&这一次我们就要对这种预生成关卡的 Roguelike 多做一些了解。这篇文章的内容有多个来源,我们进行了整合。具体来源列在下面的参考内容中。本文主要来源是 Dead Cells 开发团队的 &a href=&https://link.zhihu.com/?target=https%3A//twitter.com/deepnightfr& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Sébastien Bénard&/a& 发表于 &a href=&https://link.zhihu.com/?target=https%3A//forums.tigsource.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Gamasutra&/a& 的文章:&a href=&https://link.zhihu.com/?target=https%3A//www.gamasutra.com/blogs/SebastienBENARD/642/Building_the_Level_Design_of_a_procedurally_generated_Metroidvania_a_hybrid_approach.php& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Building the Level Design of a procedurally generated Metroidvania: a hybrid approach&/a& 一文,我们已经取得了他的授权进行编译。&/p&&h2&Spelunky 的地图生成&/h2&&p&《洞穴探险(&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/game/spelunky--1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spelunky&/a&)》想必大家不会陌生。我最早的时候一度以为它是完全随机生成的,因为引入了绳索和炸弹巧妙的解决了出现死图的问题。后来经过了解,才发现它原来也是预生成关卡的。&/p&&p&这部分内容的编译并没有去取得授权,因为 indienova 的会员 &a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/root& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&@highway★&/a& 已经进行了翻译:《&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/root/blogread/5454& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spelunky 关卡生成&/a&》&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/indie-game-development/the-procedurally-generated-map-of-dead-cells/%23reference& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[1]&/a&,下面有部分内容来自该文章。&/p&&p&算法的第一部分是生成关卡的 Critical Path(通路),关卡由 16 个房间按照 4x4 的网格组成。方法是先在第一排随机找到一个起始房间,然后随机使相邻的房间成为通路的一部分,一直到最后一排,生成出口。找到通路之后,连接这些房间,然后再随机补充上非通路部分的房间,打通这些房间。如图顺序所示:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-acf47fce75256_b.jpg& data-size=&normal& data-rawwidth=&1020& data-rawheight=&826& class=&origin_image zh-lightbox-thumb& width=&1020& data-original=&https://pic4.zhimg.com/v2-acf47fce75256_r.jpg&&&figcaption&Spelunky 地图的分步生成[2]&/figcaption&&/figure&&p&然后为这些房间随机选取对应的预定义好的关卡模块。根据 Spelunky 的教学文章&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/indie-game-development/the-procedurally-generated-map-of-dead-cells/%23reference& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[3]&/a&,这里有 4 种不同的房间类型:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-c7e69a8ddd08e0f681e42_b.jpg& data-size=&normal& data-rawwidth=&960& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&960& data-original=&https://pic1.zhimg.com/v2-c7e69a8ddd08e0f681e42_r.jpg&&&figcaption&放置对应的预制地图块[3]&/figcaption&&/figure&&ul&&li&0:不在 Critical Path 上,不会产生任何出口(可被忽略的)次要房间&/li&&li&1:左右一定有出口&/li&&li&2:左右下一定有出口,如果有 2 号 Room 在上方,也一定有上方出口&/li&&li&3:左右上一定有出口&/li&&/ul&&p&将这些不同类型的随机房间放到对应的位置上,地图基本上就生成了。(上图视频截图来自:&a href=&https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DUqk5Zf0tw3o& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Game Maker's Toolkit&/a&,不知道 &a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/caraz& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&@卡姐 Cara&/a& 做过字幕没有)&/p&&p&不过我们在玩游戏的时候,会发现各个房间都有很大不同,看起来不像是预先定义好的,至少不像 Rogue Legacy 那样明显。这是因为 Derek Yu 为每个房间的每个 Tile 都定义了更多的随机属性,这些属性的变动不会对这个房间形成本质上的影响,但是却会实现这样的效果:即使是使用了同样的预定义模块,但是表现还是有很大差别。这也是为何一开始好多人以为它是纯随机生成的原因。&/p&&p&如果对 Spelunky 地图生成有兴趣,@highway★ 的译文&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/indie-game-development/the-procedurally-generated-map-of-dead-cells/%23reference& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[1]&/a& 提供了更多细节。下面的参考中也有其它相关链接。&/p&&h2&Dead Cells 的地图:遇到困难&/h2&&p&下面让我们回到主题上,Dead Cells 的关卡是如何生成的。&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//motion-twin.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Motion Twin&/a& 的 Dead Cells 创造了 Roguelite+银河战士恶魔城的全新类型:RogueVania,玩家将在相连世界中体会逐渐探索的乐趣,且游戏将兼有 Roguelike 游戏的重复可玩性和刺激的永久死亡体验。是一款相当优秀的游戏,indienova 也有幸参与了部分中文化工作。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c06799d3eeabd309f075_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&800& data-rawheight=&450& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic4.zhimg.com/v2-c06799d3eeabd309f075_r.jpg&&&/figure&&p&Dead Cells 在 &a href=&https://link.zhihu.com/?target=https%3A//forums.tigsource.com/index.php%3Ftopic%3D59381.40& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TIG Source&/a&&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/indie-game-development/the-procedurally-generated-map-of-dead-cells/%23reference& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[4]&/a& 上就吸引了大量的关注,后来,这款游戏的开发/策划/关卡设计 Sébastien Bénard / &a href=&https://link.zhihu.com/?target=https%3A//twitter.com/deepnightfr& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&@deepnightfr&/a& 特地在 TIG 写了一篇关于 Dead Cells 关卡生成的文章,在 &a href=&https://link.zhihu.com/?target=https%3A//www.gamasutra.com/blogs/SebastienBENARD/642/Building_the_Level_Design_of_a_procedurally_generated_Metroidvania_a_hybrid_approach.php& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Gamasutra&/a& 也有发表&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/indie-game-development/the-procedurally-generated-map-of-dead-cells/%23reference& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[5]&/a&。我们虽已获得翻译授权,但是这部分内容并不是单纯的译文,希望能够给有兴趣的开发者提供一些信息。&/p&&p&最开始,大约在 2015 年,他们想要采用传统的方式制作每一个关卡,但是很快就发现,以他们的团队规模,根本没有足够的时间做到足够好。于是,他们想要找到一种新的制作方式。而在这款游戏之前,他们已经开发了一些 Web 游戏,而且已经多次使用过 PCG 程序生成方式来制作游戏,拥有不少经验,所以,他们想到了利用程序来生成关卡。&/p&&p&很快,原型就出来了,他们欣喜的发现,这给游戏带来了很大的重玩性。不仅如此,Permadeath(永久死亡)机制还使得游戏更加刺激,需要玩家更多的靠技巧和本能来完成游戏,而不是单纯的背版。总体来说,非常棒。&/p&&p&不过新的问题很快浮现了,虽然新鲜感很强,但是生成的不精妙的关卡问题很多,而且关卡的不一致性也造成玩家无法很好的沉浸其中。&/p&&h2&柳暗花明&/h2&&p&在纯手工打造和纯随机生成两者间,开发团队陷入了两难的境地。他们必须要寻找新的方式。这时,他们幸运的遇到了 Spelunky 的开发者(原文并没有提及是否是 Derek Yu),为他们提供了新的思路,那就是我们上面刚刚讲到的混合随机以及手工预先制作关卡,以实现既多样化又连贯的玩家体验。这样,Dead Cells 遇到的问题应该可以得到解决了!&/p&&p&另外,Sébastien 还提到了两个灵感来源&/p&&ol&&li&《超越光速(&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/game/ftl-faster-than-light& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&FTL: Faster Than Light&/a&)》。他们认为这款游戏虽然是基于程序生成的,却有着精心规划的情节和非常一致的宇宙,非常值得深入研究;&/li&&li&来自 Valve 的《求生之路(&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/game/left-4-dead& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&L4D: Left For Dead&/a&)》。这一个可能出乎大家的预料,不过确实,开发团队从这款游戏上学到了很多东西。&/li&&/ol&&p&我在准备这篇文章的时候,也找来了 Left For Dead 的 AI 讲稿&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/indie-game-development/the-procedurally-generated-map-of-dead-cells/%23reference& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[6]&/a& 看了看,果然有料!虽然是讲稿,但是还是能了解到一些关于这款游戏设计的闪光点,基础的比如寻路、处理障碍等,高级一些的包括动态改变关卡、动态生成僵尸以提升性能,以及动态调节游戏节奏等,而这些高级的动态生成都是基于被 Valve 称为 AI Director(人工智能导演) 的系统完成的。&/p&&p&这个 AI Director 可以动态调节游戏的节奏,实现像电影一样的节奏控制。比如,一次激战之后,会调整敌人生成,甚至移除部分敌人,让玩家进行短暂的喘息。而当有玩家陷入紧急状态的时候,敌人也会相应减少一些,等等……讲稿中提供了一份按照既定节奏生成敌人和根据游戏进程动态生成敌人的对比图。可以看到,有了 AI Director 的自动调校,游戏的节奏变得很棒。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-ae4e9ee14_b.jpg& data-size=&normal& data-rawwidth=&2056& data-rawheight=&596& class=&origin_image zh-lightbox-thumb& width=&2056& data-original=&https://pic4.zhimg.com/v2-ae4e9ee14_r.jpg&&&figcaption&L4D 的 AI Director 自动调整游戏节奏&/figcaption&&/figure&&p&为了便于大家学习参考,之前已经分享这份文档到微信群,现在放在这里,可以直接从 Steam CDN 下载:&/p&&a href=&https://link.zhihu.com/?target=https%3A//steamcdn-a.akamaihd.net/apps/valve/2009/ai_systems_of_l4d_mike_booth.pdf& data-draft-node=&block& data-draft-type=&link-card& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&L4D AI 系统讲稿 PDF&/a&&p&显然,除了地图以外,Dead Cells 在 Roguelike 上想得更多,走得更远。他们开发了自己的 AI Director 系统,用在游戏中以动态调节游戏节奏。&/p&&h2&技术实现&/h2&&p&接下来说到地图生成的实现。经过多次的试验和探索,最后技术实现分为六个步骤:&/p&&p&1)先为关卡设置固定的元素,包括:整体设计风格,不同地点如何衔接,如何开启新的通路等基本要素。这些都是需要事先设计,在以后也不会随着随机生成而改变。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-e2eee168a4eff117d19f83b41deccdab_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&867& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic3.zhimg.com/v2-e2eee168a4eff117d19f83b41deccdab_r.jpg&&&/figure&&p&2)然后设计一大堆预制的关卡地图块,他们也将这些地图块称为 Tile(可以叫做预制地图块)。这些地图块都有着不同的表现和可配置参数,这里是一些在 CastleDB 中制作的预制地图块:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-0ffb8e40f74c82a430a700a8b04589d3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&576& data-rawheight=&356& class=&origin_image zh-lightbox-thumb& width=&576& data-original=&https://pic2.zhimg.com/v2-0ffb8e40f74c82a430a700a8b04589d3_r.jpg&&&/figure&&p&事实上,每一个房间都有着自己独特的平台布局和用途。一个藏宝房间看起来绝对不会像一个商店,而着重于战斗的房间也不同于着两者。正如前面提到这些预制关卡会有不同的参数,所以这些预制地图块在实际使用中会产生很多不同的表现,比如出入口的位置变化等等。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a5f68c9ddca9db1a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&720& data-rawheight=&390& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&https://pic1.zhimg.com/v2-a5f68c9ddca9db1a_r.jpg&&&/figure&&p&&br&&/p&&p&每个房间还有特定的主题属性,例如监狱房间就绝不会出现在下水道关卡中。这样可以给予每个大关卡不同的特性,举个例子:下水道关卡的预制地图块都设计得很紧凑,无法跳跃,那么玩家就要准备好自己的应对策略,在这个关卡中一以贯之。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-84ffbe81fff4ce44a19a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&867& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic1.zhimg.com/v2-84ffbe81fff4ce44a19a_r.jpg&&&/figure&&p&3)在准备好这些精心设计过的预制地图块之后,就需要将它们通过富有逻辑的、有趣的方式组合起来。所以他们为每个大关卡创建了原型图(概念图),通过节点达到对每一个场景预制地图块的可视化管理。&/p&&p&关卡设计从出入口开始,然后添加特殊房间(宝藏、商店等等),最后添加战斗和探索的部分。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-266fb55bdbdb09edd9a48a14_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&867& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic4.zhimg.com/v2-266fb55bdbdb09edd9a48a14_r.jpg&&&/figure&&figure&&img src=&https://pic4.zhimg.com/v2-b61e8ebfef500b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&867& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic4.zhimg.com/v2-b61e8ebfef500b_r.jpg&&&/figure&&p&这个原型图给后面的程序生成提供了数据:大关卡长度,特殊预制地图块的数量,迷宫的复杂度,入口和最近的出口之间有多少预制地图块等等。这样,开发者们就可以对生成的大关卡地图做出宏观的控制,比如,堡垒关卡可能就比较平铺直叙,而下水道关卡则可能蜿蜒曲折。&/p&&p&4)一旦这些准备设计工作完成,就可以开始生成关卡了。针对每一个节点,系统都会尝试使用一个随机的预制地图块房间,当然这些预制地图块要符合当前设定关卡的主题。然后检查这个得到的预制地图块是否符合设计要求(入口的位置和数量,类型,等等)。如果结果并不符合要求,那么就尝试下一个。然后,voilà!结束!不过,还有……&/p&&p&5)接下来,敌人。关卡中的敌人由前面提到的原型图定义,之前定义了多少个战斗地图块,也就对应多少敌人。举个例子:如果在下水道关卡原型中定义了 250 个战斗地图块,并且定义了每个战斗地图块中应该有多少敌人,例如每 5 个地图块中有一个敌人,那么就放置 50 个敌人到地图中就可以了。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-8f8acd892e855a512094ec_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&867& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic3.zhimg.com/v2-8f8acd892e855a512094ec_r.jpg&&&/figure&&p&每个敌人都有自己的限制和属性,比如有些危险的敌人可能 10 个战斗地图块才出现一次,有些敌人只能出现一次,有些不能与其它敌人出现在同一平台,有些敌人需要较大空间以便移动,等等。&/p&&p&6)最后一步是生成各种宝物和劫掠品,这也是不可缺少的。开发者在这里卖了个关子,说是秘密,我觉得大家可以根据自己的需要怎么做都可以。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-7bafe8ba107cbd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&867& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic4.zhimg.com/v2-7bafe8ba107cbd_r.jpg&&&/figure&&p&以上就是大概的思路。是不是自己也可以动手试一试了?&/p&&p&Sébastien Bénard 还特地制作了一段视频,可以看一下:&/p&&a href=&https://link.zhihu.com/?target=https%3A//v.qq.com/x/page/d0668jw2lwe.html& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-a772ac43dda_180x120.jpg& data-image-width=&320& data-image-height=&180& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dead Cells PCG_腾讯视频&/a&&h2&总结&/h2&&p&我来一个快速的总结:&/p&&ul&&li&对于平台动作游戏(Platformer)来说,混合随机生成和预制地图是一个已经被验证的优秀方案;&/li&&li&开发者需要找到适合自己游戏数据的一个架构;&/li&&li&各个预制地图块应该有多种可调整参数,以便实现多样化。&/li&&/ul&&h2&参考&/h2&&p&[1] &a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/root/blogread/5454& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spelunky 关卡生成&/a&,&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/root& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&highway★&/a&,indienova &br&[2] &a href=&https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DUqk5Zf0tw3o& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&How (and Why) Spelunky Makes its Own Levels&/a& | Game Maker's Toolkit, youtube &br&[3] &a href=&https://link.zhihu.com/?target=http%3A//tinysubversions.com/spelunkyGen/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spelunky Generator Lessons&/a&, (Chrome 方可使用)&br&[4] &a href=&https://link.zhihu.com/?target=https%3A//forums.tigsource.com/index.php%3Ftopic%3D59381.40& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dead Cells - Roguevania Action Platformer [2D Pixelart]&/a&, TIG Source &br&[5] &a href=&https://link.zhihu.com/?target=https%3A//www.gamasutra.com/blogs/SebastienBENARD/642/Building_the_Level_Design_of_a_procedurally_generated_Metroidvania_a_hybrid_approach.php& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Building the Level Design of a procedurally generated Metroidvania: a hybrid approach.&/a&, &a href=&https://link.zhihu.com/?target=https%3A//twitter.com/deepnightfr& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Sébastien Bénard&/a&, Gamasutra &br&[6] &a href=&https://link.zhihu.com/?target=https%3A//steamcdn-a.akamaihd.net/apps/valve/2009/ai_systems_of_l4d_mike_booth.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The AI Systems of Left 4 Dead&/a&, Michael Booth, Valve&/p&&p&&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/root& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&@highway★&/a& 的文章《&a href=&https://link.zhihu.com/?target=https%3A//indienova.com/u/root/blogread/5454& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Spelunky 关卡生成&/a&》亦对本文有重要贡献。在此鸣谢。&/p&
前言我们之前讨论过的
地图生成算法(),基本上都是完全随机生成,缺点就是无法实现一些精准的关卡设计。于是大家会采用各种辅助方法,比如在生成过程中塞入特制的区块,来实现某些特定的设计。例如,我之前做过的一款 Roguelike,就会在生成…
&h2&&b&背景&/b&&/h2&&p&
前一节里,解决了Houdini地形无缝导入到UE4的流程问题。但这种方法也有它的局限性,在实际游戏项目里,LA和LD还是偏向在游戏引擎编辑器里工作,他们的一些设计也会影响到地形的信息,那么就需要Houdini对已经导入UE4中并Bake成Landscape的地形资源做二次修改。通常会选择两种方案:&/p&&ul&&li&方案一:把整个地形和建筑都导回到Houdini里,重新过程化和调整生成后,再全部导入回UE4做处理。&/li&&li&方案二:使用HDA节点的Input和Output,通过调用Houdini Engine API,直接在UE4里完成调用Houdini过程化节点对地形做修改。&/li&&/ul&&p&
这里方案一不但要求美术和策划对Houdini有一定了解,而且因为Houdini里和引擎的渲染效果不一致。可能还需要导入到UE4里才能看到最终效果,大地形还要做WorldCompositon和LandscapeStreamingProxy的生成。除了地形以外的的场景和建筑部分,还会和GamePlay以及优化显示逻辑相关,通常会包装成BP或Prefab的形式,这些东西要导入Houdini再导回也不仅仅是资源处理的工作。这么看方法一是非常费时费力的方法。这也导致了国内一部分项目虽然是把Houdini的地形引入到制作管线里,但也仅仅是作为WordMachined的替代品,并不能完全发挥Houdini的全部功能。&/p&&p&
所以,我们的目标还是方案二的开发方式,除了第一次在Houdini里做完初始地形导入到UE4里生成WorldCompositon后,就不再需要重新导回到Houdini,而是在UE4里调用预先封装好过程化功能的HDA节点来完成功能。这也是近年来Ubisoft在Tom Clancy’s Ghost Recon: Wildlands和Far Cry 5里使用Houdini的方案。具体案例在GDCVault上有GDC2017和GDC2018的相关视频,这里的目标也是要UE4里用类似他们的方法来实现功能。&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-e779effed_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1280& data-rawheight=&720& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-e779effed_r.jpg&&&/figure&&p&图:Far Cry 5的地形编辑工具示例。可以直接在编辑器里调用Houdini功能对地形做修改。&/p&&p&&br&&/p&&p&
但是使用原生的UE4 Houdini Engine的前提下,无缝大地型的UE4内部修改还是会有以下几个问题:&/p&&ul&&li&虽然Houdini Engine支持Landscape的读回到Houdini,但他Output只支持使用回读的Landacape Data信息创建一个新的Landscape:&/li&&ul&&li&每次修改,都要重新跑一遍上节提到的生成WorldCompositon和LandscapeStreamingProxy的过程,重新对地形对切割,还是非常耽误时间&/li&&li&虽然提供了基于Landscape Component的Input,但Output时每个Component会Cook成一个landscape,导致生成多个Component。&/li&&/ul&&li&在原生配置下,即便只处理一部分地形的迭代也必须把整个Landsacape Data通过HDA Input到Houdini来进行处理:&/li&&ul&&li&假如使用Houdini里读入8x8k的地形,内存占用和过程化处理和交换数据量都会变大,从而导致Cook时间变长,降低迭代的速度。&/li&&li&不能基于Componment的控制生成范围,那就需要给HDA额外加入一个选择区域的Input,导致美术工作上变的更加繁琐。&/li&&/ul&&li&很难在UE里做资源的版本管理,也不方便多人合作地形&/li&&/ul&&p&
对前面提到的一些概念和方法不了解也没关系,我接下来会用示例还原这些过程,直到最终的目标方案的原型,也就是Far Cry 5的功能效果。&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-ecc206a1e4fb8e50da0db2ce0c1feed9_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&972& data-rawheight=&540& class=&origin_image zh-lightbox-thumb& width=&972& data-original=&https://pic2.zhimg.com/v2-ecc206a1e4fb8e50da0db2ce0c1feed9_r.jpg&&&/figure&&p&图 Far Cry 5 地形编辑器,可以选择一个Terrain的Section或Sector区域来做过程化生成,极大的减小了处理和引擎与Houdini交换的资源量。&/p&&p&
时间和篇幅的缘故,本节会分为上下篇,上半部分主要是在如何修改Houdini Engine源码,可以在UE4里基于Landscape Component进行小规模的迭代迭代的功能,下篇则是实现Houdini Engine管线的基础上,如何制作HDA节点,实现各种不同的编辑效果。&/p&&h2&&b&使用Houdini制作闭环&/b&&/h2&&p&
本节继续使用上一节的场景资源做作为测试用例,讲解一些HDA Input基础知识,让没有Houdini开发经验的程序人员也能很快接手Houdini Engine的改造。首先创建一个用来开发调试用的HDA节点,它的功能就是可以选中UE4场景里的一个Landscape作为Input,通过Houdini Engine把UE4 Landscape Height Data和Layer Data 转换为Heightfiled Height和Mask Data传入到HDA,在HDA里不做任何处理直接输出原始的Height和Mask,再次经过Houdini Engine生成UE4的Landscape Data。&/p&&p&下图就是一个Houdini闭环处理地形的流程展示,不需要打开Houdini,在UE4里就能完成闭环的操作。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-8ff315cd6e9bbd7713d8ec_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1067& data-rawheight=&407& class=&origin_image zh-lightbox-thumb& width=&1067& data-original=&https://pic1.zhimg.com/v2-8ff315cd6e9bbd7713d8ec_r.jpg&&&/figure&&p&&br&&/p&&p& 如何创建一个SOP(Surface OPerators or geometry nodes )类型的Houdini Node Input的流程,Houdini Engine的官方文档里也有讲解 &a href=&http://link.zhihu.com/?target=https%3A//www.sidefx.com/docs/unreal/_inputs.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&sidefx.com/docs/unreal/&/span&&span class=&invisible&&_inputs.html&/span&&span class=&ellipsis&&&/span&&/a& &/p&&p&方法一,Houdini里File-&New Asset,按下图的创建一个新的Operator&/p&&figure&&img src=&https://pic3.zhimg.com/v2-af68efb7fae23df2a86cc6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1000& data-rawheight=&401& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&https://pic3.zhimg.com/v2-af68efb7fae23df2a86cc6_r.jpg&&&/figure&&p&方法二 创建一个Gemotry节点,Create Digital Asset,在HDA的Parameter里添加一个Operate Path的参数,把这个参数的与Object_Merge的Object做关联。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-711e3ed787a37cef6d409_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&984& data-rawheight=&582& class=&origin_image zh-lightbox-thumb& width=&984& data-original=&https://pic2.zhimg.com/v2-711e3ed787a37cef6d409_r.jpg&&&/figure&&p&&br&&/p&&p&
不论用这两种哪个方法制作都可以得到这个HDA文件,把它加入到UE资源并拖入到Level的话,按下图那样把Input类型选择为Landscape Input,就可以选择要处理LandscapeStreamingProxy。而勾选上最下面的“Export Selected Landscape Components Only”,就可以把Landscape Component作为Input输入给Houdini。但就像一开始提到的,原生Houdini Engine的这个功能并不能满足我们的需求。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-b0d6c5907bcd358319fdffa682a5dc76_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&449& data-rawheight=&570& class=&origin_image zh-lightbox-thumb& width=&449& data-original=&https://pic3.zhimg.com/v2-b0d6c5907bcd358319fdffa682a5dc76_r.jpg&&&/figure&&h2&&b&基于component的更新的问题&/b&&/h2&&p&如下图所示,原生UE4的Landscape的是支持多选Component Selection的,Houdini Engine也是支持多个Landscape Component 的Input。&/p&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-319fc24cbc5bf43ec023f12b52f60465_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1339& data-rawheight=&660& class=&origin_image zh-lightbox-thumb& width=&1339& data-original=&https://pic2.zhimg.com/v2-319fc24cbc5bf43ec023f12b52f60465_r.jpg&&&/figure&&p&选择4x4个Component作为要处理的Landscape信息,然后用Recommit看下结果.&/p&&figure&&img src=&https://pic3.zhimg.com/v2-b0d6c5907bcd358319fdffa682a5dc76_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&449& data-rawheight=&570& class=&origin_image zh-lightbox-thumb& width=&449& data-original=&https://pic3.zhimg.com/v2-b0d6c5907bcd358319fdffa682a5dc76_r.jpg&&&/figure&&p&
如下图所示,虽然在效果面上,Houdini Enine把读入的LandScape Data转成HeightField Data 输入给Houdini又没有丝毫误差的的Output后转为LandScape Data,但Houdini Engine把这16个Component创建成了16个Landscape,这明显不是想要的结果。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-ce76769f02acc86ec711dea_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&941& data-rawheight=&536& class=&origin_image zh-lightbox-thumb& width=&941& data-original=&https://pic3.zhimg.com/v2-ce76769f02acc86ec711dea_r.jpg&&&/figure&&p&
另外要注意的是,新创建的Landscape的Transform和老的Landscape的Transform也不一样,这是Houdini和UE4的高度信息单位不同导致,这个问题也会在后面修改Houdini Engine时造成一定的困扰。而且原生的Component多选功能在Height和Mask更新上也会有一些问题。另外预先提到的一点,虽然Houdini Engine的Landscape的更新上有以上各种问题,但类似读取Landscape的信息来动态摆放,生成植物生态系统等不会修改LandscapeData的功能并不会受影响,这个具体的HDA开发也会在下篇里涉及到。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a0d1aedc7c2d0_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&972& data-rawheight=&537& class=&origin_image zh-lightbox-thumb& width=&972& data-original=&https://pic1.zhimg.com/v2-a0d1aedc7c2d0_r.jpg&&&/figure&&p&图:类似FarCry5根据选择地块的Mask信息生成植被的管线,原生的Houdini Engine也是可以胜任的。只需要一些Houdini HDA的功能开发就可以了。&/p&&h2&&b&定制Houdini Engine支持基于Component的生成和更新&/b&&/h2&&p&因为时间和篇幅关系,这里提供一个不需要修改UE4源码,只少量修改Houdini Engine就可以解决问题的方法,先进入到UE4的Houdini Engine Plugin工程代码里。&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-60d910eab73c16f4fbe51d6f6857dcf4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&428& data-rawheight=&847& class=&origin_image zh-lightbox-thumb& width=&428& data-original=&https://pic1.zhimg.com/v2-60d910eab73c16f4fbe51d6f6857dcf4_r.jpg&&&/figure&&p&&br&&/p&&p&和Landscape相关的功能,是在HoudiniLandscapeUtils和HoudiniEngineUtils里,建议有时间还是全看一遍,这里用注释简单描述下整个数据流程方便定位问题。&/p&&p&&b&Input部分&/b&:&/p&&p&调用FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray函数,把选择的Landscape Component的Height Data信息转为Houdini的HeightField Data。&/p&&p& 1. Extracting the height data&/p&&p& 2. Convert the height uint16 data to float&/p&&p& 3. Set the HeightfieldData in Houdini&/p&&p& 4. Extract and convert all the layers&/p&&p& // 1. Extract the uint8 values from the layer&/p&&p& // 2. Convert unreal uint8 to float&/p&&p& // 3. Set the heighfield data in Houdini&br&&br&&b&Output部分:&/b&&/p&&p&当数据在Houdini里处理完成后,调用FHoudiniLandscapeUtils::CreateAllLandscapes基于Houdini的volume数据生成Landscape。&/p&&p& // First, we need to extract proper height data from FoundVolumes&/p&&p&
// Check that all layers/mask have not changed too&/p&&p& // Extract the Float Data from the Heightfield&/p&&p& // Convert the height data from Houdini's heightfield to Unreal's Landscape&/p&&p& // Look for all the layers/masks corresponding to the current heightfield&/p&&p& // Extract and convert the Landscape layers&/p&&p& // Create the actual Landscape&/p&&p&定位到FHoudiniLandscapeUtils::CreateLandscape函数里,它的核心功能把转换后的的uint16的Landsacpe的Height信息(TArray& uint16 && IntHeightData)和Layer信息(TArray& FLandscapeImportLayerInfo && ImportLayerInfos),通过调用UE4的ALandscapeProxy::Import来生成一个全新的Landscape。&/p&&p&
这里选择的解决方案是不创建新的Landscape,把HeightData和LayerData做适当的封装,直接使用FLandscapeEditDataInterface的SetHeightData和SetAlphaData输入到需要修改的Landscape的对应Component的数据做更新。&/p&&p&&b&Layer Data的处理方法&/b&&/p&&p&
为了讲解简单起见,直接在CreateAllLandscapes函数的后面加上这部分功能。其中的Height Layer的更新相对简单,把之前Import用的TArray& FLandscapeImportLayerInfo & ImportLayerInfos的数据对应的用LandscapeEdit.SetAlphaData传给老的Landscape就可以了。&br&&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// Set Layer Data
for (int32 LayerIndex = 0; LayerIndex & ImportLayerInfos.Num(); LayerIndex++)
LandscapeEdit.SetAlphaData(ImportLayerInfos[LayerIndex].LayerInfo,
SelectLandscapeComponent-&GetSectionBase().X,
SelectLandscapeComponent-&GetSectionBase().Y,
SelectLandscapeComponent-&GetSectionBase().X +
SelectLandscapeComponent-&ComponentSizeQuads,
SelectLandscapeComponent-&GetSectionBase().Y +
SelectLandscapeComponent-&ComponentSizeQuads,
(uint8*)ImportLayerInfos[LayerIndex].LayerData.GetData(), 0);
&/code&&/pre&&/div&&p&然后选中一个Component,给HDA配置上Landscape材质,以及一个HeightField
Mask Noise节点,给地形的Groud信息图层信息增加一些噪声 。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-6555fec53c0e9ec96e5dae761fd22ea3_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&802& data-rawheight=&331& class=&origin_image zh-lightbox-thumb& width=&802& data-original=&https://pic4.zhimg.com/v2-6555fec53c0e9ec96e5dae761fd22ea3_r.jpg&&&/figure&&p&&br&&/p&&p&左边是处理前的效果,右边是增加噪声后的效果。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-c98ab0b4ccc711ccb34a16_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1190& data-rawheight=&432& class=&origin_image zh-lightbox-thumb& width=&1190& data-original=&https://pic3.zhimg.com/v2-c98ab0b4ccc711ccb34a16_r.jpg&&&/figure&&p&可以看到,这里已经把Houdini处理过的Height Mask信息写回到了UE4原本的Landscape Layer上,实现了目标的效果。&/p&&p&&b&Height Data的处理办法&/b&&/p&&p&和处理Layer Data的方法类似,把Houdini Engine Output的(TArray& uint16 && IntHeightData)用LandscapeEdit.SetHeightData函数传回给Landscape Component。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&ULandscapeInfo* LandscapeInfo =
SelectLandscapeComponent-&GetLandscapeProxy()-&GetLandscapeInfo();
FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo);
int Num = IntHeightData.Num();
for (int i = 0; i & N i++)
// Convert Transform
IntHeightData[i] = (IntHeightData[i] - ZeroValueInDigit)*
SelectLandscapeScale.Z / OldLandscapeScale.Z + 32768.f;
// Set HeightData
LandscapeEdit.SetHeightData(SelectLandscapeComponent-&GetSectionBase().X,
SelectLandscapeComponent-&GetSectionBase().Y,
SelectLandscapeComponent-&GetSectionBase().X +
SelectLandscapeComponent-&ComponentSizeQuads ,
SelectLandscapeComponent-&GetSectionBase().Y +
SelectLandscapeComponent-&ComponentSizeQuads ,
(uint16*)IntHeightData.GetData(), 0, false );
&/code&&/pre&&/div&&p&
Convert Transform注释部分的处理,是因为经过重新处理后Houdini的Height Field的Data Range和之前的发生了变化,导致ConvertHeightfieldDataToLandscapeData里生成的HeightData和Transform信息和Input时的Transform信息不匹配,ZeroValueInDigit和OldLandscapeScale.Z分别代表了新地形的Transform信息。需要把两个Transform信息的差异对HeigtData做修正,才能把正确的Height Data写回到Landscape。如果你发现写回的地形整体高了一块或低了一块,或者高度比例和原来不一致,那通常就是这个HeightData的还原处理出错了。所以原始的Landscape Transform也要尽量标准,例如本节示例里初始Landscape的Transform就做的尽量正规化&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a1e77ac3e79b11fc6d814c5eaddc2f84_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&351& data-rawheight=&82& class=&content_image& width=&351&&&/figure&&p&看下修改代码后的效果,选择一个Landscape Component地块,在HDA节点里增加一个Heightfiled Noise的节点,对Landscape Height Data做一些轻微的噪声修改:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-fc279c618c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&810& data-rawheight=&573& class=&origin_image zh-lightbox-thumb& width=&810& data-original=&https://pic4.zhimg.com/v2-fc279c618c_r.jpg&&&/figure&&p&&br&&/p&&p&左侧是未处理的,右侧是处理完的。可以看到地块有了噪声的高低差的效果。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-e688cb841ec8eadc0ba139_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1066& data-rawheight=&349& class=&origin_image zh-lightbox-thumb& width=&1066& data-original=&https://pic2.zhimg.com/v2-e688cb841ec8eadc0ba139_r.jpg&&&/figure&&p&继续做一个Height Field Erode的测试,用TimeShift来控制Height Field Erode的演算帧数。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-a1e5377ccddff18e750b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&375& data-rawheight=&372& class=&content_image& width=&375&&&/figure&&p&
下图结果是TimeShift = 30 和 TimeShift = 60的效果对比。基本上实现了用Houdini Engine对一个Landsape Component的修改功能。但是问题也很明显。处理的Component和未处理的Component的边缘高度无法很好的衔接。距离推上生产线,还有不少功能需要开发和支持。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-15bbe2ef81f95_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1196& data-rawheight=&399& class=&origin_image zh-lightbox-thumb& width=&1196& data-original=&https://pic2.zhimg.com/v2-15bbe2ef81f95_r.jpg&&&/figure&&p&图:Height Field Erode效果生成,相比整个Landscape的演算,一个Component只要几秒内就能完成效果计算。&/p&&p&&br&&/p&&p&PS: 6月28日补充,在植被系统的预研究,发现了有绘制选取的方式来提交修改区域,比选择Landscape Component更加灵活。就在这里也补充一遍了。&/p&&p&UE4除了Component Select Tool之外,在Terrain Sculpt Tool里提供Region Selection Tool的功能,这个要比Component Select Tool更加灵活和便捷。但和Component Select Tool一样,这个功能对原生的Houdini Engine并不适用...&/p&&figure&&img src=&https://pic3.zhimg.com/v2-bf7ad37a4a5e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1158& data-rawheight=&648& class=&origin_image zh-lightbox-thumb& width=&1158& data-original=&https://pic3.zhimg.com/v2-bf7ad37a4a5e_r.jpg&&&/figure&&p&
如上图所示,Region Selection Tool是Terrain Sculpt Tool里的功能,不过可以借用这个功能来作为Input的Mask Paint来使用。接下来看下怎么扩展Houdini Engine把这个Mask作为Input传到Houdini里去。&/p&&p&
Region Selection Tool绘制的Mask的方式,当使用者绘制时,会在FLandscapeToolStrokeMask::Apply函数里根据笔触和权重值,把绘制值添加到class ULandscapeInfo的TMap&FIntPoint,float& SelectedRegion 里的,只有绘制过的区域才会保存在SelectedRegion里,其中FIntPoint代表的是在Landscape里的XY位置信息,作为整型保存,而float为绘制Mask的权重。&/p&&p& 拿到了SelectedRegion后,就是要在Houdini Engine里把它作为Mask,输入到HDA中进行处理。Houdini Engine是在FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponent函数里对Height Data和Mask Data进行Input打包的,这里选择在这个函数里加入SelectedRegion的Mask的打包工作。&/p&&p&
首先,是根据Houdini里一个Landscape Component的大小MaxX x MaxY,创建出对应的SelectedRegion大小的Mask数组。在LandscapeInfo的SelectedRegion里查找每个点的信息,如果有就复制到对应位置,没有则设置为0。这样,提供给Houdini使用的SelectedRegionData就完成了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
TArray&float& SelectedRegionD
  for (int32 X = MinX; X &= MaxX; X++)
      for (int32 Y = MinY; Y &= MaxY; Y++)
      {
        float RegionSelect =
        LandscapeInfo-&SelectedRegion.FindRef(FIntPoint(X, Y));
        SelectedRegionData.Add(RegionSelect);
      }
&/code&&/pre&&/div&&p&接下里,跟LayerMask同样的方式,通过C++代码创建一个名为SelectedRegion的Mask节点,并跟其他的Volume Merge到一起。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&  FString LayerName = &SelectedRegion&;
  HAPI_NodeId LayerVolumeNodeId = -1;
  if (!CreateVolumeInputNode(LayerVolumeNodeId, LayerName, ParentId))
  HAPI_PartId CurrentPartId = 0;
  if (!SetHeighfieldData(LayerVolumeNodeId, CurrentPartId,
    SelectedRegionData, SelectedRegionLayerVolumeInfo, LayerName,
    ComponentIndex))
      
  if (!CommitVolumeInputNode(LayerVolumeNodeId, InputMergeNodeId,
    MergeInputIndex))
  MergeInputIndex++;
&/code&&/pre&&/div&&p&另外,ULandscapeInfo提供了把绘制的SelectedRegion转为Selected Component的功能,这样绘制过过程化的影响区域后,就不用再选择一次Landscpe Component了。这个修改也很简单,在FHoudiniEngineUtils::HapiCreateInputNodeForLandscape函数里,当没有selected components时,就把绘制的区域转换成SelectedComponents。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if ( LandscapeInfo )
  // Get the currently selected components
  SelectedComponents = LandscapeInfo-&GetSelectedComponents();
  // 如果没有selected components,则从绘制区域获取selected components
  if (SelectedComponents.Num() == 0)
    SelectedComponents = LandscapeInfo-&GetSelectedRegionComponents();
&/code&&/pre&&/div&&p&把名为“SelectedRegion”的Mask作为Input输入到HDA后,需要在HDA里对应这个Mask Layer来识别。在HDA的Heightfield Noise节点里,把SelectedRegion作为Mask Layer来使用&/p&&figure&&img src=&https://pic3.zhimg.com/v2-18e69b9c9c3d9645a32bff3df0b830f2_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&432& data-rawheight=&584& class=&origin_image zh-lightbox-thumb& width=&432& data-original=&https://pic3.zhimg.com/v2-18e69b9c9c3d9645a32bff3df0b830f2_r.jpg&&&/figure&&p&这样HeightField只有在有Mask的部分会有Noise的效果,这个同样也可以用在植被的Entity Point Cloud的生成上。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-2d2a0c55d4d18f1fc2b6_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&917& data-rawheight=&642& class=&origin_image zh-lightbox-thumb& width=&917& data-original=&https://pic3.zhimg.com/v2-2d2a0c55d4d18f1fc2b6_r.jpg&&&/figure&&p&&br&&/p&&p&下图的效果,就是在绘制的'X'的区域内,对9个Landscape Component产生噪声变化。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-29a27cd3db0d940bddb5_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&782& data-rawheight=&974& class=&origin_image zh-lightbox-thumb& width=&782& data-original=&https://pic2.zhimg.com/v2-29a27cd3db0d940bddb5_r.jpg&&&/figure&&h2&&b&总结&/b&&/h2&&p&Houdini Engine基于Landscape Component的过程化生成,确实可以大幅度的提升生成效率和速度,但是Houdini Engine和HDA制作都还需要一系列的定制开发&/p&&ul&&li&对多选Landscape Component的支持,并且解决多个Component之间的接缝问题。&/li&&li&Input不能只有Landscape Component来控制范围,还需要为美术提供选区的功能来控制生成范围,避免Component边界问题。&/li&&/ul&&p&在下篇中,我们会针对这些问题,继续对Houdini Engine进行定制,以及提供针对不同功能的HDA开发的示例。&/p&
背景 前一节里,解决了Houdini地形无缝导入到UE4的流程问题。但这种方法也有它的局限性,在实际游戏项目里,LA和LD还是偏向在游戏引擎编辑器里工作,他们的一些设计也会影响到地形的信息,那么就需要Houdini对已经导入UE4中并Bake成Landscape的地形资源做二…
&figure&&img src=&https://pic2.zhimg.com/v2-b50f151ea570108bfc1044_b.jpg& data-rawwidth=&1936& data-rawheight=&1296& class=&origin_image zh-lightbox-thumb& width=&1936& data-original=&https://pic2.zhimg.com/v2-b50f151ea570108bfc1044_r.jpg&&&/figure&&p&体积光是现实中常见的因丁达尔效应而产生的一种大气现象,文人墨客常用“慵懒的阳光泄下”描绘该现象带来的美感。笔者在一次旅游后见到了这种神奇的自然现象,遂决定在游戏中实现并使用这样的效果。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-98dfba89b799_b.jpg& data-size=&normal& data-rawwidth=&1936& data-rawheight=&1296& class=&origin_image zh-lightbox-thumb& width=&1936& data-original=&https://pic4.zhimg.com/v2-98dfba89b799_r.jpg&&&figcaption&青藏高原上雨后初晴的体积光&/figcaption&&/figure&&p&体积光是由空气中的水蒸气和灰尘对光线的散射造成的,显然在实时渲染中我们无法模拟庞杂的微小粒子,只能用近似的方法获得,实现的原理其他的大神已经解释的非常详细了,这里直接给出地址,我们这里将更加着重于实现。&br&&/p&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic2.zhimg.com/129dffb87a69_180x120.jpg& data-image-width=&1014& data-image-height=&574& class=&internal&&FOXhunt:游戏开发相关实时渲染技术之体积光&/a&&p&我们将使用Unity自带的Shadowmap实现体积光,众所周知,实时渲染中的阴影是通过将像素点的世界坐标(在fragment shader中获得或由像素深度反推获得)乘灯光的viewProjectionMatrix获得该像素点在shadowmap下的uv,并与shadowmap记录的像素点深度进行比较,若shadowmap记录的深度大于等于该点,则表示该点并没有在阴影下,应该受到光照,反之则表示该点受到遮挡,不应该受到光照。Unity中目前支持的实时光照有Directional Light, Spot Light, Point Light,这三种光照类型的实现大同小异。&/p&&p&另外,希望读者在阅读之前,确保自己对Unity渲染管线,官方提供的.cginc文件以及CommandBuffer, GL等API比较熟悉,接下来的分析将不会出现对此类基础的详解。&/p&&p&效果的实现全部在本人的开源里,注意,本开源项目并非完全原创,在经过Slightly Mad大神的同意后,对其开源原型进行魔改,增减,并上传:&/p&&a href=&https://link.zhihu.com/?target=https%3A//github.com/MaxwellGengYF/Unity-Volumetric-Light& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic2.zhimg.com/v2-761ee85dd3406ebe8805d5_ipico.jpg& data-image-width=&309& data-image-height=&309& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MaxwellGengYF/Unity-Volumetric-Light&/a&&figure&&img src=&https://pic4.zhimg.com/v2-69d54c173e819c8f484f98ba_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&600& data-rawheight=&1094& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic4.zhimg.com/v2-69d54c173e819c8f484f98ba_r.jpg&&&/figure&&ol&&li&Directional Light:&/li&&/ol&&p&Directional light常用来模拟太阳光,月光等自然光照,由于所有物体都会受到directional light的影响,因此我们的可以直接使用后处理来实现。正如链接里的解释,raymarch的基本原理就是将一条线段按比例分成多段,然后将每个顶点上的采样结果相加,所以首先需要获得射线的起点,我们通过以下代码获得renderTexture上的4个角的近裁面位置,然后在后处理shader中通过fragment shader的线性插值即可获得每个像素点在近裁面上的位置:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&cam = GetComponent&Camera&();
Matrix4x4 inverseViewProjectionMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);
inverseViewProjectionMatrix *= cam.worldToCameraM
inverseViewProjectionMatrix = inverseViewProjectionMatrix.
Vector3 leftBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, -1, 1));
Vector3 rightBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, -1, 1));
Vector3 leftTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, 1, 1));
Vector3 rightTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, 1, 1));
&/code&&/pre&&/div&&p&这样我们就拥有了摄像机近裁面的四个角的世界坐标,同理可以通过这样的方法取得摄像机远裁面的四个角:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&cam = GetComponent&Camera&();
Matrix4x4 inverseViewProjectionMatrix = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);
inverseViewProjectionMatrix *= cam.worldToCameraM
inverseViewProjectionMatrix = inverseViewProjectionMatrix.
Vector3 leftBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, -1, 0));
Vector3 rightBottom = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, -1, 0));
Vector3 leftTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(-1, 1, 0));
Vector3 rightTop = inverseViewProjectionMatrix.MultiplyPoint(new Vector3(1, 1, 0));
&/code&&/pre&&/div&&p&使用其他引擎或DirectX的朋友请注意,Unity3D是使用OpenGL标准的投影,所以我们这里需要经过GL.GetGPUProjectionMatrix函数获得确实的投影矩阵,并且远裁面深度为0,近裁面深度为1。&/p&&p&以近裁面坐标作为线段起点,以近裁面坐标到远裁面坐标为线段方向,我们还需要确定线段的终点,显然,直接取远裁面是非常愚蠢的,假设摄像机会渲染1000米,而我们的显卡性能中等偏良好,大概可以承受64次采样的性能消耗,那每一步采样之间将会相隔1000 / 64 = 15.625米,也就是说在15米左右之内的物体变化都不会体积光产生任何影响,而且还会导致采样点出现在着色像素之后,产生严重的bug。&/p&&p&因此,我们需要通过深度图,获得屏幕像素的深度并以此为线段的终点,同时限制线段最大长度,将线段长度设置到一个精度与视觉效果的相对平衡,确定终点的示例代码大致如下:&/p&&div class=&highlight&&&pre&&code class=&language-glsl&&&span&&/span&&span class=&c1&&// _InverseViewProjectionMatrix UNITY_MATRIX_VP矩阵的逆矩阵&/span&
&span class=&c1&&// _SampleCount
采样率&/span&
&span class=&c1&&// _VolumetricIntensity 体积光强度&/span&
&span class=&c1&&// _MaxLength 最远采样距离&/span&
&span class=&k&&float&/span& &span class=&n&&depth&/span& &span class=&o&&=&/span& &span class=&n&&tex2D&/span&&span class=&p&&(&/span&&span class=&n&&_CameraDepthTexture&/span&&span class=&p&&,&/span& &span class=&n&&i&/span&&span class=&p&&.&/span&&span class=&n&&uv&/span&&span class=&p&&).&/span&&span class=&n&&r&/span&&span class=&p&&;&/span&
&span class=&c1&&//像素深度&/span&
&span class=&k&&float&/span& &span class=&n&&worldPos&/span& &span class=&o&&=&/span& &span class=&n&&mul&/span&&span class=&p&&(&/span&&span class=&n&&_InverseViewProjectionMatrix&/span&&span class=&p&&,&/span& &span class=&n&&float3&/span&&span class=&p&&(&/span&&span class=&n&&i&/span&&span class=&p&&.&/span&&span class=&n&&uv&/span& &span class=&o&&*&/span& &span class=&mi&&2&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&n&&depth&/span&&span class=&p&&));&/span&
&span class=&c1&&//通过NDC坐标反推世界坐标&/span&
&span class=&n&&float3&/span& &span class=&n&&startPos&/span& &span class=&o&&=&/span& &span class=&p&&...&/span&
&span class=&c1&&//已经取得的世界空间近裁面坐标&/span&
&span class=&n&&float3&/span& &span class=&n&&direction&/span& &span class=&o&&=&/span& &span class=&n&&normalize&/span&&span class=&p&&(&/span&&span class=&n&&worldPos&/span& &span class=&o&&-&/span& &span class=&n&&startPos&/span&&span class=&p&&);&/span&
&span class=&c1&&//线段归一化方向&/span&
&span class=&k&&float&/span& &span class=&n&&m_length&/span& &span class=&o&&=&/span& &span class=&n&&min&/span&&span class=&p&&(&/span&&span class=&n&&_MaxLength&/span&&span class=&p&&,&/span& &span class=&n&&length&/span&&span class=&p&&(&/span&&span class=&n&&worldPos&/span& &span class=&o&&-&/span& &span class=&n&&startPos&/span&&span class=&p&&));&/span&
&span class=&c1&&//获取线段的目标长度&/span&
&span class=&k&&float&/span& &span class=&n&&perNodeLength&/span& &span class=&o&&=&/span& &span class=&n&&m_length&/span& &span class=&o&&/&/span& &span class=&n&&_SampleCount&/span&&span class=&p&&;&/span&
&span class=&c1&&//每两个采样点之间的距离&/span&
&span class=&n&&float3&/span& &span class=&n&&currentPoint&/span& &span class=&o&&=&/span& &span class=&n&&startPos&/span&&span class=&p&&;&/span&
&span class=&c1&&//记录当前采样点的位置&/span&
&span class=&k&&float&/span& &span class=&n&&intensity&/span& &span class=&o&&=&/span& &span class=&mo&&0&/span&&span class=&p&&;&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&k&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&mo&&0&/span&&span class=&p&&;&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&n&&_SampleCount&/span&&span class=&p&&;&/span& &span class=&o&&++&/span&&span class=&n&&i&/span&&span class=&p&&){&/span&
&span class=&c1&&//进行固定次数的采样&/span&
&span class=&n&&currentPoint&/span& &span class=&o&&+=&/span& &span class=&n&&direction&/span& &span class=&o&&*&/span& &span class=&n&&perNodeLength&/span&&span class=&p&&;&/span&
&span class=&c1&&//更新当前采样点&/span&
&span class=&n&&intensity&/span& &span class=&o&&+=&/span& &span class=&n&&GetShadow&/span&&span class=&p&&(&/span&&span class=&n&&currentPoint&/span&&span class=&p&&);&/span&
&span class=&c1&&//获得当前坐标的阴影遮挡信息&/span&
&span class=&p&&}&/span&
&span class=&n&&intensity&/span& &span class=&o&&*=&/span& &span class=&n&&_VolumetricIntensity&/span& &span class=&o&&*&/span& &span class=&n&&m_Length&/span&&span class=&p&&;&/span&
&span class=&c1&&//对体积光的强度进行控制&/span&
&span class=&k&&return&/span& &span class=&n&&intensity&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&p&通过类似实现,可以获得一个简易的体积光效果,如何获得阴影采样的实现,在UnityCG.cginc和UnityShadowLibrary.cginc中有详细的实现,文件目录在(Unity Folder)/Editor/Data/CGIncludes/,其中平行光的阴影采样涉及到cascadeShadowMap的计算,需要对采样进行由近及远分成4层,有兴趣的朋友可以自行研究一下,这里不再赘述。&/p&&p&2. Point Light&/p&&p&不同光源之间其实唯一的不同就是计算ray march线段的起点与终点的方法不同,point light的照射范围其实是一个球形,所以我们可以简单的直接在灯光的位置放一个球,然后通过直线与球交点的计算方法,然后其他诸如深度比较等操作,与Directional Light并无不同,示例代码大致如下:&/p&&div class=&highlight&&&pre&&code class=&language-glsl&&&span&&/span&&span class=&c1&&// _Center 球心坐标&/span&
&span class=&c1&&// _Radius 球半径&/span&
&span class=&c1&&// _InverseViewProjectionMatrix UNITY_MATRIX_VP矩阵的逆矩阵&/span&
&span class=&c1&&// _SampleCount
采样率&/span&
&span class=&c1&&// _VolumetricIntensity 体积光强度&/span&
&span class=&k&&float&/span& &span class=&n&&depth&/span& &span class=&o&&=&/span& &span class=&n&&tex2D&/span&&span class=&p&&(&/span&&span class=&n&&_CameraDepthTexture&/span&&span class=&p&&,&/span& &span class=&n&&i&/span&&span class=&p&&.&/span&&span class=&n&&uv&/span&&span class=&p&&).&/span&&span class=&n&&r&/span&&span class=&p&&;&/span&
&span class=&c1&&//像素深度&/span&
&span class=&k&&float&/span& &span class=&n&&pixelWorldPos&/span&&span class=&o&&=&/span& &span class=&n&&mul&/span&&span class=&p&&(&/span&&span class=&n&&_InverseViewProjectionMatrix&/span&&span class=&p&&,&/span& &span class=&n&&float3&/span&&span class=&p&&(&/span&&span class=&n&&i&/span&&span class=&p&&.&/span&&span class=&n&&uv&/span& &span class=&o&&*&/span& &span class=&mi&&2&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&n&&depth&/span&&span class=&p&&));&/span&
&span class=&c1&&//通过NDC坐标反推世界坐标&/span&
&span class=&n&&float3&/span& &span class=&n&&wpos&/span& &span class=&o&&=&/span& &span class=&p&&...&/span&
&span class=&c1&&//球表面位置&/span&
&span class=&n&&float3&/span& &span class=&n&&wNormal&/span& &span class=&o&&=&/span& &span class=&p&&...&/span& &span class=&c1&&//归一化的球表面法线&/span&
&span class=&n&&float3&/span& &span class=&n&&startPos&/span& &span class=&o&&=&/span& &span class=&n&&wpos&/span&&span class=&p&&;&/span&
&span class=&c1&&//确定线段起始点&/span&
&span class=&n&&float3&/span& &span class=&n&&camToSurface&/span& &span class=&o&&=&/span& &span class=&n&&wpos&/span& &span class=&o&&-&/span& &span class=&n&&_WorldSpaceCameraPos&/span&&span class=&p&&;&/span&&span class=&c1&&//从摄像机到表面的方向,即ray march的迭代方向&/span&
&span class=&k&&float&/span& &span class=&n&&camToSurfaceLength&/span& &span class=&o&&=&/span& &span class=&n&&length&/span&&span class=&p&&(&/span&&span class=&n&&camToSurface&/span&&span class=&p&&);&/span&&span class=&c1&&//摄像机到球表面的距离&/span&
&span class=&n&&float3&/span& &span class=&n&&surfaceToCenter&/span& &span class=&o&&=&/span& &span class=&o&&-&/span&&span class=&n&&wNormal&/span& &span class=&o&&*&/span& &span class=&n&&_Radius&/span&&span class=&p&&;&/span&
&span class=&c1&&//球表面到球心&/span&
&span class=&n&&float3&/span& &span class=&n&&direction&/span& &span class=&o&&=&/span& &span class=&n&&normalize&/span&&span class=&p&&(&/span&&span class=&n&&camToSurface&/span&&span class=&p&&);&/span&
&span class=&c1&&//线段方向&/span&
&span class=&n&&float3&/span& &span class=&n&&desiredEndPoint&/span& &span class=&o&&=&/span& &span class=&n&&startPos&/span& &span class=&o&&+&/span& &span class=&n&&dot&/span&&span class=&p&&(&/span&&span class=&n&&normalize&/span&&span class=&p&&(&/span&&span class=&n&&camToSurface&/span&&span class=&p&&),&/span& &span class=&n&&surfaceToCenter&/span&&span class=&p&&)&/span& &span class=&o&&*&/span& &span class=&mi&&2&/span& &span class=&o&&*&/span& &span class=&n&&camToSurface&/span&&span class=&p&&;&/span&
&span class=&c1&&//这里通过简单的立体几何知识,求得射线与球的前后两个相交点&/span&
&span class=&k&&float&/span& &span class=&n&&m_length&/span& &span class=&o&&=&/span& &span class=&n&&length&/span&&span class=&p&&(&/span&&span class=&n&&startPos&/span& &span class=&o&&-&/span& &span class=&n&&pixelWorldPos&/span&&span class=&p&&);&/span&
&span class=&c1&&//从起点到屏幕像素点的距离&/span&
&span class=&n&&m_length&/span& &span class=&o&&=&/span& &span class=&n&&min&/span&&span class=&p&&(&/span&&span class=&n&&m_length&/span&&span class=&p&&,&/span& &span class=&n&&length&/span&&span class=&p&&(&/span&&span class=&n&&startPos&/span& &span class=&o&&-&/span& &span class=&n&&desiredEndPoint&/span&&span class=&p&&));&/span&&span class=&c1&&//获得最小线段距离&/span&
&span class=&k&&float&/span& &span class=&n&&intensity&/span& &span class=&o&&=&/span& &span class=&mo&&0&/span&&span class=&p&&;&/span&
&span class=&n&&float3&/span& &span class=&n&&currentPoint&/span& &span class=&o&&=&/span& &span class=&n&&startPos&/span&&span class=&p&&;&/span&
&span class=&k&&float&/span& &span class=&n&&perNodeLength&/span& &span class=&o&&=&/span& &span class=&n&&m_length&/span& &span class=&o&&/&/span& &span class=&n&&_SampleCount&/span&&span class=&p&&;&/span&
&span class=&c1&&//每两个采样点之间的}

我要回帖

更多关于 小蜜蜂嗡嗡翁0217 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信