绘图组件无限绘制导致三角形数量暴增问题


每次用画图组件绘制一个圆圈都会增加三角形的数量,有没有什么办法优化这个问题
因为三角形的数量到达40万以上就会掉帧

重复的点,去掉试一下.

可以换个思路,自己往texture里填充rgba数据。这样子就没有这么多三角形了。只有一个DC。
https://store.cocos.com/app/detail/8258
给你参考一下

画圆还是太耗顶点了,用图片吧

那mask擦除呢

都操作像素点了,擦除不会啊 :sweat_smile:

我想到的办法也只有去重,但是也涉及到保存点的数组太大,遍历会不会很费时间的问题,还有面积重叠检测问题

你问codex啊

是类似画板/圆形笔触?一坨是涂鸦部分?
如果圆形不是可编辑/可撤销,用纹理当画布?后面的圆覆盖前面的。
用mask/模板测试/裁剪测试剔除,性能改善不了多少。
或者检测完全被覆盖的圆形不要绘制,或者把轨迹转为平滑的多边形而不是一个个圆


几年前做的了

既然是 自由涂鸦的画板 ,并且需要 支持更换颜色和粗细 ,那么“方案一:RenderTexture(渲染到纹理)”就是你唯一的、也是最完美的解决方案。

因为你每次更改颜色或粗细后画的线,都会被“印”在画板上,之前画的内容已经被固化成图片像素了,所以怎么改都不会影响之前的涂鸦。而且无论你画了多少笔,最终屏幕上只有一张图片,帧率永远满帧。

下面是在 Cocos Creator 3.x 中实现这个功能的完整实战指南。

核心实现原理

在这个方案中,你需要三个核心组件来打配合:

  1. Graphics(画笔): 只负责画 当前正在画 的这一笔。
  2. Camera(专用摄像机): 眼睛死死盯着 Graphics,把 Graphics 画出来的东西实时拍下来。
  3. Sprite(画纸): 显示摄像机拍下来的照片(RenderTexture)。

最核心的魔法在于摄像机的 ClearFlags (清除标志)。 正常摄像机每一帧都会把上一帧的画面擦除(清空成背景色),但如果我们把这个摄像机设置为“不清除(Dont Clear)”,它就会把每一帧的画面叠加起来。这时候我们只要调用 graphics.clear() 清空画笔,画纸上的图案依然会保留!

步步为营:操作步骤

第一步:设置专属 Layer

为了不影响游戏里的其他 UI,我们需要给画笔一个专属的图层。

  1. 在编辑器右上角点击 Project -> Project Settings -> Layers
  2. 添加一个新的 Layer,比如命名为 DRAWING_BRUSH

第二步:搭建节点层级

在场景树(Hierarchy)中,搭建如下结构:

  • Canvas (默认的主画布)
    • Main Camera (主摄像机,Visibility 勾选 UI_2D 等, 千万不要勾选 DRAWING_BRUSH )
    • DrawingBoard (Sprite) (用来当画纸的节点)
      • BrushCamera (Camera) (专属摄像机)
      • BrushGraphics (Graphics) (用来当画笔的节点)

第三步:配置专属摄像机 (BrushCamera)

选中 BrushCamera 节点,在属性检查器(Inspector)中进行如下极为关键的设置:

  1. Visibility: 只勾选 DRAWING_BRUSH (只看画笔)。
  2. ClearFlags: 不要勾选 Color (如果是英文界面,设置为 DONT_CLEAR 或者只保留 DEPTH_STENCIL )。这一步是让图案叠加的灵魂!
  3. Projection: 设置为 ORTHO (正交透视,和 2D UI 一致)。

第四步:配置画笔 (BrushGraphics)

选中 BrushGraphics 节点:

  1. 将其右上角的 Layer 设置为你刚才创建的 DRAWING_BRUSH
  2. 挂载 Graphics 组件。

完整核心代码 (TypeScript)

新建一个脚本 DrawingBoard.ts ,挂载到 DrawingBoard 节点上,并把对应的节点拖入组件槽位。

import { _decorator, Component, Node, Graphics, Camera, RenderTexture, Sprite, SpriteFrame, UITransform, EventTouch, Color } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('DrawingBoard')
export class DrawingBoard extends Component {
    @property(Camera)
    public brushCamera: Camera = null!;

    @property(Graphics)
    public brushGraphics: Graphics = null!;

    @property(Sprite)
    public paperSprite: Sprite = null!;

    // 内部变量
    private renderTex: RenderTexture = null!;
    private pointCount: number = 0;
    
    // 画笔设置
    private currentLineWidth: number = 5;
    private currentColor: Color = Color.BLACK;

    start() {
        this.initRenderTexture();
        this.initTouchEvents();
        this.applyBrushSettings();
    }

    private initRenderTexture() {
        // 获取画板的宽高
        const uiTransform = this.getComponent(UITransform);
        const width = uiTransform.width;
        const height = uiTransform.height;

        // 1. 创建 RenderTexture
        this.renderTex = new RenderTexture();
        this.renderTex.reset({ width: width, height: height });

        // 2. 让摄像机渲染到这个纹理上
        this.brushCamera.targetTexture = this.renderTex;

        // 3. 将纹理赋值给画纸(Sprite)
        const spriteFrame = new SpriteFrame();
        spriteFrame.texture = this.renderTex;
        this.paperSprite.spriteFrame = spriteFrame;
    }

    private initTouchEvents() {
        // 监听触摸事件
        this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
        this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
        this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
    }

    private onTouchStart(event: EventTouch) {
        // 获取局部坐标
        const uiTransform = this.getComponent(UITransform);
        const pos = uiTransform.convertToNodeSpaceAR(event.getUILocation());
        
        this.brushGraphics.moveTo(pos.x, pos.y);
        this.pointCount = 0;
    }

    private onTouchMove(event: EventTouch) {
        const uiTransform = this.getComponent(UITransform);
        const pos = uiTransform.convertToNodeSpaceAR(event.getUILocation());
        
        this.brushGraphics.lineTo(pos.x, pos.y);
        this.brushGraphics.stroke();
        
        this.pointCount++;

        // 优化:如果这一笔画得太长(比如超过 50 个点),就“印”一次并清空画笔
        // 防止用户手指一直不松开导致单次绘制的三角形过多
        if (this.pointCount > 50) {
            this.bakeToTexture();
        }
    }

    private onTouchEnd(event: EventTouch) {
        // 手指抬起时,将这一笔彻底“印”到画纸上
        this.bakeToTexture();
    }

    private bakeToTexture() {
        // 因为 BrushCamera 的 ClearFlags 没勾选 Color,
        // 所以这一步并不会清空 RenderTexture,而是把 Graphics 的新线段叠加印上去。
        
        // 随后立刻清空 Graphics,释放本轮产生的三角形。
        this.brushGraphics.clear();
        
        // 清空后,重置起点,防止下一笔连着上一笔
        this.pointCount = 0;
    }

    // ================= 公开 API:供外部 UI 按钮调用 =================

    /** 修改画笔颜色 */
    public setBrushColor(color: Color) {
        this.currentColor = color;
        this.applyBrushSettings();
    }

    /** 修改画笔粗细 */
    public setBrushWidth(width: number) {
        this.currentLineWidth = width;
        this.applyBrushSettings();
    }

    /** 橡皮擦功能(其实就是把颜色换成背景色,通常是白色或透明) */
    public useEraser(eraserWidth: number = 20) {
        // 假设背景是白色的
        this.currentColor = Color.WHITE; 
        this.currentLineWidth = eraserWidth;
        this.applyBrushSettings();
    }

    /** 清空整个画板 */
    public clearBoard() {
        // 销毁旧的,重新建一个干净的
        this.renderTex.destroy();
        this.initRenderTexture();
    }

    private applyBrushSettings() {
        this.brushGraphics.lineWidth = this.currentLineWidth;
        this.brushGraphics.strokeColor = this.currentColor;
        this.brushGraphics.fillColor = this.currentColor;
    }
}

进阶优化细节说明

  1. 防止单笔画太长导致卡顿 ( pointCount > 50 的逻辑): 如果玩家一直在屏幕上画圈圈不松手, Graphics 里的顶点依然会堆积。上面的代码中加入了 pointCount > 50 自动触发 bakeToTexture 的逻辑。这就相当于玩家虽然还在画,但底层的画笔每画 50 个点就悄悄把墨水“印”到纸上并换一根新画笔接着画,做到了无缝衔接且永远不卡。
  2. 橡皮擦的本质: 使用这种方案,橡皮擦非常容易实现。你不需要写复杂的遮罩或裁剪算法,只需要调用 setBrushColor(Color.WHITE) (或者你画板的背景色),把画笔调粗一点,直接覆盖上去即可。
  3. 抗锯齿: Graphics 组件自带一定的抗锯齿,但如果渲染到 RenderTexture 后感觉边缘不够平滑,可以在 Graphics 组件的设置里调整 Line Join (线段交汇处)为 ROUNDLine Cap (线段两端)也设置为 ROUND ,这样画出来的线条会非常圆润。
2赞

[.WebGL-0x10c000c2c00] GL_INVALID_OPERATION: glDrawElements: Feedback loop formed between Framebuffer and active Texture.

这个代码,我跑了.报这个warn,画不出来.我继续看看

摄像机可见性的问题吧,camera拍到的内容输出到renderTexture,然后这个renderTexture又被camera拍到了.应该给这个renderTexure专门设置额外的一个摄像机.

maks擦除也可以结合shader,你是要怎么样的擦除