Android opengl es 3.0 + ndk 绘画涂鸦项目

您所在的位置:网站首页 opengl画板制作 Android opengl es 3.0 + ndk 绘画涂鸦项目

Android opengl es 3.0 + ndk 绘画涂鸦项目

2024-07-12 05:36| 来源: 网络整理| 查看: 265

在这里插入图片描述

前言

写一个opengl es 3.0 + ndk 的绘画涂鸦项目,命名为白板哈哈哈,记录自己遇到的问题,顺便学到的知识整合一遍,算是对自己一段时间的总结。

项目地址:Whiteboard

如果对你有帮助,不妨点个start支持一下。感谢

效果图

在这里插入图片描述

调研如何绘制,具体思路?

主要调研的结果是方式有两种,一种是使用原生的 api 线条+drawPath 绘制贝塞尔曲线 而笔触纹理 可以通过 Canvas 生成圆点bitmap。

输出可以使用两个bitmap作为交换显示,形成一个双缓冲机制。

我之前写了一个具体的demo可以看看 https://github.com/laishujie/MyPaint

另一个就是今天的主角Opengl的方式实现,我们知道opengl可以绘制许多图元形状,三角(Triangle),线段(Line)等,其中有一个就是点(Point)。

在顶点着色器可以gl_PointSize设置点的大小

//顶点着色器 void main() { gl_Position = vec4(aPos, 1.0); gl_PointSize = posiSize; }

输出的话就这样,大概这样子 在这里插入图片描述

是一个正方形的形状,可以通过点贴上纹理,则需要使用内置变量gl_PointCoord来查询纹理中的纹素,使其填充,形成笔触, 它有另一名称叫做 ”点精灵“。

//片元着色器 #version 300 es uniform sampler2D sprite_texture; out vec4 FragColor; void main() { FragColor = texture(sprite_texture, gl_PointCoord); };

总得来讲就是Opengl 绘制点图元,点成线,笔触通过设置不同的纹理实现。

输出屏幕,显示设计

刚刚讲了原生输出方法可以通过bitmap双缓冲的机制显示。而opengl呢,默认的只有一个屏幕缓冲,它也是个双缓冲,一般的话都是通过

xxxSwapBuffers 交换缓冲区,显示到屏幕。 但我们有背景层+画笔层。所以可以通过FBO(帧缓冲)为每层分配一个缓冲区。

简单快速的解释下FBO,你用了这个FBO也就是

glBindFramebuffer(GL_FRAMEBUFFER, fbo);

接下来的所有的读取和写入帧缓冲的操作,也就是opengl 绘制啊读取啥的,都会作用于当前绑定的帧缓冲,也叫离屏缓冲。

所以思路是这样的背景层+画笔层各一个缓冲区,最后在默认缓冲区合成图像

在这里插入图片描述

这里还涉及到一些混合的知识,跟看下面的橡皮檫内容一起说了,由于从FBO拿出来的纹理id是颠倒的,输出的时候要注意一下

画笔属性设置 大小

gl_PointSize 顶点着色器设置就好

颜色

通过片段着色器输出改变,看你的笔刷纹理图是什么颜色,目前的话一半的笔刷图有两种,一种是白色一种是黑色的纹理。

类似这样纹理图片ing

正片叠底说白了就是相乘,也就是两个颜色的每个通道相乘。

outColor=a*b

而滤色呢就是两个颜色都反相,相乘,然后再反相

outColor = 1 - (1-a)*(1-b)

所以他们都有这个特性看图把

在这里插入图片描述

原图素材来自于 https://www.bilibili.com/video/BV1jU4y1s7kt

想要深入了解的也可以看看这个视频

正片叠底方式

//片段着色器 #version 300 es precision highp float; uniform vec4 outColor; out vec4 fragColor; uniform sampler2D Texture; float aTransparent; void main (void) { vec4 mask = texture(Texture, vec2(gl_PointCoord.x, gl_PointCoord.y)); fragColor = aTransparent * vec4(outColor.rgb, 1.0) * mask; }

但大多数黑色的纹理,采用的是滤色的方式改变

//片段着色器 #version 300 es precision mediump float; out vec4 fragColor; uniform sampler2D textureMap; uniform vec4 outColor; float outColorTransparent; float aTransparent; void main() { vec4 mask = vec4(0.); mask = texture(textureMap, vec2(gl_PointCoord.x,gl_PointCoord.y)); outColorTransparent = outColor.a; vec3 aTransparentColor=vec3(0.); //如果当前纹理像素带透明度 if(mask.aoutType == BrushInfo::ERASER) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA); //绘制模式的混合 else glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

关于图层合并输出的混合模式也是

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

如果想在线预览效果,或者查看每个混合的效果 可以到这个网站看看

Visual glBlendFunc + glBlendEquation Tool

点的生成,笔触

点成线,快速的在屏幕移动,可下发的点是有限的,也就是会得到几个零散的点,要做的是要进行一定程度的密度插值,以及曲线的生成,我这里是采取了这个博主所使用的算法。

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

搬运过来发现还有些问题,没时间研究,等日后有空在重新写过这个算法吧,感兴趣的具体可以去看看

笔触的话,我这边是直接拿画吧的资源,发现有些笔触需要每个点需要进行不同方向的旋转绘制,比如说这个蜡笔

问题是如何给每个点进行旋转?我们绘制的时候会传入一批数据,如果外面传入旋转矩阵进行赋值,那么这一批数据都会一个旋转方向进行旋转,不符合要求,但也种不能一个点一个点的画吧。

所以根据调研的结果是,假如有一批顶点数据进来,那么每个顶点都会经过这个顶点着色器。执行的次数是这样的

如果这次绘制需要传入4个顶点,假如形成一个宽高为100的正方形,那顶点着色器只需要计算4次,而片元着色器需要执行100x100=10000次

那最终的方案是 在顶点着色器算出旋转矩阵传给到片段着色器即可达到每个点精灵都有不同的旋转方向

我们把之前的颜色大小跟旋转结合起来,片段着色器,和顶点着色器是这样的

//顶点 #version 300 es layout(location = 0) in vec4 vPosition; uniform float brushSize; float timeStamp; //旋转矩阵 out mat2 rotation_matrix; //生成每次随机的变量 float random(float val) { return fract(sin(val * 12.9898) * 43756.5453123 ); } void main() { timeStamp = random((vPosition.x+vPosition.y)) * 100.0; float sin_theta = sin(timeStamp); float cos_theta = cos(timeStamp); rotation_matrix = mat2(cos_theta, sin_theta, -sin_theta, cos_theta); gl_Position = vPosition; gl_PointSize = brushSize; }

片段

//片段着色器 #version 300 es precision mediump float; out vec4 fragColor; uniform sampler2D textureMap; uniform vec4 outColor; //需要输出的透明度 float outColorTransparent; //当前纹理透明度 float aTransparent; //接受顶点过来的旋转矩阵 in mat2 rotation_matrix; //是否支持旋转 uniform float isSupportRotate; void main() { vec4 mask = vec4(0.); //为0代表不需要参与旋转 if(isSupportRotate==0.0){ mask = texture(textureMap, vec2(gl_PointCoord.x,gl_PointCoord.y)); }else{ //平移中心后在旋转 vec2 pt = gl_PointCoord - vec2(0.5); mask = texture(textureMap, rotation_matrix * pt + vec2(0.5)); } outColorTransparent = outColor.a; vec3 aTransparentColor=vec3(0.); //如果当前纹理像素带透明度 if(mask.a


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3