ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity船模浮力与操控实践:从静态模型到可驾驶帆船游艇

Unity船模浮力与操控实践:从静态模型到可驾驶帆船游艇 1. 从商店里拖一艘船进场景只是万里长征第一步先说结论任何一个市场里的船模从导入到能在游戏里开起来中间的距离比你想象的大得多。我见过太多项目美术从商店买了一艘漂亮的帆船拖进场景左转右转发现船像个纸盒子一样飘在水面上——要么半截身埋在水里要么干脆一个后空翻扎进海平面。这不是模型质量的问题而是我们在拿静态展示模型的思路去做可驾驶载具。先说资源导入这一关。帆船和游艇这类资源在Asset Store或CGTrader等平台上的标注通常很模糊你可以把它当静态装饰也可以当交互载具。但两者的三角面数标准完全不同静态展示用的高模船可能动辄几十万甚至上百万个三角面细节到甲板上的缆绳桩、桅杆上的滑轮都建模了而物理模拟驱动的水上载具物理网格Collider Mesh和渲染网格Visual Mesh必须分开处理。你不可能把一艘含全部索具细节的高模船直接丢给刚体当碰撞体物理碰撞每帧对多边形网格做接触检测的成本会把性能拖垮。所以在导入阶段我先看三样东西模型规格面数级别UV是否有合理的岛分布法线是否统一朝外锚点位置资源在建模软件里的原点Origin到底在哪儿是在船底、甲板中线还是桅杆顶部直接决定后面船上驾驶位的Transform基准轴心朝向不同DCC软件导出时轴系不一致最容易出现的就是Maya做的船导入Unity后朝向左转90度或变成斜的这也是为什么那批热词里会出现Maya材质球法线Unity不识别这种问题——本质上就是资源管线缺乏统一规范说一个我反复踩过的坑军博和游戏论坛里很多人吹某个优质帆船模型说UV那叫一个精致结果下载下来发现它是为渲染器预设的法线贴图是OpenGL坐标系的绿色通道翻转版本进Unity后所有法线细节直接变得凹凸不平甚至反向。你在Unity里如果把法线贴图搞反了船体侧面会呈现肉眼可见的凹陷。修理办法倒是不难可以在Shader里对绿色通道乘以-1或者用纹理导入设置里的Flip Green Channel选项但这个知识点对一批刚开始接触Unity的人可能完全没意识到。一个经验丰富的Unity开发者的习惯是在导入这艘船之前先在Unity里建一个空的Project导入模型确认原点位置和轴向对齐再挂上刚体和Collider做初步的浮力测试。这个习惯看起来浪费半小时却能避免后期所有在已有场景里折腾半天才发现是模型源文件有问题的尴尬。2. 浮力和船体这些物理运算不是内置的很多人以为Unity自带浮力系统拖一个Buoyancy组件进去就完事了。实际上Unity没有原生的船体浮力解决方案。社区和商店里看到的各种浮力实现底层用的基本是阿基米德原理的近似算法。把物理原理说透物体所受浮力等于它排开液体的重量。船的吃水深度只要足够排开等重的海水船就能浮起来。但游戏引擎里不可能真的把船体包围盒切成无数个格子实时计算排开体积那样性能吃不消。最常见的做法是分布采样点法——在船体上预先放置一批假想的浮力采样点Buoyancy Point每个点检测它是否没入水面如果没入就根据入水深度给一个向上的推力推力大小相当于该点处排开水的重量。看图就知道这个概念想象一枚硬币浮在水面你把它压下去的一半它受到向上推的力大大增加。对应到代码里做这件事就只需要这三步在船体内部或表面布置8到12个采样点每帧检测每个采样点相对水面高度正确的做法是用水体的波函数算出水面高度而不是用静态plane对入水的点施加浮力同时对船的刚体施加阻力布点的时候有个关键细节点不要只布在船底。你需要在船底部放多个点用来支撑静态浮力但在船两侧接近水线的位置也要布点否则当船舷倾斜浸水时没有对应浮力点来产生恢复力矩船就会像喝醉一样侧翻还不回来。我自己调试过的一个项目里帆船侧倾到35度时完全失控怎么打舵都回正不了最后排查下来就是因为两侧水线附近没布浮力点。浮力之外另一项关键物理是阻力模型。你总不希望这艘船在水里以恒定加速一直开下去——那不符合直觉。这里需要用水阻力近似阻力和速度的平方成正比方向与速度相反。考虑到船的航向特性大致可以用一个系数数组区分纵向阻力、横向阻力和垂向阻力。船首尾方向阻力最小侧面阻力最大这样船才会顺着船头方向走而不是像一块漂在水面的泡沫板随便来一个浪就被横着推走。然后你会面临一个新问题——水面的波浪不是平的。如果你用静态平面做水面所有浮力运算都基于y0的平面高度那船在水里就像在游泳池的镜面水面上滑动完全没有起伏感。要想让船能跟着浪上下摇采样点处的水面高度必须由波形函数计算。这里我联想到搜词里有个高频词是mathf.perlinnoise很多人喜欢用Perlin噪声做水面高度生成。Perlin噪声做水面其实可用但缺点是它生成的浪非常平没有那种大浪和小浪叠加的层次感。更贴近水面的做法是叠加多个不同方向、不同波长、不同振幅的正弦波Gerstner Wave这样的水面有波峰尖锐、波谷平缓的视觉特性船在波浪中也会产生更真实的俯仰和横摇。一个常见坑Gerstner Wave这种需要Shader参与的水面在浮力采样时必须在CPU端用C#复算一遍波形方程因为Shader算出来的波高不在CPU里。我也见过有人用RenderTexture把Wave Height烘焙出来再拿去做浮力采样但那个方案复杂度高对小型项目来说通常不值得。3. 帆船的控制风是核心帆和其他动力不一样接下来讲帆船。帆船和游艇最大的不同在于游艇靠发动机和螺旋桨动力控制逻辑和开车类似帆船靠风油门根本不是一个能直接调大小的参数而是体现在帆的迎角上。先说最简单的入门方案把帆船的推进力和风力解耦。也就是说玩家按键控制帆的收放tack系统根据当前风向与船头方向的夹角和帆的收放程度乘积得出推进力。这个方案虽然简化到不能再简化但至少玩家会产生哦帆船是按风驱动的这种感受而不是以为船尾装了隐形马达。这是能让项目先跑起来、再优化的路线。更好的进阶方案是引入**真风True Wind与视风Apparent Wind**的区分。真风是自然环境中地表的实际风向风速视风是站在船上感受的风——它等于真风向量减去船自身速度向量。所以当你迎风航行时即使真风只有5节由于船本身的运动导致合成风速变大你会感觉风力更强。帆船游戏的高级手感差异很大程度来自这里有没有做对。许多帆船模拟游戏的核心操作其实就是围绕如何调整帆的角度使它在当前视风下获得最大升力展开的这和飞机机翼产生升力的原理有相通之处。当你要实现帆面角度调整可以先从简单的做法开始帆面是一个铰接物体它的默认角度受风压影响然后玩家通过按键调整它的目标角度角速度由伺服速率直接限制。具体数值上帆的转动角速度不宜太快否则就失去了操控帆的感觉。我的一艘主力帆船项目里主帆伺服转动速度设置为每秒12度到18度之间大约是玩家能从全松状态收到全紧状态需要3到4秒的体感——这个节奏刚刚好。关于航行方向的常识也给新手提示一下避免做出违背物理直觉的游戏逻辑帆船不能直接顶风行驶迎角小于约35度到40度时帆会开始失速推进力趋近于零侧风行驶是顺风效率最高的角度推进力最大顺风正后方来风其实比侧风效率低因为这时帆面只能起到阻力作用升力方向与航行方向不一致所以帆船游戏的操作始终是之字形换舷tacking大部分玩家第一次接触会困惑为什么不能像快艇那样自由转向这时候如果能在游戏内给一个简单的提示界面说明当前船与风向的夹角状态体感会好很多。桅杆和船体的力矩也不可忽略。当帆上有很大的侧向力时船会有明显的横倾角度heel。这个横倾角度反过来会改变帆受风的投影面积形成负反馈。如果游戏里做得精细一点你会看到船越来越倾斜、帆受力面积减小、推进力下降直到一个动态平衡点。这个平衡点带来的手感正是帆船和游艇拉开差距的地方。回归实操开发生意上我不建议一开始就做完整的视风模型和失速模拟。先做简化风模型加帆角控制把整条链路跑通验证游戏的游玩循环有没有趣再逐步加复杂度和精确度。因为风帆控制的物理调参是个无底洞物理越复杂你需要调的参数越多玩法可能反而会被数值折腾得失去原始乐趣。4. 游艇的机动性低速的船和高速的船根本是两种怪物游艇的动力逻辑看起来比帆船简单——一个螺旋桨推力一个舵机转角加上海上阻力。但当你真正把这个逻辑跑起来会发现低速和高速工况下的操纵特性差异极大这也是游艇区别于普通船只的核心课题之一。低速工况下比如离港、靠泊水流速度低舵面周围的水流不足以提供足够横向力所以转向很不灵敏。这就是为什么真实游艇会配备侧推器bow thruster——一个装在艇艏的横向小推进器帮助低速状态下平移。如果你做一个游艇游戏但只用一把舵玩家在离港时就会疯狂撞码头觉得自己蠢得像不会开车。这个负反馈来自设计缺失而不是玩家能力问题。高速工况下游艇的船体会产生滑行效应planing。船速越过某个临界值后船体被水动力抬起吃水面积大幅减小表面阻力陡降速度有一个明显的二次提升。你要在游艇游戏里做出推背感和高速度带来的兴奋感滑行效应就是必须模拟的机制。实现方式不复杂根据当前船速来动态调整吃水深度和浮力分布一个简单的Lerp函数就能搞定——决定船在排水状态和滑行状态之间的过渡权重。具体公式上的思路大概是这样的planingFactor smoothstep(criticalSpeed - transitionRange, criticalSpeed transitionRange, speed)当船速超过临界速度后吃水深度逐渐减小水阻力项整体乘以一个随planingFactor下降的系数。实测下来临界速度的选取非常影响手感我建议以游艇的全长来估算6到9米的玻璃钢运动艇滑行速度大约在15到20节上下能把临界速度当作可调参数暴露在Inspector面板里方便策划不断调试。游艇的旋回半径也和帆船完全不同。帆船因为桅杆和船体的流动特性转向响应极慢游艇在高速下的转向响应很快但惯性巨大——你打满舵船身会有一个明显的等待——压过去——外甩过程。这种迟滞感用刚体和角速度阻尼配合模拟时关键是侧滑摩擦调参要到位。这里有一个在物理引擎里很容易犯的错误引擎默认的刚体阻力是线性的船在Z方向自身坐标系收到1000N推力就线性加速看上去像太空飞行器。为了模拟水的阻力必须把阻力设成相对速度平方相关的函数而不仅仅是线性的。否则船永远不会达到一个稳定航速的平衡点玩家会感觉这艘船在水面上像鱼儿一样越游越快根本刹不住。在具体项目实现里可以针对不同的船型设置不同的阻力曲线配置甚至做成曲线资产AnimationCurve在Inspector里直接调整。你要明白船是唯一一种质量巨大但加速度平缓、方向盘又极度不线性的常见载具它和车、飞机的手感模型完全是两套逻辑。关于转向控制还有一个细节当你在高速状态下急打舵游艇还会产生明显的横倾heel和甩尾。如果你不做这个反馈玩家会觉得船像一台全向移动的无人机漂浮在水面上一样少了被水托着走的重量感。做法是给刚体增加一个与横向角速度相关的额外力矩模拟重心较高时产生的侧倾力矩。游艇项目里玩家说得最多的一句话我在反馈问卷里看到过的原话是我感觉这艘船好飘。我觉得这里的飘指的就是高速转向时船体没有侧倾、没有头部上抬、没有任何和水面角动量交互的视觉反馈。5. 摄像机跟随船和车不一样镜头必须处理水面的黏性很多人在做船时直接用第三人称摄像机套上现成的SmoothFollow脚本然后运行一看镜头从船上跳来跳去画面抖得像在洗衣机里拍的。船类摄像机Follow和车类在本质上有一个区别**船的偏航角速度慢但俯仰和横摇却非常活跃。**这是因为船的质心低、浮力分布广水波给了它持续的周期性角扰动。我倾向于将船上的摄像机拆成两部分来处理**第一部分是位置跟随。**摄像机位置不应该严格绑定船体坐标而是绑定一个船体的平滑跟随点。这个点在船尾上方3到6米、偏后1.5到2.5米的位置。位置跟随用带阻尼的插值——不要用单纯的Lerp固定系数建议使用带阻尼的半弹簧模型临界阻尼模型否则摄像机起停的刹那会有明显晃动。一个简单的二阶临界阻尼公式可以用在Update里比只做SmoothDamp还要稳。**第二部分是旋转跟随。**摄像机旋转的参考轴应该来自船的行驶方向而非船体的瞬时姿态。也就是说船在高速行驶抬头bow up时镜头不应该完全跟着抬头否则画面上方全是天空下方全是船头什么都看不见。正确做法是把船的航行向量作为镜头前向的基准然后加上一个由俯仰、横摇角乘上权重系数后产生的偏移量。权重值我调了多次后觉得0.25到0.35之间比较合适——既保留了水面起伏带来的沉浸感又不会让玩家晕船。还有一个新手容易忽略的细节**摄像机与船之间不要有物理碰撞器。**很多人的镜头脚本会自动与船的甲板、舱室、船舱顶碰出奇怪的视角。别试图用碰撞层来解决这个问题那只会产生各种抽风。更好的做法是在船体上设置若干个摄像机候选点让镜头根据船体姿态自动选择最佳视点。这个方案在复杂的帆船高桅杆、多条绳索场景里尤为有用。顺带说一个高频搜索词相关的坑那些unity 摄像机跟随的问题里80%的人其实是把Unity的Cinemachine当成了三流跟随工具来用。Cinemachine在船上如果直接Follow一个船体Transform效果往往很差。一定要在Cinemachine的CinemachineVirtualCamera上设置目标LookAt并配合其他组件来实现看向一个移动中的、自带旋转的目标时的平滑延迟。就我的经验而言在船上最好给Cinemachine设置一个专门的LookAt点而不是直接LookAt整艘船的Transform。LookAt点可以放在船头前方大约3米、海面上方0.5米到1米的位置这样镜头视角会始终包含船体和前方的海面空间有良好的驾驶视野。还有一点涉及性能如果船在场景里开了很远摄像机在远处看它时LOD和阴影管理会变得复杂。你不可能让摄像机永远贴着船也不可能在没有远距离LOD切换的情况下让整艘船一直以全分辨率渲染。资源里的LOD组LOD Group必须配好否则你船模的75000个三角形在远景会直接把帧率拖成PPT。6. 水和船体的交互渲染Shadergraph能做但别迷信刚才浮力部分提到水面波函数这次讲渲染侧的船与水交互。搜词列表里高频出现unity shadergraph 假室内、unity双面材质 shader、unity辉光怎么做post吗这些词说明大家对ShaderGraph已经比较熟悉但对水面材质和船体材质的特殊性还缺少系统概念。先说水的Shader。真正能做到看起来像海水的Shader至少要包含以下这几层要素多层波浪法线叠加不同频率和方向的法线扰动让水面在光照下产生细碎高光菲涅尔效果Fresnel视线角度越平水的反射越强。这会让远处海面呈现天空色近处的浪花反射更多阳光次表面散射的近似水在波峰附近透光、呈蓝绿色在波谷附近显得更暗泡沫在浪尖或船体切割水面处产生白色泡沫是水活的关键泡沫这个点特别重要因为它是船与水面交互最直观的视觉证据。你在海面上看见一艘船第一眼判断这船是搁在那里还是真的在航行靠的就是船首破浪的白色泡沫以及船尾拖出的航迹。如果没有破浪泡沫和尾流就算物理算得再精确玩家看起来也是一艘塑料船在玻璃板上滑行。实现船体周围泡沫的最快办法是在船头下方加一个粒子发射器在航行时根据速度发射白沫粒子再在船尾做一条尾流带可以用一个长条形的Quad贴图配合扭曲Shader实现。进阶一点的方案是在船体周围采样水面高度生成一个高度差贴图凡是高度差超过阈值的区域输出白色泡沫。总之核心思路是哪里受到船扰动、水面被压缩或抬高哪里就该有泡沫别把泡沫做成一团假的贴图。再说一个冷门的Shader坑就是上面提到的双面材质 shader。船体和帆面经常会遇到双面渲染的问题——从背面看时模型竟然透明了这是因为Unity默认的Lit Shader只渲染正面。如果你的帆船资源面朝向不规范很多商店模型确实如此正面反面混用开双面渲染几乎是必须的。做法是在ShaderGraph的Master Stack里把Surface Option里的Cull Mode改成Off或者写一段极简的Surface Shader把Cull指令关掉。改完之后帆的背面也能正确显示配合布料模拟Cloth或者简单的顶点波浪动画帆布才有随风鼓动的感觉。有个性能优化提醒双面渲染会明显增加片元处理成本不要在远处的大型场景物体上全局开双面。只用在你确认真有问题的船模部件上并且尽量用更小的纹理尺寸来控制带宽消耗。这边还关联到阴影问题——unity阴影问题也是那组热词里的常客。在海上场景里船和水的阴影处理比室内场景麻烦得多水的平面接收阴影时如果阴影贴图分辨率不够远处会出现可怕的阴影锯齿或者整片闪烁。解决思路有三条其一是让水面的阴影材质用更高的Shadow Map分辨率其二是给水面Shader关闭接收阴影选项改用实时光源自己计算的阴影颜色毕竟水面本来就该有周围反射的环境光其三是如果你做的是靠近海岸的场景树、建筑和船的阴影可以用简化的平面投影Projector方案来替代传统阴影管线。每一台游戏机的GPU预算都是有限水面的阴影常常是第一个被砍掉的东西。7. 在海面场景中角色/货架上的常见问题从脚本控制到输入矛盾我还想聊聊在船上做交互设计时很容易忽略的几个非物理问题。搜词列表里出现了unity脚本控制逐渐消失、unity中的layermask与renderinglayermask的区别是什么这些都触及到船类场景中的常规LeCun失败环节。第一个常见问题人物在甲板上行走时容易滑出船体。很多船模有复杂的甲板结构甲板边缘、舱门盖、锚缆孔之间总有一些模型缝隙。如果你在船甲板上放一个可控制的角色角色踩到缝隙边缘就直接穿过模型掉下去。标准做法是给甲板轮廓生成一个简易的甲板可行走范围Collider——用一个坡度缓和的Box Collider覆盖整条甲板上面再用几个小Box把关键障碍物桅杆、救生圈、箱体包出来。这种大一号的碰撞方案在船上比精细的Mesh Collider好维护得多。第二个问题驾驶时输入源冲突。如果船同时支持键盘、手柄和屏幕触控驾驶输入和角色移动输入就容易打架。比如按W时角色在走但船也在加速玩家一按就角色跑向船边然后被船带着一起调整姿态画面很乱。我建议定义清晰的驾驶模式切换只有角色进入驾驶位靠近舵轮时才允许船舶输入在甲板上行走时所有驾驶输入全部丢弃。做法可以是让驾驶位周围有一个Trigger Zone角色进入Enable船舶输入脚本离开Disable。第三个问题图层管理。一个刚入行的朋友还在费解Unity里Layermask和Rendering Layer Mask的区别结果在船项目的是否让船体遮挡角色这个需求里卡了一整天。在Unity的Camera里当角色在船舷后面移动时如果不希望角色遮挡太突兀可以配合Layermask物理检测用和Sorting相关属性渲染排序用分别处理碰撞和显示层级。要记住物理层和渲染层的坐标系是两套独立系统分开设置其实才是正规玩法。第四个问题船的LOD和不可见时的裁剪。海上场景几乎没有任何遮挡物镜头推向远海时整艘船的渲染和无尽的天空/水面合成到一起。这里的优化建议是把船体的渲染距离拆开层级LOD0近处全细节保持25米左右LOD1降到一半面数LOD2只剩一个简单的船体剪影Box用于几百米外的远景。如果做不到LOD至少给整个船体勾选Occlusion Culling的合适设置避免镜头在下甲板时依然渲染上甲板的大量物体。用代码控制LOD其实很简单关键在于美术愿意为不同距离产出不同精度的模型——很多买来的船资源只带一个高模你需要自己在Blender或Maya里做减面版本。不想麻烦的其实可以顺手在商店里找那类Optimized LOD Pack之类的资源包补一下减面模型。8. 优化技巧一艘船都不卡但10艘船就得卡最后说说性能。你在做海上游戏时目标不可能是一艘孤零零的小船迟早要面对大量船只同屏的情况——避风港里停着十几艘游艇、比赛场景里帆船挤在一起、渔港里渔船穿梭。这时的性能热点往往不在船体本身而是在水面Shader、粒子泡沫和阴影上。第一个思路是共享水面渲染。把水面做成一个大范围的Quad或者网格平面而不是每艘船周围单独挂一块水面。所有船只浮力采样走同一个波函数同一个网格平面用同一个材质渲染这样不会出现交界处水面不平滑的观感。同时水面的网格细分可以按距离动态变化——摄像机近处细密远处稀疏大幅减少顶点数量。UWA等性能分析报告里经常出现水面上万顶点这种可视化大坑其实完全可以通过一张Tileable波法线贴图来大幅补偿远处视觉细节。第二个思路是粒子泡沫数量要设上限。船多了以后每艘船的破浪泡沫粒子如果各自发射全屏粒子数量会爆炸。我给一个参考数值单艘船的船首泡沫粒子池控制在200个以内屏幕内同时活跃的泡沫粒子总量控制在4000个左右尾流泡沫用贴图动画替代粒子成本几乎为0。这样即使在港口场景里塞进8到10艘船同时动粒子也不至于成为瓶颈。第三个思路是阴影范围的克扣。海上场景里太阳光产生的实时阴影是很贵的尤其当阴影投射范围包含大片波动的海面时。可以在Light组件的Shadow Distance上动手比如视距30米以内才显示实时阴影更远处只保留环境光遮蔽或干脆不投阴影。针对船体特定重要程度你可以利用Shadow Caster的Culling Mask单独控制哪些物体会投射阴影。不然一艘LOD0的帆船在高海的阳光下投射出的密集网格阴影足以让中端移动设备直接掉到20帧。这里我想提一下unity游戏去马赛克这个词。它跟船体远距离渲染有一定关系当你的船在远处LOD切换或者多重采样抗锯齿采样不足时物体边缘会出现严重的锯齿/马赛克闪烁。船模上的细绳、缆索、桅杆在横向扫描时特别容易闪因为它们的屏幕宽度常常不到1像素。对付这种闪烁最好的方案不是无脑提高MSAA到8x而是给这些细小部件单独设置一个边缘加大的Shader即Pixel Width Offset或者厚度扩展让它们在屏幕空间至少占1.5到2像素宽。这个技巧在船模出海场景里几乎必备否则玩家视角拉远一点看到的桅杆绳索全是跳动的虚线。还有一处移动端特别容易踩的坑是水面反射。如果直接上Planar Reflection或SSR海面会渲染两倍的场景移动端很难扛住。替代方案是用Cubemap环境贴图加粗糙度高光来骗。夜里或阴天时把反射强度降低利用高光来模拟水面阳光闪烁效果好且性能开销极低。消息的最后我还想补充一个使用姿势层面的心得。在做船项目前别直接追求写一个全网最强浮力系统。先做一艘简单的筏子或者几艘不用的船验证你的浮力算法、水波交互和摄像机手感跑通一个最小闭环再复制到正式船型。因为当你把控制、渲染和物理细分同时加进去以后你会发现自己90%的时间都花在调参而不是写功能上。这也不是让你刻意精简而是一条我在多个船/海项目里都验证过的路径**先把一搜艘船开到能开、不沉、不飘、不闪的程度再开始做第二艘。**这比试图直接搓一个完整游艇系统然后面对几十个互相耦合的参数无从下手要高效得多。如果你正在为一个新项目选船资源我最后的实际建议是去试加载资源的时候先把阴影关了把水面改成纯色材质跑一圈浮力测试看看船在纯物理层面是否像样。物理层过关再逐步把Shader、粒子、摄像机、UI这些看得见的东西叠上去。每一次只开一个开关系统性地验证出问题时你才知道是哪个层面的锅。这套方法论听着朴素但在船类项目混乱的组件关系里真的是最省时间的排错顺序。
返回列表