【特效分享】 3D残影 特效!惨不忍睹的成果 + 耗时大半天的折腾过程……

首先再次感谢 @天煞魔猎手 大佬!

上次 3D拖尾 特效的帖子里看到有人回复说可以试试这个效果:

考虑到某些不方便说的原因(我不会做),我对此表示:

因为我想要一个残影效果,今天就开始琢磨了。
先来个惨不忍睹的效果:

接下来开始一天的回顾。
不喜欢八卦的朋友可以跳过这一段,直接下载源码就完事了。

首先,百度残影,基本上实现方式都是复制一个节点,改变它的透明度。好简单!
但我不想复制节点,因为不确定节点上会挂载一些什么样的脚本,会有些什么不可预测的事情发生。
真正需要的,是节点的坐标角度缩放,网格的顶点数据、三角面数据、材质的漫反射贴图,因为残影不再需要光照、阴影等效果。

所以,怎样拿到网格的顶点数据呢?

直接查看 creator.d.ts 中的 cc.Mesh,结论是:没有方法。
所以,得从源码中去找。
先找到 cc.Mesh 的源码 CCMesh.js 脚本,怎么找到的下次有机会再说。

看,这是什么?


我认识中文,我知道copyAttribute这个方法的作用是读取子网格的指定属性到目标缓冲区中。
然而我并不知道后面两个参数是做什么用的。
又经过了一番折腾,得到以下结论:
1、网格的顶点数据,包含了顶点的空间坐标、法线方向、UV坐标、颜色等信息,这是以前就知道的。

2、但是,所有这些数据,都是存储在一个数组中的,
而且是,将第一个顶点的坐标的X、Y、Z值、法线的X、Y、Z值、UV的X、Y值、颜色的r、g、b、a值等这些数字,连续地存起来,
再接着是第二个顶点的坐标、法线等,
并不会像我们想象的那样,一个数组存位置坐标,一个数组存UV坐标。

3、那么,我想要知道第 N 个顶点的位置坐标,怎么找呢?
4、这就是顶点数据格式的作用了。

顶点数据格式是什么呢?
用来描述一份顶点数据包含了顶点的哪些信息,比如,它包含了顶点的位置,UV,颜色等属性,而每一种属性,又需要有一个详细的信息,下面的截图中的对象就描述了顶点的位置属性的信息,我们且称它为一个attribute:


大概有人会对这个 a_position 眼熟~

同样的,其他UV属性,颜色属性等,都会用一个这样的 attribute 来描述,然后这些数据全部存在 gfx.VertexFormat 中。
这个东西到底有什么用呢?
现在我想得到第 5 个顶点的坐标,假设我已经拿到了网格的顶点数据 vData(简单理解为一个 number[ ] ),那么:
描述一个顶点的全部属性需要多少个数字?

但上面的截图中已经标出, stride 可以简单理解为顶点数据的长度。
其实 VertexFormat 这个东西中就记录了这个长度,我突然想到我还可以这样截图:
图中的 _attr2el、_elements,是分别用对象和数组的方式保存了所有attribute。

就是这个 _bytes,表示一个顶点数据占用的总字节数。(补充一下,vData就是一个 Uint8Array,即数组的每个元素都是用一个字节表示的无符号的8位整型数,所以数组占了多少个字节,就相当于多长的长度)。

第 5 个顶点的顶点数据的第一个数字,在 vData 中的索引,就是 4 * _bytes;
假设 _bytes 的值为32,则从第128个数开始,接下来的连续 32 个数,都是第5个顶点的数据,那么哪几个数是表示它的位置坐标的呢?
上面那个 name : “a_position” 的属性中,offset 的值就是用来记录这个信息的。

所以,从128开始,向后再移动 0 个位置,就是开始表示 a_position 属性的数据。

attribute 的 num,表示用几个数字来描述这个属性,很显然,3D空间中的坐标有 3 个数值。
那么从 128 + 0 开始,往后的连续 3 个数字就是表示位置坐标了吗?

在我们的代码中,节点的坐标一般是浮点数,一个 float32 要占用4个字节,而 vData 中一个元素是一个uint8,所以一个浮点数对应在 vData 中是 4 个元素。
所以,用来描述 a_position 这个属性的数据,在 vData中对应是 3 * 4 = 12 个元素,这就是 上面截图中的 bytes 的含义。

现在知道了,从 128 + 0 的位置开始,往后的连续 12 个数字都是表示位置坐标的。

如果顶点数据中只保存了顶点位置、UV坐标,顶点位置的 offset 为0,则接下来的数据就是表示 UV 坐标的,UV数据的 offset 就是顶点数据的字节长度 12 。

逐渐忘记主题……

主题就是,copyAttribute (primitiveIndex, attributeName, buffer, stride, offset) 这个方法怎么用?

现在应该明了了,stride 就是一份顶点数据的长度,至于这个 offset 嘛,和上面出现的 offset 是两回事了,我们使用的时候,直接传个 0 就行了。我都懒得解释。(记住了,遇到不懂的,都可以用这句话来回答。)

通过对源码的深入研究,冥思苦想,得出这个方法的作用:
根据 attributeName,从顶点数据格式中找到该属性名的数据格式,
根据该数据格式中记录的偏移、长度等信息,从 vData 中读取对应索引的数值,再保存到 buffer 中。
每个顶点的描述 attributeName 属性的数据,都这样读取、保存一下。这样就将网格的所有顶点的指定属性都偷出来了。
忘了说了,buffer,是一个ArrayBuffer,百度上有,我都懒得解释。

好了,我们已经知道了怎样获取网格属性,现在就可以做出很高大上的残影特效来了,是不是很简单……

1、新建一个网格:let mesh = new cc.Mesh();
2、给这个网格初始化:
网格初始化需要至少两个参数,顶点数据格式,和顶点数量。
顶点数量?

我怎么知道目标节点的网格有多少个顶点呢?

不用慌,网格的顶点数据 vData 的长度 byteLength / 一份顶点数据占用的字节数,就是顶点个数了。
好了,继续初始化新建的 mesh。
顶点数据格式,我们只要顶点的坐标和UV就行,初始化 mesh 的代码,我都懒得写……
但是,我们是要从另一个网格中偷数据出来的,用来描述 坐标 和 UV 的信息也得跟他一样才行。


那我就先把你的数据格式偷出来。

假设我们已经给 mesh 初始化完毕。
开始偷顶点数据。
前面折腾了这么久的东西,终于要发挥作用了。

ele 就是一个 attribute 数组,这里就是通过 for 循环,将我们需要的每一种属性都偷出来,前面的折腾,都是为了这里能正确使用 mesh.copyAttribute 这个方法。
很显然,我目前的做法是,每偷到一种属性的数据,就拿回来存好,再接着回去偷。
那么为什么不一次性把需要的数据全部偷了,再拿回来保存呢?
这是因为,

顶点数据已经拿到,三角面就简单多了,copyIndices (primitiveIndex, outputArray),只有两个参数,我都懒得解释……

终于完成了我们的网格数据复制大业,接下来就是新建一个节点,将它添加为目标节点的兄弟节点,这样它的坐标、角度、缩放,全部和目标节点一样就行了,如果有特殊情况,也可以将它添加到其他节点下,坐标什么的注意转换一下就行。
给新建的节点添加MeshRenderer组件,赋值mesh,以及材质……问题又来了:我们需要怎样的材质,材质又需要设置哪些属性?

作为残影,它需要使用和目标一样的漫反射贴图,需要颜色比目标淡一点,需要透明度能逐渐变到 0,不需要接受光照。
新建一个effect,frag中函数如下:


这……是不是太简单了?
我不管,反正我就这么写。

设置它的漫反射贴图,很显然,最理想的做法是,直接从目标节点使用的材质中获取。
但目标节点使用的材质中,不一定会有 “diffuseTexture"这个属性……
无奈之下,我们只能由外部传入这个贴图。

你想要我生成残影?你不但要告诉我目标节点是谁,你还得告诉我它使用的是哪个贴图,还得告诉我这个残影需要多长时间消失!

好了,复制大业这次是真的完成了。
返回的数据中,包含了残影节点和残影的材质,可以根据实际使用情况,交给合适的脚本进行管理。
当然目前的 create 函数里,对材质的透明度做了处理,但对节点是回收还是销毁,我就懒得管了。

总结就是,原理很简单,过程很折腾。因为cocos还没有把复制数据的方法放出来,找源码看呢又没有中文注释,不懂英文的我只得逐行理解源码,才知道用。
这中间我还尝试不使用 copyAttribute 这个方法,直接自己从 vData 中一次性读取需要的数据,因为上面截图中的方法有些繁琐,但结果是,直接读取并赋值给 mesh 后,某些数据它没有,于是我又自己创建这些数据并给它赋值,结果这些数据又缺失了某些东西……如此下去,我发现我得把很多源码都复制过来才行了。
机智的我,立刻放弃了这个想法。

按理说,这个类应该是设置成静态类的,想生成一个残影时,只需调用一下create就行了,但出于某些原因(你们懂的),我还是继承了 cc.Component,并挂载在节点上。我知道一定有更优雅的使用方式,但是我不会。

上源码。(是整个项目,可以直接打开 test 场景,预览效果。使用的cocos creator 2.3.2 版本,2.3以上的应该都可以打开。上次的3D拖尾也在里面,顺便修改了它的两个BUG。)

3D特效(3月28日).rar (193.2 KB)

雁过留影,侠过留名。大爷常来玩啊……

11赞

看了半天 小伙子你还是没有实现第一个图的效果啊

1赞

那是拖尾,不是残影,想骗我做那个?我就不!就不!

很高端mark 一下

Mark

跑起来没看到残影啊

你用的什么版本,有没有打开test场景,除了没有残影,能不能看到其他内容

mark

所以拖尾怎么做呢:11:

这个是讲残影效果的,拖尾效果在这里:在此输入链接描述

在此输入链接描述

阔以的小老弟。。

必须mark

这就完了?我会说我是冲着第一张图来的?不可能,绝对不可能!

不明觉厉,mark一下