返回顶部
首页 > 资讯 > 移动开发 >在iOS中使用OpenGL ES实现绘画板的方法
  • 439
分享到

在iOS中使用OpenGL ES实现绘画板的方法

iOS绘画板OpenGLES 2022-05-15 09:05:13 439人浏览 独家记忆
摘要

今天我们使用 OpenGL ES 来实现一个绘画板,主要介绍在 OpenGL ES 中绘制平滑曲线的实现方案。 首先看一下最终效果: 在 iOS 中,有很多种方式可以实现一个绘画板

今天我们使用 OpenGL ES 来实现一个绘画板,主要介绍在 OpenGL ES 中绘制平滑曲线的实现方案。

首先看一下最终效果:

iOS 中,有很多种方式可以实现一个绘画板,比如我的另外一个项目 MFPaintView 就是基于 CoreGraphics 实现的。

然而,使用 OpenGL ES 来实现可以获得更多的灵活性,比如我们可以自定义笔触的形状,这是其他实现方式做不到的。

我们知道,OpenGL ES 中只有 点、直线、三角形 这三种图元。因此, 怎么在 OpenGL ES 中绘制曲线 ,是我们第一个要解决的问题,也是最复杂的问题。

我们会使用比较大的篇幅来讲解这个问题。至于绘画板的其他功能实现,并不是说不重要,只是说其他的绘画板实现方式,也会有类似的逻辑,所以这部分会放在最后再简单介绍一下。

一、怎么绘制曲线

在 OpenGL ES 中绘制曲线的方式,就是 将曲线拆分成点序列来绘制

因为要绘制点,所以我们采取的是 点图元 。即我们要把顶点数据当成 来绘制,并且每个点都要绘制出笔触的纹理。关键步骤如下:

指定图元类型:


glDrawArrays(GL_POINTS, 0, self.vertexCount);

顶点着色器:


attribute vec4 Position;

unifORM float Size;

void main (void) {
  gl_Position = Position;
  gl_PointSize = Size;
}

片段着色器:


precision highp float;

uniform float R;
uniform float G;
uniform float B;
uniform float A;

uniform sampler2D Texture;

void main (void) {
  vec4 mask = texture2D(Texture, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y));
  gl_FraGColor = A * vec4(R, G, B, 1.0) * mask;
}

这里的关键点在于 gl_PointCoord 这个内置变量,当我们使用点图元的时候,可以通过这个变量获取到 当前像素在点图元中的归一化坐标

但是这个坐标的原点是在左上角,这和纹理坐标在竖直方向上是相反的。所以从纹理读取颜色的时候,要做一个 y 坐标的转换。

接下来,我们通过 UITouch 来获取触摸点的位置,然后算出归一化的顶点坐标。


- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  [super touchesMoved:touches withEvent:event];
  
  [self addPointWithTouches:touches];
}

但是由于 iOS 系统触摸事件的派发频率有限,我们最终得到的只能是稀疏的点。如下图所示,每个触摸点之间的间隔会比较大。

二、怎么绘制密集的点

很容易想到,只需要在两个点之间,按照一定的密度进行插值,就可以绘制出连续的轨迹。

但是很明显,我们的绘制结果是折线,并不平滑。

三、怎么使曲线变平滑

解决点连接不平滑的问题,一般是使用贝塞尔曲线。这种方案在 MFPaintView 中也得到了很好的应用。

具体的做法是使用 两个顶点间的中点一个顶点 ,来构造一条贝塞尔曲线。如下图,图中的 3 个 红点 被用来构造一条贝塞尔曲线。

于是,我们的问题就变成了 怎么在 OpenGL ES 中绘制贝塞尔曲线 。相当于已知贝塞尔曲线的 3 个关键点,反向来求曲线上的点序列。

我们知道贝塞尔曲线的方程是 P = (1 - t)^2 * P0 + 2 * t * (1 - t) * P1 + t^2 * P2t 是唯一的变量,其取值范围是 0 ~ 1

所以我们可以采取线性取值的方式,每一条贝塞尔曲线取 n 个点( n 是个确定的常量)。只要依次往方程中代入 1 / n 、 2 / n 、 ... n / n ,就可以得到一个点序列。

先将 n 取一个比较小的值,这样比较容易看出存在的问题。我们发现, 点序列的间隔并不均匀 。原因有两个:

  • 不同贝塞尔曲线的长度不一样,使用同一个 n 值,算出来的点的疏密程度肯定不同。
  • 由于贝塞尔曲线随着 t 增长,曲线长度的增长并不是线性的。按照我们上面的算法,最终会得到的结果是 两头比较稀疏,中间比较密集

四、怎么生成均匀的点序列

贝塞尔曲线生成均匀的点序列,涉及到了一个经典的「贝塞尔曲线匀速运动」问题。

这个问题的推导和计算比较复杂。如果你有兴趣,可以阅读一下文末的两篇文章。由于我还不能完全领悟,就不在这里误导大家了。

简单来说,就是我们通过一系列的骚操作,封装了一个方法,只需要传入贝塞尔曲线的 3 个关键点和笔触尺寸,就可以获取均匀的点序列。


+ (NSArray <NSValue *>*)pointsWithFrom:(CGPoint)from
                  to:(CGPoint)to
                control:(CGPoint)control
               pointSize:(CGFloat)pointSize;

下面我们固定贝塞尔曲线的 起始点控制点 ,只移动 终止点 ,来验证一下这个方法是否可靠。

可以看到,在移动过程中,点和点的距离基本是保持一致的,并且是均匀的。通过这个「神奇」的方法,我们终于画出了平滑且均匀的曲线。

五、绘画板功能实现

终于讲完了最麻烦的部分,接下来简单介绍一下绘画板基本功能的实现。

1、颜色混合

在以往的例子中,我们在开始一次渲染之前,都会调用 glClear(GL_COLOR_BUFFER_BIT) 来清除画布,因为我们不希望保留上次的渲染结果。

但是对于一个绘画板来说,我们要不断地往画布上画东西,所以是希望保留上次结果的。因此,在绘制之前不能执行清除的操作。

另外,由于我们的画笔可能是半透明的,所以新绘制的颜色需要和画布上已经存在的颜色进行混合。因此在绘制开始之前,需要开启混合选项。


glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

2、笔触调整

笔触有 3 个属性可以调整: 颜色、尺寸、形状 。它们本质上都是对点图元的调整,通过 uniform 变量的形式,将颜色、尺寸、纹理传入着色器并应用。

3、橡皮擦

GLPaintView 在初始化的时候,需要传入一个背景色参数,当用户切换到橡皮擦功能的时候,内部只是单纯地将画笔的颜色切换成背景色,于是就产生了橡皮擦的效果。

4、撤销重做

撤销重做功能需要依赖两个栈来实现。我们把用户的手指从 按下屏幕到离开屏幕 这一过程中产生的数据,定义为一个操作对象,这个操作对象保存了归一化后的点序列,以及点的属性。


@interface MFPaintModel : NSObject

/// 笔刷尺寸
@property (nonatomic, assign) CGFloat brushSize;
/// 笔刷颜色
@property (nonatomic, strong) UIColor *brushColor;
/// 笔刷模式
@property (nonatomic, assign) GLPaintViewBrushMode brushMode;
/// 笔触纹理图片文件名
@property (nonatomic, copy) NSString *brushImageName;
/// 点序列
@property (nonatomic, copy) NSArray<NSValue *> *points;

@end

撤销重做的代码实现大概像这样子:


- (void)undo {
  if ([self.operationStack isEmpty]) {
    return;
  }
  MFPaintModel *model = self.operationStack.topModel;
  [self.operationStack popModel];
  [self.undoOperationStack pushModel:model];
  
  [self reDraw];
}

- (void)redo {
  if ([self.undoOperationStack isEmpty]) {
    return;
  }
  MFPaintModel *model = self.undoOperationStack.topModel;
  [self.undoOperationStack popModel];
  [self.operationStack pushModel:model];
  
  [self drawModel:model];
}

需要注意的是,由于 撤销操作 需要先清除画布,所以每次都需要重绘。而 重做操作 可以利用上次绘制的结果,所以每次只需要绘制一个步骤即可。

源码

请到 GitHub 上查看完整代码。

到此这篇关于在iOS中使用OpenGL ES实现绘画板的方法的文章就介绍到这了,更多相关iOS 绘画板内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 在iOS中使用OpenGL ES实现绘画板的方法

本文链接: https://lsjlt.com/news/30458.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 在iOS中使用OpenGL ES实现绘画板的方法
    今天我们使用 OpenGL ES 来实现一个绘画板,主要介绍在 OpenGL ES 中绘制平滑曲线的实现方案。 首先看一下最终效果: 在 iOS 中,有很多种方式可以实现一个绘画板...
    99+
    2022-05-15
    iOS 绘画板 OpenGL ES
  • 怎么在JavaScript中使用canvas实现一个画板和签字板功能
    怎么在JavaScript中使用canvas实现一个画板和签字板功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。<!DOCTYPE html><...
    99+
    2023-06-06
  • R语言使用ggplot绘制画中画细节放大的方法
    目录1. 载入相关包2. 数据生成3. 基础绘图4. 放大效果5. 绘图美化其他方法当我们在利用ggplot绘图时,当遇到一些量纲相差过大,或者一些图的某些点排布密集时,需要将细节部...
    99+
    2024-04-02
  • iOS实现转场动画的3种方法示例
    什么是转场动画 在 NavigationController 里 push 或 pop 一个 View Controller,在 TabBarController 中切换到其他 V...
    99+
    2022-05-28
    ios 转场 动画
  • 在 Node.js 中使用原生 ES 模块方法解析
    从版本 8.5.0 开始,Node.js 开始支持原生 ES 模块,可以通过命令行选项打开该功能。新功能很大程度上得归功于 Bradley Farias。 1.演示 这个示例的代码目录结构如下: e...
    99+
    2022-06-04
    模块 方法 Node
  • 混合语言编程—C#使用原生的Directx和OpenGL绘图的方法
    由于项目需要做一些图形展示,所以就想到了使用Directx和OpenGL来绘图,但项目准备使用C#来开发(大家比较熟悉C#),在网上看了相关的资料,有一些第三方的控件可用,试用了下,...
    99+
    2022-11-15
    C# Directx OpenGL
  • jQuery中动画的实现方法
    这篇文章主要介绍“jQuery中动画的实现方法”,在日常操作中,相信很多人在jQuery中动画的实现方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”jQuery中动画的实现...
    99+
    2024-04-02
  • css3中实现动画的方法
    这篇文章给大家分享的是有关css3中实现动画的方法的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。css3中实现动画的两种方式分别是:1、分别利用transition属性和transform属性来设置过渡和形状;2...
    99+
    2023-06-07
  • deepin20怎么使用画板打开图片?deepin画板打开图片的两种方法
    deepin20系统中可以使用画板打开图片,该怎么设置呢?下面我们就来看看详细的教程。 方法一: 点右上角的下拉菜单按钮,在下拉菜单中点【打开】。 操作与二种方法类似,在对话框中选择要打开的图片即可,图片会自动加载到画板,...
    99+
    2022-05-22
    deepin 画板 图片
  • iOS仿抖音视频加载动画效果的实现方法
    前言 这几天一直跟开源的抖音demo斗智斗勇,今天跟大家分享的是抖音中或者快手中加载视频的动画,这个加载效果还是挺实用,下面话不多说了,来随着小编一起学习学习吧 上图看成品 实现...
    99+
    2022-05-22
    视频 加载 动画
  • iOS中UIBezierPath实现饼状图的方法
    这篇文章主要介绍iOS中UIBezierPath实现饼状图的方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!首先看效果图:代码:#import <UIKit/UIKit.h> NS_...
    99+
    2023-06-14
  • iOS使用fastlane实现持续集成的方法教程
    前言 最近公司有打渠道包的需求,领导说使用fastlane来做持续集成,发了点时间研究了下,所有有了这篇文章 本文主要涉及到以下几个主题: fastlane是什么和为什么使...
    99+
    2022-05-26
    ios fastlane 持续集成
  • 使用canvas实现创意绘画和艺术表现的技巧
    利用canvas实现创意绘画和艺术表达 概述:在Web开发中,我们常用的HTML5元素之一就是Canvas。Canvas是一个用于绘制图形的HTML元素,我们可以使用JavaScript在其中进行创意绘画和艺术表达。本文将介绍如...
    99+
    2024-01-17
    创意绘画 艺术表达 canvas实现
  • Android基于OpenGL在GLSurfaceView上绘制三角形及使用投影和相机视图方法示例
    本文实例讲述了Android基于OpenGL在GLSurfaceView上绘制三角形及使用投影和相机视图方法。分享给大家供大家参考,具体如下: 定义三角形 OpenGL 允许我...
    99+
    2022-06-06
    示例 方法 视图 glsurfaceview opengl 相机 Android
  • iOS中读写锁的简单实现方法实例
    目录废话开篇思考一、对于锁的类型的理解思考二、读写锁的实现逻辑思考三、简单封装读写锁,满足读写逻辑总结废话开篇 iOS 下的多线程的技术的应用衍生出了锁的机制,试想,如果 iOS 下...
    99+
    2022-06-04
    ios 读写锁
  • 如何使用单div实现CSS绘图方法
    本篇内容主要讲解“如何使用单div实现CSS绘图方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何使用单div实现CSS绘图方法”吧! ...
    99+
    2024-04-02
  • Android实现在map上画出路线的方法
    本文实例讲述了Android实现在map上画出路线的方法。分享给大家供大家参考。具体如下: 最近在搞在地图上画出路线图,经过一段时间的摸索,终于搞明白了,其实也挺简单的,写个类...
    99+
    2022-06-06
    map 方法 路线 Android
  • 怎么在Python中使用turtle库绘画飘落的银杏树
    这篇文章给大家介绍怎么在Python中使用turtle库绘画飘落的银杏树,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、导入所需的库import turtleimport randomfrom math import...
    99+
    2023-06-15
  • 如何使用JavaScript实现模板方法模式
    模板方法模式是一种行为设计模式,它是指将一个算法的骨架定义在一个操作中,将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。在本文中,我们将介绍如何使用 JavaScript 实现模板方法模式。实...
    99+
    2023-05-14
  • CSS3中@keyframes简单动画的实现方法
    这篇文章主要介绍了CSS3中@keyframes简单动画的实现方法,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。CSS3 @keyframes简单动画实现定义:通过 @key...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作