android 贝塞尔曲线_OpenGL 实践之贝塞尔曲线绘制

说到贝塞尔曲线,大家肯定都不陌生,网上有很多关于介绍和理解贝塞尔曲线的优秀文章和动态图。

以下两个是比较经典的动图了。

二阶贝塞尔曲线:

e466cb8ed19a3ea7e10cbe0a5cc15bdb.gif

三阶贝塞尔曲线:

95f7ffe7cea039accdc3137ed6231cec.gif

由于在工作中经常要和贝塞尔曲线打交道,所以简单说一下自己的理解:

现在假设我们要在坐标系中绘制一条直线,直线的方程很简单,就是 y=x ,很容易得到下图:

21da8616ff7f8a8d36c304a43b2812eb.png

现在我们限制一下 x 的取值范围为 0~1 的闭区间,那么可以得出 y 的取值范围也是 0~1。

而在 0~1 的区间范围内,x 能取的数有多少个呢?答案当然是无数个了。

0c7796e41db4250f98de42cbcdbb9a85.png

同理,y 的取值个数也是有无数个。每一个 x 都有唯一的 y 与之对应,一个 (x,y) 在坐标系上就是一个点。

所以最终得到的 0~1 区间的线段,实际上是由无数的点组成的。

那么这条线段有多长呢?长度是由 x 的取值范围来决定的,若 x 的取值为 0~2,那么线段就长了一倍。

另外,如果 x 的取值范围不是无数个,而是以 0.05 的间距从 0 到 1 之间递增,那么得到的就是一串点了。

由于 点 是一个理想状态下的描述,在数学上点是没有宽高、没有面积的。

但是,如果你在草稿纸上绘制一个点,不管你用到是铅笔、毛笔、水笔还是画笔,一个点总是要占面积的。

毛笔画一个点的面积可能需要铅笔画几十个点了。

在实际生活中,如果要以 0.05 的间距在第一幅坐标系图中画出 x 在 0~1 区间的一串点,最终结果就和直接画一条线段没啥差别了。

这就是现实和理想的差别了。理想一串点,现实一条线。


我们把这个逻辑放到手机屏幕上。

手机屏幕上的最小显示单位就是像素了,一个 1920 * 1080 的屏幕指的就是各方向上像素点的数量。

假如绘制一条和屏幕一样宽的线段,一个点最小就算一个像素,最多也就 1080 个点了。

点占的像素越多,那么实际绘制时需要的点的数量越少,这也算是潜在的优化项了。


说完直线,再回到贝塞尔曲线上。

曲线和直线都有一个共同点,它们都有各自特定的方程,只不过我们用的直线例子比较简单,既 y = x ,一眼看出计算结果。

直线方程 y = x,在数学上可以这么描述:y 是关于 x 的函数,既 y = F(x) ,其中 x 的取值决定了该直线的长度。

根据上面的理解,这个长度的直线实际又是由在 x 的取值范围内对应的无数个点组成的。

反观贝塞尔曲线方程以及对应的图形如下:

  • 二阶贝塞尔曲线:其中,P0 和 P2 是起始点,P1 是控制点。
36e5b826bea480bdf38c9f12e2e323f3.png
324ec4cfcb68a7628ec0ab3969a6a570.png
  • 三阶贝塞尔曲线其中,P0 和 P3 是起始点,P1 和 P2 是控制点。
9bf598930495736071bef2a9a9b791bd.png
64101c96bcfcdcbab68613dfa957bd7c.png

不难理解,假设我们要绘制一条曲线,肯定要有起始和结束点来指定曲线的范围曲线。

而控制点就是指定该曲线的弧度,或者说指定该曲线的弯曲走向,不同的控制点得出的曲线绘制结果是不一样的。

另外,可以观察到,无论是几阶贝塞尔曲线,都会有参数 t 以及 t 的取值范围限定。

t 在 0~1 范围的闭区间内,那么 t 的取值个数实际上就有无数个了,这时的 t 就可以理解成上面介绍直线中讲到的 x 。

这样一来,就可以把起始点、控制点当初固定参数,那么贝塞尔曲线计算公式就成了 B = F(t) ,B 是关于 t 的函数,而 t 的取值范围为 0~1 的闭区间。

也就是说贝塞尔曲线,选定了起始点和控制点,照样可以看成是 t 在 0~1 闭区间内对应的无数个点所组成的。

有了上面的阐述,在工(ban)程(zhuan)的角度上,就不难理解贝塞尔曲线到底怎么使用了。


Android 绘制贝塞尔曲线

Android 自带贝塞尔曲线绘制 API ,通过 Path 类的 quadTo 和 cubicTo 方法就可以完成绘制。

 1 // 构建 path 路径,也就是选取 2 path.reset(); 3 path.moveTo(p0x, p0y); 4 // 绘制二阶贝塞尔曲线 5 path.quadTo(p1x, p1y, p2x, p2y); 6 path.moveTo(p0x, p0y); 7 path.close(); 8 9 // 最后的绘制操作10 canvas.drawPath(path, paint);

这里的绘制实际上就是把贝塞尔曲线计算的方程式交给了 Android 系统内部去完成了,参数传递上只传递了起始点和控制点。

我们可以通过自己的代码来计算这个方程式从而对逻辑上获得更多控制权,也就是把曲线拆分成许多个点组成,如果点的尺寸比较大,甚至可以减少点的个数实现同样的效果,达到绘制优化的目的。

OpenGL 绘制

通过 OpenGL 可以实现我们上述的方案,把曲线拆分成多个点组成。这种方案要求我们在 CPU 上去计算贝塞尔曲线方程,根据 t 的每一个取值,计算出一个贝塞尔点,用 OpenGL 去绘制上这个点。

这个点的绘制可以采用 OpenGL 中画三角形 GL_TRIANGLES 的形式去绘制,这样就可以给点带上纹理效果,不过这里面的坑略多,起始点和控制点都是运行时动态可变的实现难度会大于固定不变的。

这里先介绍另一种方案,这种方案实现比较简单也能达到优化效果,我们可以把贝塞尔曲线的计算方程式交给 GPU, 在 OpenGL Shader 中去完成。

这样一来,我们只要给定起始点和控制点,中间计算贝塞尔曲线去填补点的过程就交给 Shader 去完成了。

另外,通过控制 t 的数量,我们可以控制贝塞尔点填补的疏密。

t 越大,填补的点越多,超过一定阈值后,不会对绘制效果有提升,反而影响性能。

t 越小,那么贝塞尔曲线就退化成一串点组成了。所以说 t 的取值范围也能对绘制起到优化作用。

绘制效果如下图所示:

a3b1fa5c64388564666897106602c120.gif

以下就是实际的代码部分了,关于 OpenGL 的基础理论部分可以参考之前写过的文章和公众号,就不再阐述了。

在 Shader 中定义一个函数,实现贝塞尔方程:

1vec2 fun(in vec2 p0, in vec2 p1, in vec2 p2, in vec2 p3, in float t){2 float tt = (1.0 - t) * (1.0 -t);3 return tt * (1.0 -t) *p0 4 + 3.0 * t * tt * p1 5 + 3.0 * t *t *(1.0 -t) *p2 6 + t *t *t *p3;7}

该方程可以利用 Shader 中自带的函数优化一波:

1vec2 fun2(in vec2 p0, in vec2 p1, in vec2 p2, in vec2 p3, in float t)2{3 vec2 q0 = mix(p0, p1, t);4 vec2 q1 = mix(p1, p2, t);5 vec2 q2 = mix(p2, p3, t);6 vec2 r0 = mix(q0, q1, t);7 vec2 r1 = mix(q1, q2, t);8 return mix(r0, r1, t);9}

接下来就是具体的顶点着色器 shader :

 1// 对应 t 数据的传递 2attribute float aData; 3// 对应起始点和结束点 4uniform vec4 uStartEndData; 5// 对应控制点 6uniform vec4 uControlData; 7// mvp 矩阵 8uniform mat4 u_MVPMatrix; 910void main() {11 vec4 pos;12 pos.w = 1.0;13 // 取出起始点、结束点、控制点14 vec2 p0 = uStartEndData.xy;15 vec2 p3 = uStartEndData.zw;16 vec2 p1 = uControlData.xy;17 vec2 p2 = uControlData.zw;18 // 取出 t 的值19 float t = aData;20 // 计算贝塞尔点的函数调用21 vec2 point = fun2(p0, p1, p2, p3, t);22 // 定义点的 x,y 坐标23 pos.xy = point;24 // 要绘制的位置25 gl_Position = u_MVPMatrix * pos;26 // 定义点的尺寸大小27 gl_PointSize = 20.0;28}

代码中的 uStartEndData 对应起始点和结束点,uControlData 对应两个控制点。

这两个变量的数据传递通过 glUniform4f 方法就好了:

 1 mStartEndHandle = glGetUniformLocation(mProgram, "uStartEndData"); 2 mControlHandle = glGetUniformLocation(mProgram, "uControlData"); 3 // 传递数据,作为固定值 4 glUniform4f(mStartEndHandle, 5 mStartEndPoints[0], 6 mStartEndPoints[1], 7 mStartEndPoints[2], 8 mStartEndPoints[3]); 9 glUniform4f(mControlHandle,10 mControlPoints[0],11 mControlPoints[1],12 mControlPoints[2],13 mControlPoints[3]); 

另外重要的变量就是 aData 了,它对应的就是 t 在 0~1 闭区间的划分的数量。

1 private float[] genTData() {2 float[] tData = new float[Const.NUM_POINTS];3 for (int i = 0; i < tData.length; i ++) {4 float t = (float) i / (float) tData.length;5 tData[i] = t;6 }7 return tData;8 }

以上函数就是把 t 在 0~1 闭区间分成 Const.NUM_POINTS 份,每一份的值都存在 tData 数组中,最后通过 glVertexAttribPointer 函数传递给 Shader 。

最后实际绘制时,我们采用 GL_POINTS 的形式绘制就好了。

1 GLES20.glDrawArrays(GLES20.GL_POINTS, 0, Const.NUM_POINTS );

以上就是 OpenGL 绘制贝塞尔曲线的小实践。

具体的代码部分可以参考我的项目:

https://github.com/glumes/AndroidOpenGLTutorial

在参考中,也有一个 OpenGL 绘制贝塞尔曲线的例子,不过他绘制的是贝塞尔曲线面,采用的是 GL_TRIANGLES 的形式,而且在 tData 数组的构造也有些不同,但是都大同小异了,看明白了本文的例子也不难理解参考的文章。

关于 OpenGL 相关的文章,可以参考我之前写过的公众号内容:

  • OpenGL 系列---基础绘制流程
  • OpenGL 学习系列---基本形状的绘制
  • OpenGL 学习系列---坐标系统
  • OpenGL 学习系列---投影矩阵
  • OpenGL 学习系列 --- 纹理
  • OpenGL 学习系列---观察矩阵
  • OpenGL ES 学习资源分享
  • 用 OpenGL 对视频帧内容进行替换

参考

  1. https://yalantis.com/blog/how-we-created-visualization-for-horizon-our-open-source-library-for-sound-visualization/
319e6555b00af783763a46f934d3992f.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/563827.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Node.js Event loop 图解

直接上自己制作的流程图

支持串行隔离级别_从0到1理解数据库事务(上):并发问题与隔离级别

最近准备写一篇关于Spanner事务的分享&#xff0c;所以先分享一些基础知识&#xff0c;涉及ACID、隔离级别、MVCC、锁&#xff0c;由于太长&#xff0c;只好拆分成上下两篇&#xff1a;上&#xff1a;并发问题与隔离级别主要讲事务所要解决的问题、思路&#xff0c;先理解为什么…

mysql_unbuffered_query的_用mysql_unbuffered_query函数取大数据

昨天在做项目的时候&#xff0c;因为涉及到数据表结构的改动&#xff0c;需要进行大量数据的导入&#xff0c;那么如何高效的进行是我比较关注的。本文暂且从使用PHP脚本层面上来说&#xff0c;因为使用其他语言或其他方式也可以进行数据的重导。 在讨论这个问题的时候&#xf…

如何发布接口_Devops下的接口全生命周期管理与测试

什么是devops&#xff1f;随着时间的推移&#xff0c;devops的定义也在不断的演进。对于其定义可能出现千人千面&#xff0c;但从核心观点&#xff0c;整体业界还是保持着一致的认识。DevOps不是单一的技术或者工具&#xff0c;甚至不只是一个流程&#xff0c;而是包含应用设计…

lisp修改界址线属性_地籍与房产测量 A卷答案

地籍与房产测量 A卷答案一、单项选择题(共10个小题&#xff0c;每小题2分&#xff0c;共20分。)1、地籍测绘规范规定 界址点的精度分( C )A.一级 B.二级 C.三级 D.四级2、界址点的精度要求最高为 AA.5cm B.10cm C.15cm D.20cm3、城镇地区城区地籍图的比例尺一般采用( A )A. 1:1…

mysql获取数据库名_mysql获取数据库名

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航&#xff0c;为用户…

scala mysql连接池_Java与Scala的两种简易版连接池

Java版简易版连接池&#xff1a;import java.sql.Connection;import java.sql.DriverManager;import java.util.LinkedList;/*** 简易版的连接池*/public class ConnectionPool {//静态的Connectionprivate static LinkedListconnections;//加载驱动static {try {Class.forName…

查看mysql日志post_(转)MySQL 日志组提交

原文:https://jin-yang.github.io/post/mysql-group-commit.html组提交 (group commit) 是为了优化写日志时的刷磁盘问题&#xff0c;从最初只支持 InnoDB redo log 组提交&#xff0c;到 5.6 官方版本同时支持 redo log 和 binlog 组提交&#xff0c;大大提高了 MySQL 的事务处…

like语句太慢 sqlserver_SQLServer找出执行慢的SQL语句

SELECT(total_elapsed_time / execution_count)/1000 N平均时间ms,total_elapsed_time/1000 N总花费时间ms,total_worker_time/1000 N所用的CPU总时间ms,total_physical_reads N物理读取总次数,total_logical_reads/execution_count N每次逻辑读次数,total_logical_reads N逻辑…

mysql maxpreparedstmtcount_NodeJs Mysql Cant't create more than max_prepared_stmt_count statements

这阵子碰到一个数据库上的问题&#xff0c;一个刚上线不到一周的 NodeJs 接口服务里所有的查询全部都挂掉了&#xff0c;接口一直处于 pending 状态&#xff0c;看了下 pm2 的日志发现了报错&#xff1a;Cantt create more than max_prepared_stmt_count statements&#xff0c…

MySQL查询语句转postGRE语句_PostgreSQL DBA常用SQL查询语句

查看帮助命令DB# help --总的帮助DB# \h --SQL commands级的帮助DB# \? --psql commands级的帮助按列显示&#xff0c;类似MySQL的\GDB# \xExpanded display is on.查看DB安装目录(最好root用户执行)find / -name initdb查看有多少DB实例在运行(最好root用户执行)find / -name…

苹果cms10自适应模板_哪里有苹果cms10自适应模板?

1&#xff0c;苹果CMSv10大图轮播高端大气自适应视频网站模板源码苹果cms10自适应模板下载地址&#xff1a;https://www.mytheme.cn/maccms/54.html第一款大图宽屏的海报轮播幻灯样式&#xff0c;宽屏模板支持DIY扩展自适应影视模板苹果cms10自适应模板苹果cms10自适应模板2&am…

python实现进程通信_python进程间的通讯实现

1&#xff1a;进程间通讯的方法&#xff1a;apply_async()非阻塞式通讯 apply()阻塞式通讯2&#xff1a;使用Queue实现对Process创建的进程间通讯&#xff0c;Queue本身是一个消息队列程序&#xff0c;Queue常用方法&#xff1a;Queue.qsize():返回当前消息队列的消息数量Q…

yum mysql 版本低_mysql小版本升级(yum方式)

5.7.21 --> 5.7.231. 数据库备份(略)2. mysql配置文件备份cp /etc/my.cnf /etc/my.cnf.bak3. yum配置mysql源3.1安装mysql57-community-release-el6-8.noarch.rpm之类的东西rpm -ivh mysql57-community-release-el6-8.noarch.rpm3.2 查看mysql的repovim /etc/yum.repos…

mysql definer设置为root_mysql如何修改所有的definer

mysql中的definer是什么&#xff0c;有什么作用&#xff1f;我们在mysql创建view、trigger、function、procedure、event时都会定义一个Definer‘xxx’,类似如下&#xff1a;CREATEALGORITHMUNDEFINEDDEFINER root% SQL SECURITY DEFINERVIEW v_ questions ASSELECTq.idASid,q.…

vscode php formatter mac配置_Mac上配置Vs code时,遇到的几个“坑”!

在写一些简单的程序时&#xff0c;你喜欢用什么编译器呢&#xff1f;之前我一直用的是sublime&#xff0c;它打开的速度快&#xff0c;占用的内存小。但是有个麻烦的地方&#xff0c;配置的时候要花一些时间&#xff0c;如果你的网不好还时不时给你来个timeout。最近在Youtube上…

java 创建mysql表_MySQL 创建数据表

MySQL 创建数据表创建MySQL数据表需要以下信息&#xff1a;表名表字段名定义每个表字段语法以下为创建MySQL数据表的SQL通用语法&#xff1a;CREATE TABLE table_name (column_name column_type);以下例子中我们将在 W3CSCHOOL 数据库中创建数据表w3cschool_tbl&#xff1a;tut…

python内置对象是什么_#【Python】【基础知识】【内置对象常用方法】

数字的常用方法&#xff1a;>>> dir(int)[‘__abs__‘, ‘__add__‘, ‘__and__‘, ‘__bool__‘, ‘__ceil__‘, ‘__class__‘, ‘__delattr__‘, ‘__dir__‘, ‘__divmod__‘, ‘__doc__‘, ‘__eq__‘, ‘__float__‘, ‘__floor__‘, ‘__floordiv__‘, ‘__for…

模为2的逆元是什么_两种求模m逆元的方法

在a|b(a能整除b)的前提下&#xff0c;计算(b/a)mod m的时候转化为 计算(b*x)mod m ; 这时的x就是a的逆元(a模m的逆元)&#xff1b;此时x满足 (a*x mod m 1)&#xff1b; 这个x的求法有一下两种&#xff1a;1)扩展欧几里得算法求解 a*xm*y1; 因为 a*x mod m 1 <> a…

java值栈_Struts2 中的值栈是什么?

7.1值栈7.1.1值栈是什么简单的说&#xff1a;值栈是对应每一个请求对象的轻量级的内存数据中心。Struts2中一个很激动人心的特性就是引入了值栈&#xff0c;在这里统一管理着数据&#xff0c;供Action、Result、Interceptor等Struts2的其他部分使用&#xff0c;这样一来&#x…