Android OpenGL学习笔记

Posted nullZgy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android OpenGL学习笔记相关的知识,希望对你有一定的参考价值。

Demo效果:

效果图

最近做了个贴纸相机的项目,用到OPenGL渲染,一路下来,遇到许许多多的问题,现在写个文章,当做笔记。

OpenGL基础简介

CPU :  中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能.CPU将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的纹理)。

GPU: 一个类似于CPU的专门用来处理Graphics的处理器, 作用用来帮助加快格栅化操作,当然,也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。

格栅化 :  是 将图片等矢量资源,转化为一格格像素点的像素图,显示到屏幕上。

渲染机制分析渲染流程简介

     UI对象—->CPU处理为多维图形,纹理 —–通过OpeGL ES接口调用GPU—-> GPU对图进行光栅化(Frame Rate ) —->硬件时钟(Refresh Rate)—-垂直同步—->投射到屏幕

      android系统每隔16ms发出VSYNC信号(1000ms/60=16.66ms),触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。

     正常情况下Android的GPU会在16ms完成页面的绘制,如果一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件 等待GPU完成栅格化渲染操作,然后再次绘制界面,这样就会看起来画面停顿。当GPU渲染速度过慢,就会导致如下情况,某些帧显示的画面内容就会与上一帧的画面相同。

GPU 渲染效率比 CPU 高

     这点是  GPU、CPU 自身架构决定的,CPU 计算单元少,逻辑控制单元多,为的是执行维度、类型复杂的各种任务,毕竟 CPU 是中枢嘛~。GPU 计算单元多,逻辑控制单元少,专注于图形绘制、渲染, ALU 是计算单元,剩下的不用说大家也能猜到,所以但是把绘图、渲染任务从 CPU 切到 GPU 都是性能上的巨大提升.

OpenGl详解

      图形渲染管线包含很多部分,每个部分都将在转换顶点数据到最终像素这一过程中处理各自特定的阶段。我们会概括性地解释一下渲染管线的每个部分,让你对图形渲染管线的工作方式有个大概了解。

    首先,我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data);

       顶点数据是一系列顶点的集合。一个顶点(Vertex)是一个3D坐标的数据的集合。而顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据,但是简单起见,我们还是假定每个顶点只由一个3D位置(译注1)和一些颜色值组成的。

 顶点着色器  --> 它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。

attribute vec4 vPosition; //变量 float[4]  一个顶点  java传过来的

attribute vec2 vCoord;  //纹理坐标

varying vec2 aCoord;

void main(){
    //内置变量: 把坐标点赋值给gl_position 就Ok了。
    gl_Position = vPosition;
    aCoord = vCoord;
}

 片段着色器 ---> 片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

precision mediump float; // 数据精度
varying vec2 aCoord;

uniform sampler2D  vTexture;  // samplerExternalOES: 图片, 采样器

void main(){
    //  texture2D: vTexture采样器,采样  aCoord 这个像素点的RGBA值
    vec4 rgba = texture2D(vTexture,aCoord);  //rgba
//    gl_FragColor = vec4(1.-rgba.r,1.-rgba.g,1.-rgba.b,rgba.a);
    gl_FragColor = rgba;
}

| 内置变量      | 含义                               | 值数据类型 |
| ------------- | ---------------------------------- | ---------- |
| gl_PointSize    | 点渲染模式,方形点区域渲染像素大小 | float      |
| gl_Position      | 顶点位置坐标                                          | vec4     |
| gl_FragColor   | 片元颜色值                                             | vec4      |
| gl_FragCoord  | 片元坐标,单位像素                               | vec2     |
| gl_PointCoord | 点渲染模式对应点像素坐标                    | vec2      |

gl_Position

   `gl_Position`内置变量主要和顶点相关,出现的位置是顶点着色器语言的`main`函数中。  `gl_Position`内置变量表示最终传入片元着色器片元化要使用的顶点位置坐标。

   如果只有一个顶点,直接在给顶点着色器中设置内置变量`gl_Position`赋值就可以,内置变量`gl_Position`的值是四维向量`vec4(x,y,z,1.0)`,前三个参数表示顶点的xyz坐标值,第四个参数是浮点数`1.0`。

void main() {
  //顶点位置,位于坐标原点
  gl_Position = vec4(0.0,0.0,0.0,1.0);
}

   如果你想完全理解内置变量`gl_Position`,必须建立`逐顶点`的概念,如果javascript语言中出现一个变量赋值,你可以理解为仅仅执行一次,但是对于着色器中不能直接这么理解,如果有多个顶点,你可以理解为每个顶点都要执行一遍顶点着色器主函数`main`中的程序。

  多个顶点的时候,内置变量`gl_Position`对应的值是`attribute`关键字声明的顶点位置坐标变量`apos`,顶点位置坐标变量`apos`变量对应了javascript代码中多个顶点位置数据。


<script id="vertexShader" type="x-shader/x-vertex">
  //attribute声明vec4类型变量apos
  attribute vec4 apos;
  void main() {
    //顶点坐标apos赋值给内置变量gl_Position   //逐顶点处理数据
    gl_Position = apos;
  }

<!-- 顶点着色器源码 -->
  //attribute声明vec4类型变量apos
  attribute vec4 apos;
  void main() {
    //创建平移矩阵(沿x轴平移-0.4)
    //1   0   0  -0.4
    //0   1   0    0
    //0   0   1    0
    //0   0   0    1
    mat4 m4 = mat4(1,0,0,0,  0,1,0,0,  0,0,1,0,  -0.4,0,0,1);
    //平移矩阵m4左乘顶点坐标(vec4类型数据可以理解为线性代数中的nx1矩阵,即列向量)
    // 逐顶点进行矩阵变换
    gl_Position = m4*apos;
  }

gl_Position的顶点数据传递

 //顶点着色器源码
    var vertexShaderSource = document.getElementById( 'vertexShader' ).innerText;
    //片元着色器源码
    var fragShaderSource = document.getElementById( 'fragmentShader' ).innerText;
    //初始化着色器
    var program = initShader(gl,vertexShaderSource,fragShaderSource);
    //获取顶点着色器的位置变量apos,即aposLocation指向apos变量。
    var aposLocation = gl.getAttribLocation(program,'apos');

    //类型数组构造函数Float32Array创建顶点数组
    var data=new Float32Array([0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5]);

    //创建缓冲区对象
    var buffer=gl.createBuffer();
    //绑定缓冲区对象,激活buffer
    gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
    //顶点数组data数据传入缓冲区
    gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
    //缓冲区中的数据按照一定的规律传递给位置变量apos
    gl.vertexAttribPointer(aposLocation,2,gl.FLOAT,false,0,0);
    //允许数据传递
    gl.enableVertexAttribArray(aposLocation);
...

gl_FragColor

   `gl_FragColor`内置变量主要用来设置片元像素的颜色,出现的位置是片元着色器语言的`main`函数中。

   内置变量`gl_Position`的值是四维向量`vec4(r,g,b,a)`,前三个参数表示片元像素颜色值RGB,第四个参数是片元像素透明度A,`1.0`表示不透明,`0.0`表示完全透明。

// 片元颜色设置为红色
gl_FragColor = vec4(1.0,0.0,0.0,1.0);

理解内置变量`gl_Position`需要建立`逐顶点`的概念,对于内置变量`gl_FragColor`而言,需要建立`逐片元`的概念。顶点经过片元着色器片元化以后,
得到一个个片元,或者说像素点,然后通过内置变量`gl_FragColor`给每一个片元设置颜色值,所有片元可以使用同一个颜色值,也可能不是同一个颜色值,
可以通过特定算法计算或者纹理像素采样。

根据位置设置渐变色

void main() {
    // 片元沿着x方向渐变
    gl_FragColor = vec4(gl_FragCoord.x/500.0*1.0,1.0,0.0,1.0);
  }

纹理采样

// 接收插值后的纹理坐标
varying vec2 v_TexCoord;
// 纹理图片像素数据
uniform sampler2D u_Sampler;
void main() {
  // 采集纹素,逐片元赋值像素值
  gl_FragColor = texture2D(u_Sampler,v_TexCoord);
}

片元坐标`gl_FragCoord`

   内置变量`gl_FragCoord`表示WebGL在canvas画布上渲染的所有片元或者说像素的坐标,坐标原点是canvas画布的左上角,x轴水平向右,y竖直向下,
`gl_FragCoord`坐标的单位是像素,`gl_FragCoord`的值是`vec2(x,y)`,通过`gl_FragCoord.x`、`gl_FragCoord.y`方式可以分别访问片元坐标的纵横坐标。

<!-- 片元着色器源码 -->
  void main() {
    // 根据片元的x坐标,来设置片元的像素值
    if(gl_FragCoord.x < 300.0){
      // canvas画布上[0,300)之间片元像素值设置
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }else if (gl_FragCoord.x <= 400.0) {
      // canvas画布上(300,400]之间片元像素值设置
      gl_FragColor = vec4(0.0,1.0,0.0,1.0);
    }else {
      // canvas画布上(400,500]之间片元像素值设置
      gl_FragColor = vec4(0.0,0.0,1.0,1.0);
    }    
    // 所有片元设置为红色
    // gl_FragColor = vec4(1.0,0.0,0.0,1.0);
  }

下面用一个Demo来详细的记录下。代码结构如下

Demo地址 https://download.csdn.net/download/u011694328/25358718

以上是关于Android OpenGL学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

Android OpenGL学习笔记

Android OpenGL学习笔记

Android OpenGL学习笔记

视频学习笔记:Android OpenGL渲染YUV420P图像

OpenGL ES for Android 笔记

OpenGL ES for Android 笔记