Cocos Shader丨通过 UV 坐标实现「点击产生水波纹」效果-天天新视野

在开发中,我们可以通过 UV 坐标实现多种 Shader 效果。本次,Cocos 布道师孙二喵将分享如何用 UV 坐标实现水波纹。Demo 见文末。

什么是 UV 坐标


【资料图】

UV 坐标,也称为纹理坐标,是 3D 建模和游戏开发中的一个重要概念。

在 3D 建模中,我们常通过纹理映射,将一张纹理(也就是图片)黏在模型表面,以控制模型外观。模型的每个顶点都有一个 UV 坐标,定义了该顶点在纹理中对应的 2D 坐标。通过 UV 坐标,我们就可以将 2D 图像上的每一个点精确映射到 3D 模型表面,实现高精度的纹理和贴图效果。

命名为 UV 的原因是 XYZ 已经被用于表示三维空间中对象的坐标轴,为了区分开,所以用了 UV 命名。其中 U 是水平方向坐标轴,V 是垂直方向坐标轴,U 和 V 的范围都是 0 到 1。

Cocos 中的 UV 坐标

在 Cocos 中,UV 坐标系的原点默认在左上角,纹理和图片像素的垂直轴、以及在着色器中对纹理进行采样时,都是下指向的,即从上到下。

这与大多数图像文件格式存储像素数据的方式一致,也与大多数图形 API 的工作方式一致,包括 DirectX、Vulkan、Metal、WebGPU,但 OpenGL 除外。如果你以前的开发经验是基于 OpenGL,在用同样的方式开发 Cocos 游戏的 Shader 时,会发现你的网格上的纹理是垂直翻转的。此时务必要以左上角为 UV 的原点,做一下调整。

UV 坐标系也与 Cocos 中其他地方使用的世界坐标系(Y 轴指向上方,如下图)不一致,通过世界坐标计算 UV 位置的时候也需要注意这个问题。

Cocos 中的世界坐标系

在 Shader 内使用 UV

在 Cocos 中,2D 精灵的 Shader 和 3D Mesh 的 UV 都是在顶点着色器(VS)中获得,并传入像素着色器中(FS)。

在 Cocos 中,3D 的 Shader 默认会乘以平铺 Tilling 系数并加上 Offset 偏移系数,同时支持 RenderTexture 的翻转修复。

我们先来看一个 UV 应用的小效果:

vec4frag(){vec4col=mainColor*texture(mainTexture,v_uv);vec2uv=v_uv;if(uv.y>0.5){col.rgb*=0.5;}CC_APPLY_FOG(col,v_position);returnCCFragOutput(col);}

当 UV 的 V 大于 0.5 时候,把颜色的 RGB 都乘 0.5,使颜色变暗。动态改变这个数值,就可以实现简单的动画效果:

UV 在 Shader 中的更多应用

使用 UV 坐标可以实现多种 Shader 效果,如帧动画、水波纹、烟雾、火焰等。

帧动画

帧动画的实现,可以参考麒麟子的文章:

https://forum.cocos.org/t/topic/145096

水波纹效果

本次重点分享如何通过 UV 在 2D 精灵和 3D 面片上实现“点击后出现水波纹”的效果。

实现思路大同小异,通过 Sin 函数波动模拟水波,再在 Update 函数内增加波动的范围。

vec2waveOffset(invec2uv0){floatwaveWidth=0.25;vec2uv=uv0;#ifUSE_WAVEvec2clickPos=vec2(waveFactor.x-uv0.x,waveFactor.y-uv0.y);floatdis=sqrt(clickPos.x*clickPos.x+clickPos.y*clickPos.y);floatdiscardFactor=clamp(waveWidth-abs(waveFactor.w-dis),0.0,1.0)/waveWidth;floatsinFactor=sin(dis*100.0+waveFactor.z*10.0)*0.01;vec2offset=normalize(clickPos)*sinFactor*discardFactor;uv+=offset;#endifreturnuv;}

这里简单做了一些优化,UV 在外部提前计算好(为了方便动画效果),使用 1 个 Vec4 进行 Shader 参数设置,减少开销。

2D 精灵上实现水波纹效果

效果演示

@ccclass("spTouchExample")exportclassspTouchExampleextendsComponent{@propertywaveDis=0.4;@propertywaveSpeed=1;@propertywaveStr=0.5;@property(Node)debug:Node=null;@property(Label)debugText:Label=null;publicwaveProp:Vec4=newVec4();private_trans:UITransform;private_pass:renderer.Pass;private_handle:number;start(){this._trans=this.node.getComponent(UITransform);this._pass=this.node.getComponent(Sprite).material.passes[0];this.waveProp.w=100;this.waveProp.z=this.waveStr;this._handle=this._pass.getHandle("waveFactor");}onEnable(){this.node.on(Node.EventType.TOUCH_START,this.onTouchStart,this);}onDisable(){this.node.off(Node.EventType.TOUCH_START,this.onTouchStart,this);}onTouchStart(event:EventTouch){consttouch=event.touch;touch.getUILocation(v2_0);v3_0.set(v2_0.x,v2_0.y);this._trans.convertToNodeSpaceAR(v3_0,v3_0);this.debug.setPosition(v3_0);constsize=this._trans.contentSize;constx=size.x;consty=size.y;v4_0.x=(x*0.5+v3_0.x)/x;v4_0.y=1-(y*0.5+v3_0.y)/y;v4_0.w=0;this.waveProp.set(v4_0);this.debugText.string="Clicked:"+this.node.name+"UV:"+v4_0.x.toFixed(2)+","+v4_0.y.toFixed(2);}update(dt){if(this.waveProp.w<100){this.waveProp.w+=dt*this.waveSpeed;if(this.waveProp.w>this.waveDis){this.waveProp.w=100;}this._pass.setUniform(this._handle,this.waveProp);}}}

这里在图片节点上添加了点击的监听,把点击的 UI 世界坐标转换成了图片的本地坐标,再用本地坐标和图片的长宽算出 UV 的位置,在 update 里设置材质的 uniform 参数。

3D 面片上实现水波纹效果

效果演示

onTouchStart(event:EventTouch){consttouch=event.touch!;this.cameraCom.screenPointToRay(touch.getLocationX(),touch.getLocationY(),this._ray);this.rayHit();}/*checkmodelhit*/rayHit(){letdistance=300;letmesh:MeshRendererfor(letvofthis.meshes){letdis=geometry.intersect.rayModel(this._ray,v.model);if(dis&&dis<distance){distance=dis;mesh=v;}}if(mesh){this._ray.computeHit(v3_0,distance);constnode=mesh.node;m4_0.set(node.worldMatrix);consthalfSize=mesh.model.modelBounds.halfExtents;constscale=halfSize.x*0.1*node.scale.x;this.debug.setWorldPosition(v3_0);this.debug.setScale(scale,scale,scale);m4_0.invert();Vec3.transformMat4(v3_0,v3_0,m4_0)if(halfSize.y==0){constx=halfSize.x;constz=halfSize.z;v4_0.x=(x+v3_0.x)/(x*2);v4_0.y=(z+v3_0.z)/(z*2);}else{constx=halfSize.x;consty=halfSize.y;v4_0.x=(x+v3_0.x)/(x*2);v4_0.y=(y-v3_0.y)/(y*2);}v4_0.w=0.1;constmeshCtrl=node.getComponent(meshTouchCtrl);meshCtrl.waveProp.set(v4_0);this.debugText.string="Clicked:"+node.name+"UV:"+v4_0.x.toFixed(2)+","+v4_0.y.toFixed(2);}}

注:这里只针对 UV Mapping 时候平铺的 3D 面片。

这里通过屏幕射线检测点击的位置。

在计算 UV 的时候会有所不同。通过点击位置的世界坐标乘以面片节点的世界矩阵的逆矩阵,把点击位置的世界坐标转换为在面片节点的下的本地坐标。这里还需要点击的物体判断是面片 Quad 还是地面 Plane(Cocos 内置的基础几何体,通过半包围确认),算出点击位置所在的 UV 坐标。

资源链接

Demo 源码下载:

https://forum.cocos.org/uploads/short-url/9FaEj4MDNfBPu1b2rEEcqUBWX09.zip

论坛讨论帖:

https://forum.cocos.org/t/topic/145096

需要 Demo 的小伙伴,请将上方的源码链接复制到浏览器,即可自动下载。欢迎前往论坛专贴,一起交流探讨!

未来,我也将定期分享 Cocos Shader 的实际应用,以及相关 Demo 源码,带大家快速上手实现更多漂亮的 Shader 效果!

往期精彩

关键词: 世界坐标 动画效果 世界坐标系

推荐DIY文章
主机存在磨损或划痕风险 PICO4便携包宣布召回
穿越湖海!特斯拉Cybertruck电动皮卡可以当“船”用
vivoXFold+折叠旗舰开售 配备蔡司全焦段旗舰四摄
飞凡R7正式上市 全系标配换电架构
中兴Axon30S开售 拥有黑色蓝色两款配色
荣耀MagicBookV14 2022正式开售 搭载TOF传感器
it