学习资料
OpenGL:
- LearnOpenGL
- 计算机图形/图像(GPU/OpenGL/OpenCV)书籍收藏
- GPU 加速下的图像处理
- GPUImageGroup
- OpenGL Reference Pages
- Rendering_Pipeline_Overview OpenGL渲染管线
- opengl-es-sdk-for-android
- rastergrid
- GL命令的版本差异检索
3D图形学:
OpenGL ES:
- Android GPUImage
- OpenGL ES 和Canvas的性能比较:
Android: Canvas vs OpenGL
为什么你的canvas那么慢?浅析Android的canvas性能
图形 | Android Open Source - GLSurfaceView与TextureView的区别:
浅谈 SurfaceView、TextureView、GLSurfaceView、SurfaceTexture
android: View, SurfaceView, GLSurfaceView, TextureView 区别与联系(文章内“android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次”这句话不认同)
OpenGL 内存/性能优化:
VAO 和VBO的使用流程区别
OpenGLES顶点缓冲
android平台下OpenGL ES 3.0实例详解顶点缓冲区对象(VBO)和顶点数组对象(VAO)
VAO在OpenGL3.0上才能用。在OpenGL2.0上,使用VBO的步骤大致如下:
1. GenBuffer (called once in the whole rendering lifecycle)
2. glUseProgram(m_ProgId); (called on every draw for this one and the following commands)
3. BindBuffer to type GL_ARRAY_BUFFER
4. glBufferData or glBufferSubData() (called only when data has changed)
5. EnableVertexAttribArray
6. glVertexAttribPointer
6. glBindBuffer to 0
7. glDrawXX
8. glDisableVertexAttribArray // 在iOS低端机上频繁调用会导致绘制无效,可以不调用或者在OpenGL环境销毁时调用
9. glUseProgram(0)
10. glDeleteBuffers (called when the data is no longer used, ie. quit the Activity)
使用两个或以上的Buffer的时候注意: 调用glBufferData 和 glVertexAttribPointer前都要 BindBuffer 到对应的Buffer上. ( 比如顶点信息和参数信息分别使用 GLuint attribPositionBuffer 和 GLuint attribParamBuffer)
在做AE插件开发的时候,发现必须VBO结合VAO, 才能画粒子,并且要设置glEnable(GL_PROGRAM_POINT_SIZE);
VAO的使用示例:
1. GenBuffer (called once in the whole rendering lifecycle)
2. glUseProgram(m_ProgId); (called on every draw for this one and the following commands)
3. BindBuffer to type GL_ARRAY_BUFFER
4. glBufferData or glBufferSubData() (called only when data has changed)
5. EnableVertexAttribArray
6. glVertexAttribPointer
6. glBindBuffer to 0
7. glDrawXX
8. glDisableVertexAttribArray // 在iOS低端机上频繁调用会导致绘制无效,可以不调用或者在OpenGL环境销毁时调用
9. glUseProgram(0)
10. glDeleteBuffers (called when the data is no longer used, ie. quit the Activity)
#=========================配置VAO阶段===============================
unsigned int VBO[2], VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(2, VBO);
#=========================绑定VAO===============================
glBindVertexArray(VAO);
#=======================绑定第一个VBO============================
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
#===============================================================
glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(float), &sphereVertices[0], GL_STATIC_DRAW); //向第一个VBO中写入数据
#================告知VAO该如何解释第一个VBO的信息=================
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
#=======================解绑第一个VBO===========================
glBindBuffer(GL_ARRAY_BUFFER, 0);
#===============================================================
#=======================绑定第二个VBO============================
glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
#===============================================================
glBufferData(GL_ARRAY_BUFFER, sizeof(texVertrices), texVertrices, GL_STATIC_DRAW);//向第二个VBO中写入数据
#================告知VAO该如何解释第二个VBO的信息=================
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
#=======================解绑第二个VBO===========================
glBindBuffer(GL_ARRAY_BUFFER, 0);
#================解绑VAO=================
glBindVertexArray(0);
..............
#================绘制阶段使用VAO=================
glBindVertexArray(mVAO);
glDrawArrays(GL_POINTS, 0, mCount);
glBindVertexArray(0);
# 如果是一个VAO 对应一个VBO, 但VBO里包含两个属性,则VAO解释VBO的两行改成如下形式:
// Position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// Color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
用Direct Textures或PBO提高glReadPixels、glTexImage2D性能
Android 关于美颜/滤镜 利用PBO从OpenGL录制视频
如果想在不同的渲染图层或者渲染步骤复用glReadPixels的结果,可以把对应的计算结果存储到纹理的RGB值上(单独一个图层),此纹理就可以作为资源Map引用共享给同一GLContext的不同对象使用了。比如R和G通道存储Mask图glReadPixels处理后得到的目标方框->变换矩阵对应的每个像素的x/y目标坐标。即坐标纹理。
这里要注意两点,1是如果用Alpha存储数据,要考虑Alpha预乘,2是在后面读取此坐标纹理上的值时,可能有精度损失,
引起锯齿。一个解决方案是把坐标纹理用小尺寸再画一遍(比如16 * 16),然后再使用时,因为纹理过滤模式设置为双线性插值,每个像素原来的采样误差,会被双线性插值平滑,就没有锯齿了,只有很轻微的整体位置偏移
GPUImage(OpenGL ES)的性能优化、爬坑与架构改善(原文已删,我在Github私人仓库有备份)
gldrawelements比gldrawarrays更节省GPU消耗(vertex reuse, flexibility)
着色器里不需要精确的数据降低精度; 不需要3D绘制时vTextureCoord可以定义成二维;计算尽量在顶点着色器而不是片段着色器。
Overcome Incoherent Memory Access in OpenGL ES 3.0
-
OpenGL快问快答
问答里有“OpenGL有内存泄漏吗?”
如果一直不停创建texture,还是会使GPU增长。所以还是及时把不用的纹理删除比较好,或者选择更新纹理而不是创建新的纹理。然后GPU有自己的纹理复用回收机制,所以调用了glDeleteTextures方法,并不会马上释放纹理。 -
一般不要在Shader里加if else判断,因为会影响性能,即使要加,也有替代方法:
如何在shader中避免使用if else
- Gamma 校正
对 Gamma 校正的个人理解
颜色空间——Gamma与线性颜色空间
基于gamma2.2的颜色空间叫做sRGB颜色。也就是sRGB里的颜色值是真实的线性颜色空间里的值经过如下gamma矫正后的
image
线性空间与GAMMA校正
sRGB的话,保存的色值是偏亮的。
只有OpenGL ES3.0才开始支持配置sRGB到线性空间的自动转换。不不配置的话,sRGB格式(照片和图片软件导出的图片一般都是sRGB格式)的texture,在texture2D后获取的是保存的原始的值,即是偏亮的RGB值。
在做亮度变换/色调分离等功能时,往往也会模拟Gamma矫正以有更符合预期的人眼感知效果:
posterize.frag(此Shader应该是预设输入是线性颜色空间的Texture)
调试:
-
查看gpu的情况:
高通骁龙GPU, 可以尝试下载APK:Trepn Profiler(有些机型比如三星手机SM-A9100支持GPU使用率检测)
Trepn Profiler使用参考:
FAQ
GPU Load Support
更详细信息:https://www.jianshu.com/p/1ec4f29f190f
https://zhuanlan.zhihu.com/p/70780719 -
调试Shader
VS Code的GLSL调试工具 shadered
实时调试渲染shader工具glslViewer
非常方便的 VSCODE 的 SHADER 插件 —— SHADER TOY
thebookofshaders编辑器
实时调试渲染shader工具glslViewer
Shdr Editor Shader在线语法监测
- 抓取APP上的shader
一般都需要root手机或者debug版本的APP
Graphic Debugger工具大杂烩,你要的都在这
SnapdragonProfiler抓取游戏纹理和shader
PC Profile工具-Nsight/GPA入门
GAPID
安卓图形调试利器-GAPID
GAPID Graphics Debugger (Android Game Developer Summit 2018)
如何快速定位Android端GPU问题之工具介绍
推荐一款强大的 Android OpenGL ES 调试工具
Android Studioh 和GAPID是不能同时用的,不然在GAPID中会因为都要占用adb而找不到device。可以打开资源管理器,确认没有adb的服务后再启动GAPID软件。
如果没有自动安装gapid-arm64-v8a.apk/gapid-armeabi-v7a.apk, 那么需要手动安装。
调试release版本的应用的话,需要root过的手机。
自行排查未检测到设备的原因(可以尝试重启,然后运行adb root, 再运行adb remount):
查看log文件: Couldn't get framebuffer attachment
RenderDoc
GPU分析工具RenderDoc使用 (作者不建议抓其他家应用的shader,但还是可以抓的。然后截止2020.07.07还没有Mac平台的稳定版本的安装包。
RenderDoc is only intended for capturing your own programs. Capturing copyrighted programs that you do not have the rights to is not endorsed and I will provide absolutely no support for it.)
使用后更新:RenderDoc的确很好用,抓取的数据非常全。但调试的条件是Root机或APP为debug模式。
鉴于逆向第三方应用越来越难,所以推荐用小米Root机来调试。不过我试了三台不同型号的小米Root机,只有小米SE 8是连接稳定的。其余两台连接不稳定,无法工作。
Command-line tool for converting RenderDoc CSV export to .OBJ
Shader:
优秀的Shader教程推荐
ShaderToy
边栏的小雨伞
shadertoy网站上的一些效果
KodeLife | Shader 实时编辑预览的强大工具使用实践
the Book of Shaders
Shader大神iq的教学网站
-
性能相关
- 复杂运算,如果能在c++代码里方便计算出来,就移到C++代码里计算,然后通过uniform传给shader
- 锐化/双边滤波等效果,可能会用到固定长度的坐标数组,这样的数组可以在顶点着色器里计算,然后通过varying参数传到Fragment着色器。因为一般情况下,顶点着色器执行的次数远远少片段着色器,所以这样做能节省性能
- 二维模糊卷积核请用先x后y,二次渲染的方式:Android图像处理 - 高斯模糊的原理及实现
- 计算量较多的fragment shader, 在不影响效果的情况下,尽量用mediump精度,以提高性能;顶点着色器一般用highp精度
-
兼容性相关
如果顶点着色器和片段着色器都用到了同一个uniform值,请不要同时在顶点着色器和片段着色器同时声明一样的uniform值。这样做可能会有兼容性问题。正确的做法是在顶点着色器再声明一个varying值,通过varying参数把值传递给片段着色器
粒子动画(点精灵):
OpenGL 点精灵效果
OPenGL点精灵
粒子
StarWars.Android 界面粉碎效果中的openGL操作解析
opengles绘制点精灵
smartGL
(OpenGL标准纹理坐标的原点在左下角,但Android纹理坐标的原点在左上角,所以gl_fragcoord的原点也在左上角了)
对照:
OpenGL世界坐标系&Android纹理坐标系.png
(OpenGL标准纹理坐标的原点在左下角,但Android纹理坐标的原点在左上角)
update:其实是数据的原因,Android的Bitmap的坐标原点是左上角,Android端的OpenGL ES的glReadPixels等接口也没有对此适配,所以读入的图像数据就是颠倒的了,需要纹理坐标y轴颠倒一下来负负得正。所以理解为“Android纹理坐标的原点在左上角”,其实不准确。只是说上下颠倒坐标系,就刚好把数据又转正了。也就是只需要在bitmap 作为纹理输入的时候,需要手动上下颠倒一下,后面就按正常的OpenGL纹理处理就可以了
OpenGL ES 3.0 新增的实例渲染接口,可以增强粒子动画的表现能力,以及优化传输和绘制的性能:
glVertexAttribDivisor
glDrawArraysInstanced
glBeginTransformFeedback
待学习的粒子效果:
unity-optical-flow
PixelFlow
-
多线程与资源共享
存在多个OpenGL上下文时,纹理、shader、Buffer(VBO)等资源(包含数据,存储在可共享访问的内存区域内)是可以设置为OpenGL线程间共享,但Frame Buffer Object(FBO)、Vertex Array Object(VAO)等container objects(container objects 主要是用于存放regular objects,以及用于组织regular objects的额外信息)不可共享
Understanding OpenGL Objects
关于OpenGL的绘制上下 -
兼容性
- NPOT纹理与平铺模式
OpenGL规范从2.0开始支持显示边长为非2次幂的Texture,但限制条件是需要环绕模式为CLAMP_TO_EDGE并且过滤模式为NEAREST或者LINEAR。
解除限制的条件是硬件支持OES_texture_npot的扩展。
获取硬件扩展列表的代码如下:
std::string GetGLString(unsigned int pname) {
const char* gl_string =
reinterpret_cast<const char*>(glGetString(pname));
if (gl_string)
return std::string(gl_string);
return "";
}
...
const char* gl_extensions = GetGLString(GL_EXTENSIONS).c_str();
ULOGE("gl_extensions %s", gl_extensions);
//看打印的字符串里是否包含OES_texture_npot
OpenGL 2.0规范里有这个限制,是因为NPOT纹理设置REPEAT等模式会造成在图像连接处有接缝:
Seamless tilemap rendering (borderless adjacent images)
OpenGL ES 3.0 has full NPOT support in core; ES 2.0 has limited NPOT support (no mipmaps, no Repeat wrap mode) in core; and ES 1.1 has no NPOT support.
OpenGL对NPOT纹理的支持情况:
For ES 1.1 and 2.0, full NPOT support comes with GL_ARB_texture_non_power_of_two orGL_OES_texture_npot extension. In practice, iOS devices don’t support this; and on Android side there’s support on Qualcomm Adreno and ARM Mali. Possibly some others.
For ES 1.1, limited NPOT support comes with GL_APPLE_texture_2D_limited_npot (all iOS devices) or GL_IMG_texture_npot (some ImgTec Android devices I guess).
而谷歌从Android4.3开始强制要求设备支持OpenGL3.0:Android 4.3兼容性定义的“本机API兼容性”一栏
从5.0开始设备必须支持OpenGL3.1
所以可以认为,Android 4.3以上的设备以及iOS 7.0(iPhone系列 5S)以上的设备,启用OpenGL3.0后,支持对NPOT纹理应用GL_REPEAT等功能。而Android的4.3以前的设备,如果硬件支持OES_texture_npot扩展,那么也同样支持。
但这个只是理论上,Android的兼容性问题众所周知。
iOS升级3.0的文档:Adopting OpenGL ES 3.0
参考:
NPOT texture in iOS can't be repeat mode!
PowerVR Supported Extensions OpenGL ES and EGL
update:
实践的结论是,用shader在OpenGL2.0上实现NPOT的GL_REPEAT模式,预览效果也还好。在几台Android和iOS设备上测试,连接处的缝隙看不出来。
所以OpenGL2.0不支持的原因,我猜是在mipmap的场景下可能有问题。
记录
精度修饰符
OpenGL精度修饰符顶点着色器默认精度为highp.
片元着色器没有默认精度, 所以需要专门指定默认精度或者给每个数值类型变量指定精度.
半精度浮点数Half
由于一个浮点型数在计算机内由符号位/指数位/尾数表示,如
1000.1 =1.0001 * 2的3次
1110110.1 = 1.1101101 * 2的6次
所以数值越大,float的精度误差也随着指数的增加而增加
有效值
glGetXXLocation(比如glGetUniformLocation/glGetAttribLocation) 有效值为>=0,失败会返回-1;
GenXX(比如glGenBuffers) 有效值为>0,失败name会是0;
纹理 Texture ID大于0 等于0表示此纹理不合法;
MEDIUMP_FLT_MAX 65504.0
MEDIUMP_FLT_MIN 0.00006103515625
MEDIUMP小数点最大误差为0.048%,平均误差0.018%
其他记录
- 内存限制:
设备能使用的varying vec4数组的个数就是GL_MAX_VARYING_VECTORS值,最小值为8(2.x)或15(3.x);
对应的有GL_MAX_FRAGMENT_UNIFORM_VECTORS 最小值为16(2.x)或224(3.x);
GL_MAX_VERTEX_UNIFORM_VECTORS 最小值为128(2.x)或256(3.x);
这里的个数值是指所有UNIFORM的单个int/单个float/float向量/float向量数组等的个数,比如顶点着色器里只定义了两个uniform vec4数组, 个数限制128(2.x)或256(3.x)对应的是是两个数组的size的和
But, 在魅族和小米的两台手机上测试发现,虽然GL_MAX_FRAGMENT_UNIFORM_VECTORS的值返回256,但仍可以使用长度为280的vec4数组。。。
但因为四字节对齐的问题,可能这个vec4数组的最大个数也是float数组的最大个数:
GLSL float/vec3/vec4 array max size = GL_MAX_VERTEX_UNIFORM_VECTORS?
因此vec4或者4xn的mat,对内存的利用效率是最高的
OpenGL 3.3 guarantees a minimum uniform size of 1024 bytes 也就是16个uniform mat4
-
Learnopengl上的这句话Creating a renderbuffer object is similar to texture objects, the difference being that this object is specifically designed to be used as an image, instead of a general purpose data buffer like a texture.
我感觉讲反了,经查阅资料,更认同以下说法:
- With the texture object, the content drawn into the texture object can be used as a texture image.The renderbuffer object is a more general-purpose drawing area, allowing a variety of data types to be written.
- Renderbuffer is simply a data storage object containing a single image of a renderable internal format. It is used to store OpenGL logical buffers that do not have corresponding texture format, such as stencil or depth buffer.
- 如果遇到program的draw执行了,但没有效果,可以看看draw之前和draw之后的fbo有没有异常,看
是不是draw到了预期的fbo上,并且也restore到了预期的buffer上。
查看fbo ID的代码:
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_nowFboId);
ULOGD("glGetIntegerv filter before draw: %d", _nowFboId);
MAX_VARYING_VECTORS的解释(WebGL shaders: maximum number of varying variables)
OpenGL 关于深度测试
绘制粒子时往往需要 glDisable(GL_DEPTH_TEST);
- GLSL里的矩阵乘法
-
vector在左边,右边乘以Martix是把vector当做行向量
,运算结果与vector在右边,左边乘以Martix的转置矩阵相同GLSL里的矩阵乘法 -
已经GLSL里的Martix是以列主序存储,所以vector在左边,右边乘以Martix的好处是提高运算效率:矩阵:行主序、列主序、行向量、列向量
-
GLSL里初始化矩阵时,同样以列优先顺序赋值:
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = v * m; // = vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
- 举例:
// 第二行是vector在左边,右边乘以Martix,所以要转置旋转矩阵
mat2 rmat = mat2(cos(angle),-sin(angle),sin(angle),cos(angle));
vec2 sampleCoord = vec2(u,v) * rmat;
- 矩阵顺序与执行顺序的关系:
当前矩阵为A,变换矩阵为B,A执行B变换后新矩阵为AB,和顶点坐标v相乘,从而构成新的顶点坐标ABv。上述过程说明,程序中绘制顶点前的最后一个变换命令最先作用于顶点之上。这同时也说明,OpenGL编程中,实际的变换顺序与指定的顺序是相反的。Shader里顶点坐标v为列向量,如果和顶点坐标相乘的方式为vAB,此时其实相当于把顶点坐标v当做行向量,计算结果与v在右边,左边乘以Martix的转置矩阵相同
- 渲染SDK架构参考
2D: GPUImage C++版 GPUImage
3D:主要是ECS,也有Boost Graph Library结构或者传统按逻辑分: 平台/图形API/核心库
其他:
GamePlay
lottie
Star游戏引擎开发记录
鬼火
orge3d
Filament
Filament解析1·主体逻辑
Filament专题 by Night_Aurora liujing7256
技术文章/渲染引擎-技术文章/filament
数据驱动渲染Data Driven Rendering: Pipelines
- 抓Shader的经验
- 用Renderdoc设置连抓10张,有助于提高抓取成功率。
- 抓shader的时候,如果抓不到:
a. 图片视频编辑页面抓不到,也可以去相机实时预览页面试试
b. 可以把编辑结果放到草稿箱,然后设置连抓10帧,然后点击进入草稿箱,在APP应用草稿结果的时候,可能会抓到shader。
c. 有时候RenderDoc和Gapid抓不到的shader, SnapDragon能抓到。SnapDragon里要一行一行看执行命令来推测渲染流程,稍微麻烦一点,但uniform和纹理等资源都是可以抓到的
d. 如果是一张纹理图抓不到,可以试试Gapid, 因为Gapid的纹理列表里有整个OpenGL环境里的所有纹理,甚至是系统相册里的图片纹理。所以颜色查找表之类的纹理抓不到,可以试试去Gapid的纹理列表里找找。
噪声性能比较:
8-Table2-1.png
网友评论