用 Shader 实现旗帜飘扬动画效果

COCOS

共 8864字,需浏览 18分钟

 · 2024-04-26

觉得对于刚入门 3D 编程的朋友来说,如果能够完成代码创建模型数据->创建材质->编写Shader动画这一系列,想必会有满满的成就感。

今天就用 Cocos Creator 的 utils.MeshUtils.createMesh 接口,带大家感受一下这个流程。

这个流程不仅可以用于新手学习,也是一些高端特效的必备技巧。

准备:基础

  1. 你需要一个Cocos Creator,我这里用的是3.8.2版本

  2. 了解下创建网格的接口:查看官方文档

utils.MeshUtils.createMesh

这个接口是用来创建网格的,你可以通过这个接口创建一个网格,绑定到节点的 MeshRendermesh 属性上,就可以看到你的模型了。

起点:显示一个三角形

三角形是 3D 世界的基本单位,我们在屏幕上看到的三维模型,也是通过三角形构成的。三角形数量越多,能够表示的模型细节就越多。

我们先从一个三角形开始,然后再慢慢的扩展到更多的三角形,最后就是一个完整的模型了。


private createTriangle() {

    this._meshCreator.reset();

    let p0 = vec3Pool.alloc().set(000);

    let p1 = vec3Pool.alloc().set(100);

    let p3 = vec3Pool.alloc().set(0-10);

    this._meshCreator.verticles.push(p0 , p1, p3);

    let uv0 = vec2Pool.alloc().set(00);

    let uv1 = vec2Pool.alloc().set(10);

    let uv2 = vec2Pool.alloc().set(01);

    this._meshCreator.uvs.push(uv0, uv1, uv2);

    this._meshCreator.indices.push(021);

    this._meshRenderer.mesh = this._meshCreator.buildMesh();

}

代码看起来是不是很简单?

我们只需要创建三个顶点,然后创建三个uv,最后创建一个三角形,然后就可以看到一个三角形了。如果你不用显示贴图的话,甚至于可以不用计算uv,乱写一个就行了。其实创建网格还有很多参数,这里我们都可以先忽略它们!

进阶:显示一个正方形

正方形是两个三角形组成的,我们只需要创建两个三角形,然后组合在一起,就可以显示一个正方形了。


private createQuad() {

    this._meshCreator.reset();

    let p0 = vec3Pool.alloc().set(000);

    let p1 = vec3Pool.alloc().set(100);

    let p2 = vec3Pool.alloc().set(1-10);

    let p3 = vec3Pool.alloc().set(0-10);

    this._meshCreator.verticles.push(p0, p1, p2, p3);

    let uv0 = vec2Pool.alloc().set(00);

    let uv1 = vec2Pool.alloc().set(10);

    let uv2 = vec2Pool.alloc().set(11);

    let uv3 = vec2Pool.alloc().set(01);

    this._meshCreator.uvs.push(uv0, uv1, uv2, uv3);

   

    this._meshCreator.indices.push(021032);    

    this._meshRenderer.mesh = this._meshCreator.buildMesh();

}

与三角形类似,我们只需要创建四个点,然后创建四个uv,最后创建两个三角形,然后就可以看到一个正方形了。

聪明的你可能已经发现了,正方形虽然是两个三角形组成的,但是我们只需要创建一个正方形的四个点,然后通过索引来组合两个三角形,这样就可以减少重复的点了。

看代码还是太累,这里用图片来表示一下顶点:

终极:显示一个飘动的旗帜

旗子其实就是一个长方形,然后通过Shader来让它动起来,唯一不同的是,我们需要给它多个顶点,然后通过Shader控制各个顶点,让它动起来,从而看起来像是飘动的旗帜。


private buildMesh() {

    this._dirty = DirtyType.None;        

    this._meshCreator.reset();

    const width = this._size.x;

    const height = this._size.y;

    const verticePreUnit = this._verticePreUnit;

    const widthSegments = Math.floor(width * verticePreUnit);

    const heightSegments = Math.floor(height * verticePreUnit);

    for(let j = 0; j <= heightSegments; j++) {

        const y = - j / heightSegments * height;

        for(let i = 0; i <= widthSegments; i++) {

            const x = i / widthSegments * width;

            const v = vec3Pool.alloc();

            v.set(x, y, 0);

            this._meshCreator.verticles.push(v);

           

            const uv = vec2Pool.alloc();

            uv.set(i / widthSegments, j / heightSegments);

            this._meshCreator.uvs.push(uv);

        }

    }

    for(let j = 0; j < heightSegments; j++) {

        for(let i = 0; i < widthSegments; i++) {

            const a = i + (widthSegments + 1) * j;

            const b = i + (widthSegments + 1) * (j + 1);

            const c = (i + 1) + (widthSegments + 1) * (j + 1);

            const d = (i + 1) + (widthSegments + 1) * j;

            this._meshCreator.indices.push(a, b, c);

            this._meshCreator.indices.push(a, c, d);

        }

    }

    this._meshRenderer.mesh = this._meshCreator.buildMesh();

}

网格的创建没什么再说的了,但是这里要注意需要精确计算uv坐标,保证纹理显示正确;

而且在shader中需用通过uv坐标来控制顶点的位置,从而让旗帜动起来,简单的来说就是通过uv的横坐标控制顶点偏移的强度,毕竟靠近旗杆的地方是不会动的,而靠近旗尾的地方是会动的。

我们通过shader显示一下旗帜UV水平方向上的变化,可以看到离旗杆越远,旗帜就会动的越强烈:


vec4 SurfacesFragmentModifyBaseColorAndTransparency()

{

return vec4(ALBEDO_UV.x, 0.0, 0.0, 1.0);

}

至此,给网格添加材质普通的材质球后,我们就有了一个静态的旗帜,接下来就是通过Shader让它动起来了。

进化:让旗帜动起来

让旗子动起来很简单,只需要在Cocos Surface Shader的函数SurfacesVertexModifyWorldPos中修改顶点的位置即可,最简单的方式就是给一个sin,让他随时间的变化平移顶点的位置,这样就可以让旗帜动起来了。


vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)

{

In.worldPos += a_normal * sin(cc_time.w + In.worldPos.x);

return In.worldPos;

}

效果如下:

好吧,一言难尽,确实是动起来了,但是也飞起来了,旗杆完全没达到约束他的效果,我们用上文提到的uv来控制顶点的偏移,再试试效果:


vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)

{

In.worldPos += a_normal * sin(cc_time.w + In.worldPos.x) * a_texCoord.x;

return In.worldPos;

}

效果如下:

这次的效果明显好多了,但是还是有点问题,旗帜的波动不够自然,我们可以通过增加一张噪声贴图来让它更自然一些,这里我用了一个简单的噪声贴图,你可以用更复杂的噪声贴图来让它更自然一些,当然这里需要再顶点着色器中做纹理采样,可能有些低端手机或者平台不支持,所以需要注意一下。

当然,你也可以通过更复杂的算法来模拟噪声:


vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)

{

float noise = texture2D(a_noiseMap, a_texCoord).r;

In.worldPos += a_normal * sin(cc_time.w + In.worldPos.x) * a_texCoord.x * noise;

return In.worldPos;

}

效果如下:

有了噪声贴图,旗帜的波动就更自然了, 但是有些小问题,我再次修改下代码,形成最终的效果:


vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)

{

float t = sin(cc_time.w * speed + In.worldPos.x);

vec3 oldPos = In.worldPos;

In.worldPos += a_normal * t * height * a_texCoord.x;

vec2 uv = a_texCoord + vec2(t * 0.3, 0.0);

vec4 noise = texture(noiseMap, uv);

In.worldPos -= noise.r * height * a_texCoord.x;

return In.worldPos;

}

最后,把图片换成 Cocos 的标准模板 LOGO。


得到的效果如下:


总结

看到没有,从头到尾,我们只关注两件事:

1. 创建网格

网格我已经通过工具类封装好了,你只需要调用接口,传入参数,就可以创建一个网格了。

2. 编写Shader

而Shader我们从头到尾值关注了一个函数SurfacesVertexModifyWorldPos,这个函数是用来修改顶点的位置的,我们只需要在这个函数中修改顶点的位置,就可以让模型动起来了。

所以,3D 绘制其实是很简单的,只要你掌握了这两个点,你就可以做出很多很多的东西了,这里只是一个简单的例子,你可以通过这个例子,去尝试更多的东西,比如更复杂的模型,更复杂的Shader,更复杂的动画,这些都是你可以尝试的,希朥这个教程能够帮助到你,也希望你能够享受这个过程。

不知道你有没有发现,从头绘制一个3D模型,并且给予它生命一般的动画,这个过程是多么的有趣,这个过程就像是一个创造者,你可以创造出你想要的一切,这种感觉是多么的美妙,希望你也能够感受到这种美妙。

项目文件

点击【阅读原文】,可在文章末尾获得项目文件,直接解压放到你的工程资源文件夹下即可。

浏览 267
2点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
2点赞
评论
收藏
分享

手机扫一扫分享

举报