美文网首页
OpenGL学习资料和记录

OpenGL学习资料和记录

作者: hjm1fb | 来源:发表于2019-06-11 20:50 被阅读0次

    学习资料

    OpenGL:
    3D图形学:
    OpenGL ES:
    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

    • glDeleteTextures函数对内存的优化

    • 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)

    调试:

    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

    GAPID_配置示例.png

    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

    小米手机获取完整ROOT权限教程

    Kodelife的实用安装指南

    Shader:

    优秀的Shader教程推荐
    ShaderToy
    边栏的小雨伞
    shadertoy网站上的一些效果
    KodeLife | Shader 实时编辑预览的强大工具使用实践
    the Book of Shaders
    Shader大神iq的教学网站

    ShaderToy使用自定义视频纹理

    • 性能相关

      1. 复杂运算,如果能在c++代码里方便计算出来,就移到C++代码里计算,然后通过uniform传给shader
      2. 锐化/双边滤波等效果,可能会用到固定长度的坐标数组,这样的数组可以在顶点着色器里计算,然后通过varying参数传到Fragment着色器。因为一般情况下,顶点着色器执行的次数远远少片段着色器,所以这样做能节省性能
      3. 二维模糊卷积核请用先x后y,二次渲染的方式:Android图像处理 - 高斯模糊的原理及实现
      4. 计算量较多的fragment shader, 在不影响效果的情况下,尽量用mediump精度,以提高性能;顶点着色器一般用highp精度
    • 兼容性相关
      如果顶点着色器和片段着色器都用到了同一个uniform值,请不要同时在顶点着色器和片段着色器同时声明一样的uniform值。这样做可能会有兼容性问题。正确的做法是在顶点着色器再声明一个varying值,通过varying参数把值传递给片段着色器

    粒子动画(点精灵):

    OpenGL 点精灵效果
    OPenGL点精灵
    粒子
    StarWars.Android 界面粉碎效果中的openGL操作解析
    opengles绘制点精灵
    smartGL

    gl_PointCoord.png
    (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的绘制上下

    • 兼容性

    1. 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 兼容性计划概览

    所以可以认为,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

    1. 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.
    2. 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里的矩阵乘法
    1. vector在左边,右边乘以Martix是把vector当做行向量,运算结果与vector在右边,左边乘以Martix的转置矩阵相同GLSL里的矩阵乘法

    2. 已经GLSL里的Martix是以列主序存储,所以vector在左边,右边乘以Martix的好处是提高运算效率:矩阵:行主序、列主序、行向量、列向量

    3. 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.)
    
    1. 举例:
    // 第二行是vector在左边,右边乘以Martix,所以要转置旋转矩阵
    mat2 rmat = mat2(cos(angle),-sin(angle),sin(angle),cos(angle)); 
    vec2 sampleCoord = vec2(u,v) * rmat;
    
    1. 矩阵顺序与执行顺序的关系:
      当前矩阵为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的经验
    1. 用Renderdoc设置连抓10张,有助于提高抓取成功率。
    2. 抓shader的时候,如果抓不到:
      a. 图片视频编辑页面抓不到,也可以去相机实时预览页面试试
      b. 可以把编辑结果放到草稿箱,然后设置连抓10帧,然后点击进入草稿箱,在APP应用草稿结果的时候,可能会抓到shader。
      c. 有时候RenderDoc和Gapid抓不到的shader, SnapDragon能抓到。SnapDragon里要一行一行看执行命令来推测渲染流程,稍微麻烦一点,但uniform和纹理等资源都是可以抓到的
      d. 如果是一张纹理图抓不到,可以试试Gapid, 因为Gapid的纹理列表里有整个OpenGL环境里的所有纹理,甚至是系统相册里的图片纹理。所以颜色查找表之类的纹理抓不到,可以试试去Gapid的纹理列表里找找。

    噪声性能比较:


    8-Table2-1.png

    相关文章

      网友评论

          本文标题:OpenGL学习资料和记录

          本文链接:https://www.haomeiwen.com/subject/gbmcfctx.html