1. 从“一杯水”说起:为什么你的液体看起来像果冻?
大家好,我是老张,在游戏特效和图形这块摸爬滚打十来年了。今天咱们不聊那些高大上的全局光照、光线追踪,就聊聊一个特别常见但又很容易做“假”的效果——液体倾倒。
你有没有遇到过这种情况:辛辛苦苦做了一个倒水的动画,模型动了,贴图也换了,但怎么看都觉得那液体不像水,更像是一块有弹性的果冻或者干脆就是个固体块在移动?问题就出在表面张力和动态形态的缺失上。真实的液体,尤其是水,在倾倒时,表面会因为张力而微微鼓起,形成一个圆润的弯月面,流动的边缘也不是生硬的直线。这个微妙的“鼓包”和“圆润感”,就是让液体看起来“活过来”的关键。
传统的做法可能是用动画序列帧,或者用多个模型来切换,但这样既不灵活,物理感也弱。今天我要分享的,就是如何用Unity的Shader,实时地、动态地去模拟这个效果。核心思路其实不复杂:我们不是去“画”出液体的形状,而是用数学和物理规律去“计算”并“裁剪”出它应该有的样子。就像用一把智能的“剪刀”,根据烧杯的倾斜角度,实时剪掉不该出现的部分,并让留下的部分呈现出自然的曲面。
这个方法的好处是性能开销小,一个Shader加几行脚本就能搞定,而且效果动态自然,非常适合移动端或需要大量液体表现的场景。下面,我就带你一步步拆解这个“智能剪刀”是如何工作的。
2. 核心原理:用“水平裁剪”模拟“永不溢出”
我们先来理解最核心的物理直觉。想象你手里拿着一个圆柱形的杯子,里面装了半杯水。无论你怎么倾斜、旋转杯子,只要没倒出去,杯子里的水面始终是保持水平的,对吧?而且,水面最高点永远不会超过杯口的最低点。这就是我们Shader算法的基石:在世界空间中,维持一个水平的裁剪平面,这个平面的高度由杯口决定。
2.1 世界空间是关键
为什么必须是世界空间?因为我们的杯子(容器)会在场景里移动、旋转。如果我们在模型自己的局部空间里计算裁剪,那么当杯子一倾斜,裁剪面也跟着倾斜,结果就是液体像冻在杯子里一样跟着倾斜,完全违背了物理规律。
所以,第一步,也是最重要的一步,就是把液体模型上每个顶点的位置,从它自身的模型坐标系,转换到整个场景共享的世界坐标系。在Shader里,这个转换通过一个叫做 UNITY_MATRIX_M 的矩阵来完成。这个矩阵蕴含了模型的移动、旋转、缩放信息,乘以它,我们就知道这个顶点在“世界”中的准确位置了。
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex); // 转换到裁剪空间,用于最终渲染
o.WorldPos = mul(UNITY_MATRIX_M, v.vertex); // **关键:转换到世界空间**
// ... 其他信息处理(如法线、UV)
return o;
}
拿到 WorldPos 之后,它的 y 分量就代表了该顶点在世界中的高度。接下来,我们只需要一个简单的判断:如果这个顶点的世界高度,超过了我们允许的“杯口水平面”高度,就把它“丢弃”掉。
2.2 动态的裁剪高度
杯口水平面的高度不是固定的。它由两部分组成:
- 烧杯底座的世界坐标Y值:这是基准。假设你的烧杯放在地上,这个值就



被折叠的 条评论
为什么被折叠?



