硬核引擎魔改!实现 Graphics 2D3D 带纹理绘制

2023-10-15 03:40

本文主要是介绍硬核引擎魔改!实现 Graphics 2D3D 带纹理绘制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Cocos Creator 自带的 Graphics 组件给我们提供了一系列绘画接口,不过有时候想实现一些特殊需求,难免就需要自己想办法。今天就和大家分享一下自己继承 graphics 后魔改的一些简单功能。魔改之后将实现:

  • 2D 带纹理画出各种路径等效果;

  • 3D 可用程序高自由度绘画出各种路径、图形等效果;

  • 具体可应用在:游戏中实时绘制角色路径线、魔法画笔可以用特殊纹理画画、3D 游戏中实时生成玩家想要的 3D 物体/根据游戏玩法高自由度生成 3D 物体等等。

41265a42fbb764ed9ec5ee08369abcbb.png

2D 效果预览

b4c46a9b9477a62c47c06f73c7b3a44d.gif

3D 效果预览

本次所用引擎版本为 Cocos Creator 3.4.1,以下是我的魔改思路,同样的思路也可以用在魔改其它组件上。

一、2D 带纹理

引擎源码虽然简洁整齐但看着依然云里雾里,咋办呢?没有捷径,努力啃下来吧,万一成了呢!而当我顺着文件夹分门别类地看下来后,还真没有想象中的困难(因为以前看过一个超高耦合度项目代码,简直是地狱级阅读难度,后来再复杂的代码逻辑都觉得不太吃力了)。

阅读引擎源码可以知道 graphics 的绘制原理:

  • graphics.ts 中实现绘制组件,通过各种接口收集绘制信息;

  • graphics-assembler.ts 中实现顶点渲染数据组装器;

  • impl.ts 中实现绘制路径点的存储和加工。

那么,该如何进行魔改呢?我想到了继承+重载 graphics 组件的方法:

  • graphics 组件的 _flushAssembler 方法是获得顶点渲染数据组装器的地方,因此可以在这个方法里实现对顶点数据渲染组装器的重写。

  • 想加纹理,则在 shader 里需要线的长度,线长和线宽两个数据即可组成 uv 坐标来获取纹理的像素点。

  • onLoad 方法里,将路径点存储加工器 impl.ts 替换为自己实现的路径点存储加工器。

思路有了,开工!

首先继承 graphics 组件,然后对照着源码重载 _flushAssembler 方法。考虑到 v3.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);
}
}
  • 最终效果

49bf0daf2d29bf177708a4df121bcc9e.png

3a0877670feab57adfde0ea04ef39979.png

cce6b827f6f65a13e4fff8fd4742a063.png

注:当前代码如果绘制使用 close 会导致显示异常,偷懒方法可以不用 close

二、3D 可带可不带纹理

有了之前的经验,接下来升级实验一下将 graphics 魔改为 3D 的。

我们需要给它加一个 z 坐标,那就在之前的基础上给 graphics 加上 moveTo3dlineTo3d 等等接口,然后模仿源码将路径点存储加工类 impl.ts 复制过来重写一下,将有 2D 坐标的地方都照猫画虎的加上 z 坐标。

在我们 Graphics3D 组件的 onLoad 里将原 impl 对象的数据赋值到新 G3DImpl 对象里,然后将源码中所有用到 impl 对象的代码都复制过来改为用自己的 G3DImpl 对象。

由于顶点数据结构里 a_position 一直都有 z 坐标存储位置,所以就用上面加线长后的顶点数据结构了。最后就可以得到用程序来高自由度 3D 画图的快乐!

  • 3D 绘制组件附带的材质可勾选深度写入和深度测试,效果更好。

837a11edd9f015b3c58599bd325990f6.png

  • 3D 绘制组件可带纹理可不带纹理

44e995956d74b42c180f90bc35381f36.png

  • 最终效果

19d6c38347fe9207af28bddbe130aae5.gif

a0934d0b72969438d50cec440f90eb45.gif

0bb82337b90c43cfe016017117e2dff6.gif


欢迎点击文末【阅读原文】前往论坛专贴一起交流讨论,项目完整源码放在开源仓库供各位下载,希望能对大家有所帮助!

完整源码

https://gitee.com/XiGeSiBoSeZi/study.git

论坛专贴

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

往期精彩

0874254254a48c82b7f477a9031a566a.png

55a9bcd9c4a8168ce984a0633a839a0d.png

333c1ce12b447b36af95644921119cfc.png

d83ada0c6e325b3ed746b23c9b033f72.gif

这篇关于硬核引擎魔改!实现 Graphics 2D3D 带纹理绘制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/weixin_44053279/article/details/129577613
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/215101

相关文章

Python+PyQt5实现MySQL数据库备份神器

《Python+PyQt5实现MySQL数据库备份神器》在数据库管理工作中,定期备份是确保数据安全的重要措施,本文将介绍如何使用Python+PyQt5开发一个高颜值,多功能的MySQL数据库备份工具... 目录概述功能特性核心功能矩阵特色功能界面展示主界面设计动态效果演示使用教程环境准备操作流程代码深度解

golang float和科学计数法转字符串的实现方式

《golangfloat和科学计数法转字符串的实现方式》:本文主要介绍golangfloat和科学计数法转字符串的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望... 目录golang float和科学计数法转字符串需要对float转字符串做处理总结golang float

linux lvm快照的正确mount挂载实现方式

《linuxlvm快照的正确mount挂载实现方式》:本文主要介绍linuxlvm快照的正确mount挂载实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux lvm快照的正确mount挂载1. 检查快照是否正确创建www.chinasem.cn2.

利用Python实现时间序列动量策略

《利用Python实现时间序列动量策略》时间序列动量策略作为量化交易领域中最为持久且被深入研究的策略类型之一,其核心理念相对简明:对于显示上升趋势的资产建立多头头寸,对于呈现下降趋势的资产建立空头头寸... 目录引言传统策略面临的风险管理挑战波动率调整机制:实现风险标准化策略实施的技术细节波动率调整的战略价

使用Python和Tkinter实现html标签去除工具

《使用Python和Tkinter实现html标签去除工具》本文介绍用Python和Tkinter开发的HTML标签去除工具,支持去除HTML标签、转义实体并输出纯文本,提供图形界面操作及复制功能,需... 目录html 标签去除工具功能介绍创作过程1. 技术选型2. 核心实现逻辑3. 用户体验增强如何运行

SpringBoot实现Kafka动态反序列化的完整代码

《SpringBoot实现Kafka动态反序列化的完整代码》在分布式系统中,Kafka作为高吞吐量的消息队列,常常需要处理来自不同主题(Topic)的异构数据,不同的业务场景可能要求对同一消费者组内的... 目录引言一、问题背景1.1 动态反序列化的需求1.2 常见问题二、动态反序列化的核心方案2.1 ht

Python实现文件批量重命名器

《Python实现文件批量重命名器》在日常工作和学习中,我们经常需要对大量文件进行重命名操作,本文将介绍一个使用Python开发的文件批量重命名工具,提供了多种重命名模式,有需要的小伙伴可以了解下... 目录前言功能特点模块化设计1.目录路径获取模块2.文件列表获取模块3.重命名模式选择模块4.序列号参数配

golang实现延迟队列(delay queue)的两种实现

《golang实现延迟队列(delayqueue)的两种实现》本文主要介绍了golang实现延迟队列(delayqueue)的两种实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录1 延迟队列:邮件提醒、订单自动取消2 实现2.1 simplChina编程e简单版:go自带的time

Python使用python-docx实现自动化处理Word文档

《Python使用python-docx实现自动化处理Word文档》这篇文章主要为大家展示了Python如何通过代码实现段落样式复制,HTML表格转Word表格以及动态生成可定制化模板的功能,感兴趣的... 目录一、引言二、核心功能模块解析1. 段落样式与图片复制2. html表格转Word表格3. 模板生

SpringBoot实现多环境配置文件切换

《SpringBoot实现多环境配置文件切换》这篇文章主要为大家详细介绍了如何使用SpringBoot实现多环境配置文件切换功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 示例代码结构2. pom文件3. application文件4. application-dev文