本文为技术分享,借助官方LayaAir-Mcp实现
受法国赌神启发,分享一个Laya中的扑克牌掀角效果。

效果:拖拽牌的任意一角,牌面像真纸一样卷起来,露出正面,松手 Q 弹回去。

整个效果就两部分:一个 Shader 负责画面,一个脚本负责交互。
原理
把卷曲想象成一根圆柱压在纸上滚过去,截面就是这样:

Shader 对每个像素算一个值 x(它到折叠线的距离),然后分三种情况处理:
- x < 0:已经翻过去了 → 画牌面
- 0 < x < R:正在圆柱上弯着 → 外面画牌面,里面画牌背
- x > R:没动 → 画牌背原样
其中 R 是卷曲半径,拉得越远 R 越大,弯曲越平缓。
对于弯曲区的像素,用反三角函数反推它"展平后"在原图上的位置,再去采样纹理,就得到了卷曲的视觉效果。
光影处理
圆柱外面(亮)和里面(暗)在脊线处必须收敛到同一个亮度,否则会有一条明显的割裂线。
float edgeFade = smoothstep(0.0, 0.4, nz);
// 外面:脊线 0.8,远处 1.0
// 里面:脊线 0.8,远处 0.9
// 脊线处两层一样亮,过渡自然
底座的接触阴影用 smoothstep 柔和铺开,范围要够宽(0.3),暗度要克制(最暗 0.88),不然看起来像一条黑线。
防裁剪
牌翻出精灵范围会被 GPU 切掉。解决办法:在顶点着色器里偷偷把四个顶点往外推,等于加了一圈透明安全区。

没拖的时候安全区画透明看不出来,拖的时候卷出去的部分就有地方画了。
交互
脚本干三件事:
1. 按下 → 找最近的角
把鼠标位置换算成 UV(01),看离哪个角最近就抓哪个。
2. 拖动 → 每帧 Lerp 追踪
不直接把手指位置传给 Shader,而是用插值慢慢追。拖拽时追得快(跟手),松手后追得慢(果冻感):
let speed = isDragging ? 25.0 : 15.0;
currentPos.x += (targetPos.x - currentPos.x) * speed * dt;
3. 松手 → 把目标设回原角
targetPos = currentCorner; // 就这一行,回弹自动完成
材质质感调节
同一套 Shader,调一个参数就能模拟不同材质的手感。核心就是卷曲半径 R 的计算方式:
float rawR = D / CP_PI; // D 是拖拽距离
float R = mix(rawR, u_Radius, smoothstep(0.0, u_Radius * CP_PI * 0.8, D));
这行代码做了一件事:手指刚离开角落时 R 自然跟随(保证跟手),拖远后 R 快速撑到 u_Radius 的最大值(体现材质硬度)。smoothstep 的范围控制"多快撑满"——越小越快越硬。
只改 u_Radius 就能切换材质:
| 0.05 | 弯曲紧凑,折痕锐利 | 薄纸、餐巾纸 |
| 0.15 | 弧度适中 | 普通纸张、A4 纸 |
| 0.30 | 弧度较大,大部分面积平整 | 扑克牌、卡纸 |
| 0.45 | 弯曲区域很窄,几乎只有折角处有弧 | 厚卡纸、硬纸板 |
需要注意:如果用固定的 R = min(u_Radius, D/π) 公式,拖得近时 R 会跟着变小,哪怕设了很大的 u_Radius 也会出现紧折,看起来像布。解决方法就是上面的 mix + smoothstep,让 R 在小距离时也能快速撑到大弧度。
好,牌没有问题,本文就到这里,欢迎大家讨论补充!
示例项目如下: