ARTICLE DETAIL

资讯详情

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

Unity 2D水体Shader实战:顶点波浪与GrabPass折射全解析

Unity 2D水体Shader实战:顶点波浪与GrabPass折射全解析 简介面向Unity 2D游戏开发者的一份水体浮力效果实现包借助内置BuoyancyEffector2D物理组件模拟物体在水中的漂浮、沉没与波浪扰动适合需要快速为横版或俯视2D场景加入真实水交互的游戏项目。压缩包体积仅38KB包含37个文件主要涵盖Unity场景与工程设置asset、水面Shader、材质mat、控制脚本cs以及png贴图、json配置等结构精简便于直接导入工程对照调整。已有1158人学习下载。资源内附完整示例场景与工程设置开发者可查看BuoyancyEffector2D的参数配置、水面渲染Shader及物体浮力计算逻辑理解液体密度、浮力系数、接触权重等关键属性如何影响物理表现同时通过ProjectSettings下的Physics2D配置可快速复用现成方案免去从零搭建复杂模拟的步骤。整体来看这份小体积资源适合初中级Unity开发者学习2D水体物理交互与水面表现也适合作为项目迭代时的参考模板。1. 2dwater 资源是什么一份让水面“动起来”的 Unity2d 水体 Shader 资源做 2D 横版项目时水面是最容易露怯的地方贴一张蓝色半透明图动都不动玩家走过去完全没有“踩到水”的反馈。2dwater 这类 Unity2d 水体资源解决的正是这个环节——它用一套 Shader 在渲染层同时处理波浪顶点偏移、GrabPass 背景折射、UV 扰动和深浅水混色C# 脚本只负责透传参数不需要逐帧计算。适合横版动作、俯视角生存、塔防这些需要水面反光但美术没精力单独维护水体动画的团队。我按拆包的顺序把渲染管线、场景搭建、正交相机适配和移动端踩坑都过了一遍后面直接给可抄的参数。2. 渲染管线拆解顶点波浪、GrabPass 折射与 UV 扰动2.1 顶点波浪正弦叠加怎么做才不重复水面最容易露馅的地方是波浪看起来像机械钟摆一个周期扫过去观众立刻发现这是假的。多数 2dwater 类资源不会用单条正弦波而是至少叠两条频率、速度、相位都错开的波。第二波的频率乘一个无理数比如 1.7速度乘 0.6波形包络的重复周期会被拉长到肉眼几乎察觉不到的程度。下面是 Shader 里顶点阶段的典型写法v2f vert (appdata v) { v2f o; // 第一波主波沿 UV.x 方向传播 float speed1 _Time.y * _WaveSpeed; float wave1 sin(v.uv.x * _WaveFreq speed1) * _WaveAmp; // 第二波副波频率错开 1.7 倍速度错开 0.6 倍 float wave2 sin(v.uv.x * _WaveFreq * 1.7 speed1 * 0.6) * _WaveAmp * 0.5; // 叠加到顶点 Y 上形成水面起伏 v.vertex.y wave1 wave2; o.pos UnityObjectToClipPos(v.vertex); o.uv v.uv; o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; o.screenPos ComputeGrabScreenPos(o.pos); return o; }两个波叠加之后整体波形不会像单条正弦波那样每几秒原样重复一遍而是出现类似“两个不同频率的波互相干涉”的效果波峰和波谷的位置一直在缓慢变化。注意_WaveFreq控制的是空间频率数值越大波越密不是越快速度快慢由_WaveSpeed决定两者容易混淆调参时先分清再动手。如果你的水面不是横版长条而是岛屿四周一圈水只靠uv.x做相位输入是不够的四周的波浪会同时向同一个方向传播看起来像整圈水在朝一边倒。常见做法是用atan2(worldPos.x, worldPos.z)算出每个顶点相对水面的角度把角度当相位输入波浪就会以水面中心为圆心向外扩散。2.2 片段着色GrabPass 背板采样与深浅水混色顶点波浪只负责“形”水面看起来像不像水关键在片段着色器对背景的折射采样。2dwater 类资源里最常见的做法是用GrabPass抓取当前屏幕内容然后用水面法线的 XY 分量去偏移屏幕 UV模拟“透过水面看下面物体”的折射效果。fixed4 frag (v2f i) : SV_Target { // 噪声贴图随时间滚动产生波纹扰动 float2 noiseUV i.uv * _NoiseTex_ST.xy _NoiseTex_ST.zw _Time.y * 0.05; float3 noise UnpackNormal(tex2D(_NoiseTex, noiseUV)); // 用扰动值偏移屏幕坐标模拟折射 float2 refrOffset noise.xy * _RefrStrength * 0.1; float2 backUV i.screenPos.xy / i.screenPos.w refrOffset; fixed3 backColor tex2D(_WaterBackTex, backUV).rgb; // 深浅水颜色按扰动强度混合扰动强的地方水色更浅 float waveMask saturate(noise.z 0.5); fixed3 waterColor lerp(_WaterColorDeep, _WaterColorShallow, waveMask); // 折射背景与水色按比例混合避免完全盖住水下物体 fixed3 col lerp(backColor, waterColor, 0.7); return fixed4(col, 1.0); }片段前的GrabPass { _WaterBackTex }是整段逻辑的前提这句话告诉 Unity 先把当前画面抓进_WaterBackTex之后在这个 Pass 里采样。抓屏是一次全屏拷贝消耗不小后面避坑章节会专门说这个事的代价。refrOffset是这段代码的核心边界参数它把噪声法线的 XY 分量乘_RefrStrength再缩小 10 倍作为屏幕 UV 偏移量。_RefrStrength的合理区间在 0.02 到 0.1 之间。低于 0.02 折射几乎看不见水下物体会被水色完全盖住高于 0.1 背景会被搅成一团糊影玩家会以为画面花屏。这是个典型的“玄学参数”必须结合你的噪声贴图对比度来调。2.3 参数表与调参思路以下是这套 Shader 的核心参数表按我调试时的优先级排列参数作用建议区间常见误用_WaveAmp波浪顶点振幅0.005 0.02调太大导致水面网格穿插周围物体_WaveFreq波浪空间频率1.0 3.0调太大变成高频抖动而非水波_WaveSpeed流速0.5 1.5超过 2.0 波浪像快进_RefrStrength折射强度0.02 0.1超过 0.1 背景糊成一片_NoiseTex_ST.zw扰动贴图滚动偏移0.01 0.1为 0 时水波静止不动_WaterColorDeep深水色深蓝/深青与浅水色亮度差过小会失去立体感调参时有个快速定位问题的方法如果整面水是纯色、一点波纹都没有不要先去动波幅先检查_NoiseTex的 Wrap Mode 是不是 Repeat。很多下载的噪声贴图默认是 Clamp 模式UV 滚动到边缘就停住画面里会出现一条明显的硬边波纹也只滚动一次就卡死。这是新手最容易忽略的一步基本上 80% 的“水面没反应”都是贴图平铺设置的问题不是 Shader 代码的问题。3. 场景搭建与参数驱动把 Shader 变成可控的水面组件3.1 场景搭建步骤与材质设置Shader 到位之后场景搭建的顺序会影响你后面排错的难度。我一般按下面这个顺序走在场景中新建一个 Quad 或 Plane旋转成你需要的朝向。横版游戏一般把 Quad 立起来放在背景层俯视角游戏则让它平躺在 Y 轴上。创建材质把 Shader 指定为上一步写好的2dwater/WaterSurface。给材质分配两张贴图主纹理给一张水面颜色渐变图扰动贴图给一张法线或噪声图。将材质拖到 Quad 的 MeshRenderer 上。给水面物体挂上PolygonCollider2D并勾选isTrigger用于角色踩水的触水反馈。第 5 步容易被忽略因为很多人以为“水面只是视觉不需要碰撞”。如果你的游戏有子弹入水、角色踩水花这类交互没有碰撞体这些逻辑全都要在别的系统里绕路实现。水面本身的碰撞体不需要和视觉波浪完全贴合大致覆盖矩形区域即可精度问题后面避坑章节再说。3.2 C# 控制脚本用 MaterialPropertyBlock 透传参数Shader 里的参数不能直接裸露在 Inspector 上让策划乱调最好用一个 C# 脚本统一管理、统一驱动。我拆这个资源包时发现它自带的控制脚本核心逻辑可以用下面这段代码概括using UnityEngine; [RequireComponent(typeof(Renderer))] public class WaterSurface : MonoBehaviour { [Range(0.001f, 0.05f)] public float waveAmp 0.01f; [Range(0.5f, 3.0f)] public float waveFreq 1.5f; [Range(0.0f, 2.0f)] public float waveSpeed 0.8f; [Range(0.0f, 0.15f)] public float refrStrength 0.05f; private MaterialPropertyBlock block; private Renderer mRenderer; void Awake() { mRenderer GetComponentRenderer(); block new MaterialPropertyBlock(); } void Update() { // 从 Renderer 上取现有 PropertyBlock避免覆盖其它人设置的属性 mRenderer.GetPropertyBlock(block); // 把脚本参数写入 Block运行时不会生成材质实例 block.SetFloat(_WaveAmp, waveAmp); block.SetFloat(_WaveFreq, waveFreq); block.SetFloat(_WaveSpeed, waveSpeed); block.SetFloat(_RefrStrength, refrStrength); // 写回 RendererShader 会在下一帧读到这些参数 mRenderer.SetPropertyBlock(block); } }这段代码有两个关键点。第一用的不是material.SetFloat而是MaterialPropertyBlock。直接改 material 会在运行时生成一个材质实例场景里如果有十面水就会多出十份材质内存而且美术在 Inspector 上做的调色会全部失效。用 PropertyBlock 只是覆盖这一份 Renderer 上的参数材质本身的默认值仍然保留。第二参数全部加了Range特性Inspector 面板上会直接出现滑动条策划和美术不需要看懂 Shader 就能调出水波效果。注意参数名要和 Shader 里的Properties块名称严格一致拼错一个字母不会报错但属性会一直使用默认值肉眼很难发现。3.3 触水碰撞与互动事件的挂接控制脚本只解决“水面动起来”的问题游戏逻辑里通常还需要知道“角色有没有碰到水”。最常见的做法是给水面物体的 Collider2D 挂一个专门的监听脚本private void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag(Player)) { // 通知角色进入水面减速、改变移动方式、播放入水音效 other.GetComponentPlayerMovement().EnterWater(); } } private void OnTriggerExit2D(Collider2D other) { if (other.CompareTag(Player)) { other.GetComponentPlayerMovement().ExitWater(); } }这个脚本本身很简单但有一个容易忽略的边界如果角色快速下坠穿过水面一帧之内可能同时触发 Enter 和 Exit导致逻辑漏掉入水状态。我一般会在 EnterWater 里记一个进入时间戳Exit 时判断与进入时间的间隔小于 0.1 秒就忽略这次退出避免高速物体“穿过”水面造成的状态抖动。4. 正交相机适配为什么波浪看不见以及 UV 方案补救4.1 现象横版项目里波浪几乎不存在很多 Unity2d 项目用的是正交相机把 2dwater 资源包拖进场景后最常翻车的现象是透视相机下明明有起伏的波浪一切到正交相机就变成一条静止的线不管把_WaveAmp拉到多大都没有明显改善。我第一次遇到时一度怀疑是 Shader 没编译成功在 Inspector 里把所有参数拖了一遍才确认代码没问题。4.2 原因正交投影对顶点位移不敏感问题出在投影方式上。透视相机的特点是近大远小靠近镜头的顶点位移会被放大所以顶点波浪在透视视角下非常明显。正交相机没有这个放大机制画面里所有物体的大小不随距离变化顶点在 Y 轴上的那点位移在侧视角下叠加上整个水面网格看起来就像一条完全没有起伏的线。更麻烦的是片段着色器里的折射扰动依赖法线方向变化。法线扰动产生的屏幕 UV 偏移量在透视投影下会按深度产生视差效果正交投影下视差为 0同样的_RefrStrength值折射偏移在屏幕上几乎看不出变化。这就是为什么“参数没变效果却没了”的直接原因——不是 Shader 失效而是投影矩阵改变了扰动量的视觉放大比例。4.3 适配方案与参数区间针对正交相机我一般会把重心从顶点波浪转移到 UV 扰动上让“波纹感”来自贴图滚动而不是顶点位移。具体参数要按下面的区间重新标定参数透视相机正交相机调整原因_WaveAmp0.005 0.020.001 0.003振幅再大也看不出反而会穿帮_WaveFreq1.0 3.01.0 2.0频率太高正交视角下变成抖动_RefrStrength0.02 0.10.05 0.2需要更大的偏移量补偿视差缺失_NoiseTex滚动速度0.01 0.050.05 0.1加快滚动弥补顶点动感不足按这个表调整后水面在正交相机下会呈现一种“流动的波纹质感”虽然没有透视视角下那种明显的立体起伏但动态感完全够用。如果你的美术坚持要波浪形状我见过的一个做法是直接用 Sprites 序列帧替换 Shader 里的主纹理把水波做成逐帧动画Shader 只负责颜色混合和折射。缺点是内存占用增大但 2D 项目里水面范围通常不大一张 512x512 的序列帧纹理完全可控。5. 避坑实录透明排序、碰撞同步与移动端闪屏5.1 GrabPass 掉帧每帧抓屏的代价现象水面范围铺大之后帧率从 60 掉到 40尤其是在水面占屏幕一半以上的场景里。原因GrabPass每帧做一次全屏纹理拷贝分辨率越高拷贝耗时越大。这个开销在 PC 上不明显但在移动端是肉眼可见的掉帧来源。解决如果项目只在一两个关卡用到水面保留GrabPass没问题如果水面是全局永久资源我建议把抓屏次数压到最低。常见做法是把GrabPass替换为RenderTexture只渲染背景相机到纹理每帧只更新一次或者直接关掉折射用噪声贴图的扰动伪造一个背景混合效果。后者在正交相机下画质损失很小性能提升却最直接。5.2 碰撞与视觉不同步子弹打在水面上方现象子弹在水面视觉上方飞过时已经触发入水判定玩家看起来子弹没有接触水却暴起水花。原因水面 Collider 是平面视觉波浪在 Y 轴上的起伏只是渲染效果碰撞检测用的还是那个平面。加上正交相机下视觉误差会被放大碰撞范围看起来总比视觉大。解决不要追求 Collider 和视觉完全同步那不现实。把 Collider 的 Y 轴高度往下压 0.2 到 0.5 个单位让判定点略低于视觉水面。玩家主观感受是“接触到水面才触发”比“提前触发”自然得多。如果一定要高精度碰撞给水面做一个细分网格的 Collider 并逐帧更新顶点性能开销很大2D 项目一般不划算。5.3 透明物体渲染顺序闪屏现象角色站在水面后面时角色模型时而被水面遮住时而显示在水面之上画面闪烁。原因水面材质队列是 Transparent角色和其它透明物体之间的渲染顺序没有显式排序。Unity 对透明物体的排序依赖材质队列和 Sorting Layer如果两者的设置不一致就可能出现这一帧水面先画、下一帧角色先画的抖动。解决第一水面材质标签里必须保留QueueTransparent第二在 Inspector 上把水面的 Sorting Order 设为负值比如 -1让角色默认 0永远排在前面。这个设置看起来不起眼但能根治闪屏。注意 Sorting Order 只对 SpriteRenderer 的物体生效如果你的角色是 MeshRenderer需要在渲染管线上另行处理。5.4 部分安卓机型水面黑屏现象同一套 Shader在 PC 上正常打包到某台安卓机上水面区域直接变成黑色或完全看不见。原因旧款安卓机的 GPU 对UnpackNormal的支持不一致。UnpackNormal内部按压缩格式法线贴图的编码方式解码如果贴图压缩成 DXT 格式后解码路径异常采样出来全是噪声颜色就崩了。另外一部分设备对GrabPass的支持也有限抓屏采样返回空值。解决第一把扰动贴图格式强制设为 RGBA 32 bit不做贴图压缩第二在 Shader 末尾加一个Fallback Unlit/Color遇到不支持复杂特性的设备时自动退化成纯色水面。虽然难看但至少不会黑屏。这是我拿真机跑出来的血泪经验模拟器上看不到这个问题。6. 噪波贴图驱动风格化让波光更自然的两种验证手法到了这一步水面基本功能已经能跑剩下的是让它更像“水”。用纯正弦波做的水面波峰和波谷的分布过于均匀一眼就能看出规律。真正的自然水面波光位置是随机的明暗变化是不规则的。我一般会引入一张 RGBA 噪波贴图把四个通道拆成不同的信息分别驱动 Shader 的不同效果通道用途建议数值R波光强度扰动高斯噪波明暗对比强G波长偏移低频噪波变化缓慢B泡沫遮罩只在岸边或漩涡区域有值A透明度扰动用于浅水边缘渐隐实现上核心思路不是用噪波直接做颜色而是把噪波值作为阈值超过阈值的区域才显示波光亮点类似真实水面碎光的分布// 波光阈值噪波值超过阈值的区域才显示亮斑 float waveGlint smoothstep(_GlintThreshold, _GlintThreshold 0.1, noise.r); col.rgb waveGlint * _GlintColor * _GlintStrength;_GlintThreshold是关键太低的话整面水都在闪太高的话几乎没有亮斑。我的习惯是取 0.55 到 0.75 之间在运行时用 Debug 面板滑动这个值看到水面出现自然分布的碎光点就停。验证还有一个笨办法把相机固定在水面上方 45 度角观察波光是否在 30 秒内没有明显的循环规律如果有规律地重复说明噪波的频率过低或滚动速度太一致两张噪声叠着滚动能破掉这个规律。从那以后我每次拿到水体资源第一件事不是调颜色而是先测两件事把相机切到正交模式看效果再打包到一台低端安卓机上看帧率。两关都过了才继续调参。这个习惯帮我避掉了大半的水面资源翻车现场希望帮到你。本文还有配套的精品资源点击获取
返回列表