前言
- 论坛各路大神都在秀,小弟也分享点技术吧(由于同学们的强烈建议,写了一些详细教程,学会了也可以用来魔改其它组件)。
引擎自带的graphics绘制组件挺好用,不过有时候实现特殊需求的时候难免需要自己想办法,今天我就分享一下自己继承graphics后魔改的一些简单功能,这个魔改2D的可以用带上纹理画出各种路径啊之类的效果,3D的可以用程序高自由度绘画出各种路径、图形之类的效果。(附带代码示例所用版本为3.4.1,写的不好请轻喷)
一、2D带纹理
-
这引擎源码虽然简洁整齐可看着云里雾里的有点头晕啊好复杂,咋办呢?但是想到就要努力一下子,万一成了呢,于是就顺着文件夹分门别类的看,这一看,还真是不感觉太难(因为以前看过一个超高耦合度项目代码简直地狱级阅读难度,后来再复杂的代码逻辑都感觉阅读不太吃力了)。
-
通过阅读引擎源码,知道了graphics的绘制原理,在graphics.ts文件中是实现的绘制组件,通过各种接口收集绘制信息,在graphics-assembler.ts文件中是实现的顶点渲染数据组装器,在impl.ts文件中实现的绘制路径点的存储和加工。
-
想要魔改一下,想到了继承graphics组件,重载里面的方法。因为graphics组件的_flushAssembler方法是获得顶点渲染数据组装器的地方,所以可以在这个方法里实现对顶点数据渲染组装器的重写,想要给它加纹理,在shader里需要线的长度这个数据,这样线长和线宽两个数据就可以组成uv坐标来取纹理的像素点啦。可以在onLoad方法里实现对路径点存储加工器impl.ts替换为自己实现的路径点存储加工器。
思路有了,开工! -
首先,继承graphics组件,然后,对照着源码重载_flushAssembler方法,考虑到3.X版本的assembler方法是一个对象不是类不能继承,干脆一不做二不休新建一个对象很羞耻的命名为superGraphicsAssembler(这名字看着牛批),将原组装器的方法都赋值给新组装器。
-
因为我们的目的是给组件的顶点数据加一个线长数据,所以我们要在组装器的路径数据整理功能的_flattenPaths方法里搞事情,先把它重写了(其实就是将这个方法源码复制过来改改)。至于会报错的地方,该导入的导入,导入不了的就用比如__private._cocos_2d_assembler_graphics_webgl_impl__Impl这种方式声明它的类型,如果还不行的,就any类型。如果有需要new出对象的类型又无法从引擎导入,就重新写这个类,比如const dPos = new Point(p1.x, p1.y);这一行,就可以将引擎的Point类复制过来改个名就叫Point2好吧,顺便在这个点类里面加上自己的料lineLength线长。然后将从初始点到每个点的线长计算出来赋值给路径点数据里,用pts[0][“lineLength”] = lineLength;这种方式,到了组装顶点数据的时候用相同方法取到就行了。
-
到这里我们的路径点都带上了线长这个数据,但是光路径点也没用啊,需要将这个数据加到顶点数据里传到shader中去用。所以我们瞄上了组装连线顶点渲染数据_expandStroke方法里,将它再复制过来改改,将调用设置顶点数据的_vSet方法的地方都多传一个参数lineLength,对,就是从路径点对象里取出的线长。
-
但是我们一看,这个_vSet方法里设置数据是通过设置buffer数组里的对应下标的元素值来达成的,哦,所以接下来就是将顶点数据格式修改一下下,让这个增加新成员后的buffer存储的数据能被渲染管道下游的shader读懂。找一找,它的顶点数据格式是在graphics.ts文件里定义的const attributes = vfmtPosColor.concat([
new Attribute(‘a_dist’, Format.R32F),
]);在vfmtPosColor上跳转进去一看,原来是
export const vfmtPosColor = [
new Attribute(AttributeName.ATTR_POSITION, Format.RGB32F),
new Attribute(AttributeName.ATTR_COLOR, Format.RGBA32F),
]; -
在buffer数组里每new一条都是多加一个数据,a_position 属性格式为32位float格式的三个数组元素为一个数据,a_color 属性格式为32位float格式的四个数组元素为一个数据,在graphics文件里新加的a_dist 格式为32位float格式的一个数组元素为一个数据,这时候聪明的同学已经发现了规律,诶~我不说,你猜猜(大佬除外,坐下)。我们复制过来给它多加一条数据
const attributes2 = UIVertexFormat.vfmtPosColor.concat([
new gfx.Attribute(‘a_dist’, gfx.Format.R32F),
new gfx.Attribute(‘a_line’,gfx.Format.R32F),
]); -
对,就是线长,一个32位float元素就够用了,再多浪费。然后我们将源码中用到attributes的代码都赋值过来改为自己定义的attributes2 并且将用到这俩货的代码也这样做
const componentPerVertex = getComponentPerVertex(attributes);
const stride = getAttributeStride(attributes);
至于这俩货是个啥,在源码中跳进去生成函数看看就知道是单个顶点数据的总占用元素个数和总字节长度。 -
现在我们回到_vSet函数里,这里这时我们发现修改了顶点数据格式后,我们有空位可以放线长数据进buffer里了,于是在vData[dataOffset++] = distance;下面再加一行vData[dataOffset++] = lineLong;,嗯,完美,不对等会儿,_vSet函数改了后所有用到_vSet函数的地方都要改一下以加上线长数据,所以我们将源码中的所有用到_vSet函数的方法都复制过来加上线长参数,这回是真完美了!
-
现在总可以试试效果了吧,不,别着急,只改了渲染管道的上游让管子更粗,下游的管子还没兼容要爆管呢,本着尽职尽责的原则将下游的shader管子也复制graphics的默认shader新建一个(材质和Effect)随意命名为pathLine,在shader的顶点函数里效仿
in float a_dist;
out float v_dist;
也写一个
in float a_line;
out float v_line; -
这个a_line就是shader管道承接上游渲染数据组装器里的那个a_line线长数据(就像水管一样接过来),out的意思是让它流入下个水管(片元着色函数),当然这两个水管中间也有两截水管承接(顶点数据连三角、光栅化将每个三角切割成无数像素格子)这中间两截水管不用理会只要知道它俩的作用就行。然后就在片元着色水管里将线宽和线长组成uv坐标来取纹理的像素
vec2 uv0 = vec2(v_line,(v_dist + 1.)/2.);
uv0.x = fract(uv0.x);
uv0.y = fract(uv0.y);
o *= CCSampleWithAlphaSeparated(texture1,uv0);
这纹理哪来的,现在就加上
properties:
texture1: { value: white } -
在片元着色水管里加uniform sampler2D texture1;,然后在自己定义的SuperGraphics里加上设置材质和纹理的地方,
@ccclass(‘SuperGraphics’)
export class SuperGraphics extends Graphics {
@property(Texture2D)
lineTexture:Texture2D = null;
@property(Material)
myMat:Material = null;onLoad(){
if (this.lineTexture){
this.lineWidth = this.lineTexture.height;
lineC = this.lineWidth/ (this.lineTexture.height * 2 * this.lineTexture.width);
}
if (this.myMat){
this.setMaterial(this.myMat,0);
if (this.lineTexture)
this.getMaterial(0).setProperty(“texture1”,this.lineTexture);
}super.onLoad();
}
onEnable(){
if (this.myMat){
this.setMaterial(this.myMat,0);
if (this.lineTexture)
this.getMaterial(0).setProperty(“texture1”,this.lineTexture);
}
} -
然后,就是试验试验我们的辛苦成果了!
- 最终效果:
当前代码如果绘制使用close会导致显示异常,偷懒方法可以不用close。。。
二、3D可带可不带纹理
-
因为有了之前的经验,我们可以升级实验一下将graphics魔改为3D的,目的是将它加一个z坐标,那就在之前的基础上给graphics加上moveTo3d、lineTo3d等等接口,然后模仿源码将路径点存储加工类impl.ts复制过来重写一下,将有2D坐标的地方都照猫画虎的加上z坐标,在我们Graphics3D组件的onLoad里将原impl对象的数据赋值到新G3DImpl对象里,然后将源码中所有用到impl对象的代码都复制过来改为用自己的G3DImpl对象。由于顶点数据结构里a_position一直都有z坐标存储位置,所以就用上面加线长后的顶点数据结构了。最后就可以得到用程序来高自由度3D画图的快乐!!!
-
3D绘制组件附带的材质可勾选深度写入和深度测试更好
-
3D绘制组件可带纹理可不带纹理
-
最终效果:
-
源码项目:https://gitee.com/XiGeSiBoSeZi/study.git
喜欢的同学可以去仓库里看看,欢迎修改。