Android开发经验谈Android技术知识Android开发

OpenGL ES简介: 简述管线绘制流程

2020-05-25  本文已影响0人  unique半山居士

OpenGLES的术语很多,我想通过自己的语言讲的简单一些:(不知道效果怎样,有意见的同学欢迎在评论区指教)

1、确定顶点,纹理坐标:

任何的图形: 点、线、几何图形、空间几何图形,都可以用几个三角形来构成。
三个顶点可以确定一个三角形,所以通过一系列顶点坐标的组合,我们可以绘制出任何图形。


image

纹理简单来说就是一张图片。
纹理坐标就是图片上的点的坐标。
确定纹理坐标是指:顶点对应图片资源上的点的坐标。

2、顶点处理:

例如某个空间图形旋转90°,那旋转前后该空间图形的顶点的空间坐标发生了变化,懒惰的程序员是不会重新列举一遍顶点坐标的,我们会通过一个矩阵和原来的顶点坐标来计算新的顶点坐标。这一步Opengl es 通过顶点着色器来实现。

3、绘制纹理:

你可以把图片想象成块布,纹理坐标就是上面的特定位置加上图钉📌,最后把图钉按到对应的顶点坐标上。
平面上的每一个点都会对应图片上某个点,把对应点的颜色绘制到平面上,整个空间图形就有了色彩。
这一步Opengl es 通过片元着作色器实现。

记住流程,我们一步一步来调用具体api:

OpenGLES api

1、确定顶点坐标,纹理坐标:

2018080414041192.png

Opengl es 坐标系中(0,0)指的是画布的中心点,对于GLSufaceView就是View的中心点。x=1指的是View最右的位置,y=1指的是view最上的位置。(1,1)就是右上角的点。
以绘制一个平面图形为例,一个正方形由两个三角形组成,所以坐标为:

{
  -1, 1//三角形1号
  -1,-1
   1, 1

   1, 1//三角形2号
  -1,-1
   1,-1
}

OpenGL有个更简便的写法:

{
  -1, 1//三角形1号,1号点
  -1,-1//2号点
   1, 1 //3号点
   1,-1//三角形2号 ,4号点
}

方式1:太直观了,6个顶点。
方式2:前三个顶点组成一个三角形,后面每一个顶点代表一个三角形。实际上方式2的顶点数组会由OpenGL转换成方式1。
偶数点三角形的顶点为 [n-1 ,n-2 ,n],奇数点三角形的顶点为 [n-2 ,n-1 ,n]
方式2最后一个顶点是第四个,偶数点,三角形坐标:(1,1),( -1,-1),(1,-1)和方式1一样。

这里必须要注意的是:顶点数组的书写顺序是由要求的
先提个问:一个三角形有几个面?1个?再想想。
两个,正反面,OpenGL中使用顶点顺序来标识正反面。在没有通过api进行设置的情况下,OpenGL认为逆时针的顶点排序为正面,顺时针的顶点排序为反面。对于平面图形来说,渲染到反面的图像是看不见的。
所以注意,顶点的书写顺序为逆时针。

上图可以明显的看到顶点坐标对应的纹理坐标,以方法二为例:

{//顶点坐标
  -1, 1//三角形1号,1号点
  -1,-1//2号点
   1, 1 //3号点
   1,-1//三角形2号 ,4号点
}

{//纹理坐标
 0, 0//三角形1号,1号点
 0,1//2号点
 1, 0 //3号点
 1,1//三角形2号 ,4号点
} 

2、顶点处理:

上面说到OpenGL中这一步通过顶点着色器实现。那么什么是顶点着色器呢?
着色器是运行在OpenGL里通过GLSL(OpenGL Shading Language)语言编写的程序。
顶点着色器 Vertex Shader :则是一个用来操作顶点数据的着色器程序。

//这是一个最普通的顶点着色器
 String vStr =      "attribute vec4 vPosition;" +
                    "attribute vec2 vCoordinate;" +
                    "varying vec2 aCoordinate;" +
                    "uniform mat4 vMatrix;" +
                    "void main() { " +
                    "   gl_Position = vMatrix*vPosition;" +
                    "   aCoordinate=vCoordinate;" +
                    "}";

attribute vec4:attribute声明的变量需要开发者从外部程序输入一个坐标数组,这个坐标数组构成一个区域,而OpenGL会把区域中的一个点用一个多维向量(vec4就是四维(x,y,z,w),vec2就是二维(x,y))赋值给该变量。OpenGL会反复调用程序,对区域内的每一个点进行计算
uniform:声名一个值从程序外传入的变量。
mat4:表示变量是个4x4浮点矩阵,同理mat3——3x3
varying:varying变量是顶点着色器和片元着色器之间做数据传递用的。
gl_Position :顶点着色器的输出,是个vec4变量。其值决定了画笔的位置。

既然着色器是另外一种语言,想在android中运行另一种语言编写的程序,其中必有一番曲折:
1、创建一个空的OpenGLES程序

int mProgram = GLES20.glCreateProgram();

返回值是个句柄,大部分OpenGL方法的返回值都是句柄。

2、编译着色器
变成语言语言变成可运行程序必然需要编译啦。

//vStr是上面的顶点着色器程序代码,以String形式输入
int  vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vStr); 
------------------------------------------------------------------------------------
private int loadShader(int type, String str) {
      //1:创建Shader glCreateShader
     int id = GLES20.glCreateShader(type);
     //2:指定Shader源代码 glShaderSource
     GLES20.glShaderSource(id, str);
     // 3:编译Shader glCompileShader
     GLES20.glCompileShader(id);
     // 4:获取shader状态 glGetShaderiv
     private int[] compile = new int[1];//入参
     GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, compile, 0);
     // 5:如果出错 就 获取shader日志信息 glGetShaderInfoLog
    if (compile[0] == GLES20.GL_FALSE) {
         Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(id));
         GLES20.glDeleteShader(id);
         return -1;
     } else {
         return id;
     }
}

参数str指的是我们用GLSL语言书写的着色器程序文本字符串。
GLES20.GL_VERTEX_SHADER:指的是当前编译的着色器类型——为顶点着色器。

3、将顶点着色器加入到程序

GLES20.glAttachShader(mProgram, vertexShader);

4、指定当前使用的Program

GLES20.glLinkProgram(mProgram);

5、为着色器中的变量赋值

glPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");//获取句柄,获取一次即可
glHCoordinate = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
—————————————————————————————————————— 
GLES20.glEnableVertexAttribArray(glPosition);//激活,绘制开始时调用
GLES20.glVertexAttribPointer(glPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer); //输入顶点坐标数据
GLES20.glEnableVertexAttribArray(glHCoordinate);
GLES20.glVertexAttribPointer(glHCoordinate, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);//输入纹理坐标数据
——————————————————————————————
GLES20.glDisableVertexAttribArray(glPosition);//绘制完成后调用
GLES20.glDisableVertexAttribArray(glHCoordinate);//绘制完成后调用

说一下 glVertexAttribPointer:
第一个参数为目标参数句柄。
第二个参数2表示的是:坐标的维数,2就是2维(x,y)。
第三个参数GLES20.GL_FLOAT表示输入数据的精度。
第四个参数定义是否希望数据被标准化(归一化)。
第五个参数数是步长(Stride),指定在连续的顶点属性之间的间隔。如果传1取值方式为0123、1234、2345……
最后一个即顶点坐标数组数据,以 java.nio.Buffer形式输入。还有另外一种输入方式这里暂不讨论

vertices={
  -1, 1//三角形1号,1号点
  -1,-1//2号点
   1, 1 //3号点
   1,-1//三角形2号 ,4号点
}


ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer  vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

3、绘制纹理:

绘制纹理OpenGL 通过片元着色器实现,下面是一个简单的片元着色器:

String fStr =
            " precision mediump float; " +
                    "uniform sampler2D vTexture; " +
                    "varying vec2 aCoordinate;  " +
                    "void main() {" +
                    "   gl_FragColor=texture2D(vTexture,aCoordinate);" +
                    "}";

sampler2D:一个纹理引用
aCoordinate:由顶点作色器传过来的纹理坐标
texture2D()函数:从纹理上取纹理坐标对应点的颜色。
gl_FragColor:纹理着色器的输出,也就是画笔要画的颜色
同样片元着色器也需要经过编译绑定到OpenGL的program中

int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fStr);
GLES20.glAttachShader(mProgram, fragmentShader);

需要赋值的参数只有一个uniform sampler2D vTexture

glTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");//获取句柄
---------------------------------------------------------------------------------------------------
int[] texture = new int[1];
if (bitmap != null && !bitmap.isRecycled()) {
     //生成纹理
    GLES20.glGenTextures(1, texture, 0);
    //激活0号纹理单元 
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    //绑定纹理
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
    //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    //根据以上指定的参数,生成一个2D纹理
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    return texture[0];
}
-----------------------------------------------------------------------------------------------------
GLES20.glUniform1i(glTexture, 0);//输入纹理,使用0号纹理单元
-----------------------------------------------------------------------------------------------------

这里说一下纹理单元,有点像一个类,它里面包含很多类型的纹理成员变量,比如说常用的GL_TEXTURE_2D,GL_TEXTURE_EXTERNAL_OES。

//打个比方,不是实际代码
TextureUnit{
GL_TEXTURE_2D textureA;
GL_TEXTURE_EXTERNAL_OES textureB;
}

我们只有通过glActiveTexture激活了纹理单元后才能通过glBindTexture方法对其中的成员变量进行赋值。

以上GLSL的相关流程就结束了,完成了OpenGL管线的配置,但是要开始实际的绘制,我们还要调用绘制的api

GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);//绘制调用方式1
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);//绘制调用方式2,参数4代表输入顶点数组的长度

glDrawElements 需要额外输入索引数组 (上面的indexBuffer),其内容指的是顶点的绘制顺序。

{//顶点坐标
  -1, 1//三角形1号,1号点
  -1,-1//2号点
   1, 1 //3号点
   1,-1//三角形2号 ,4号点
}

{
  0,1,2,  
  1,3,2
 }

以上接口都需要在GLThread中调用,通过GLSufaceVIew可以简单创建GLThread环境:
OpenGL简介:简单的使用GLSufaceView

我在学习android 的OpenGL时写的小栗子
https://github.com/UniqueKenzhang/AllAboutVideo

上一篇下一篇

猜你喜欢

热点阅读