[关闭]
@TryLoveCatch 2017-09-15T14:01:25.000000Z 字数 14282 阅读 1816

opeGL ES

opengl


注意:在 OpenGL 中,只能绘制点、直线以及三角形!

GLSurfaceView

GLSurfaceView.Renderer

RenderMode

RenderMode 有两种,RENDERMODE_WHEN_DIRTYRENDERMODE_CONTINUOUSLY
1. RENDERMODE_WHEN_DIRTY:按需渲染,需要手动调用 glSurfaceView.requestRender() 才会进行更新。
2. RENDERMODE_CONTINUOUSLY:连续渲染,会以60fps的速度刷新

顶点

参考:链接

在 OpenGL 中,所有的物体都是由顶点集合来构建的,一个顶点就是一个代表几何对象的拐角,有很多附加属性,最重要的属性就是位置,代表了这个顶点在空间中的定位。

Shader(着色器)

任何 OpenGL 程序都需要两种着色器,顶点着色器 ( vertex shader ) 和片段着色器 ( fragment shader ) 。

片段着色器为组成点、线、三角形的每个片段生成最终的颜色,针对每个片段,它都会执行一次,一个片段是一个小的、单一颜色的长方形区域,类似于计算机屏幕上的一个像素。一旦最终颜色生成了,OpenGL 就会把它们写到一块称为帧缓冲区的内存块中,然后 Android 会把这个块显示到屏幕上。

举个例子,画画的时候我们经常有这么一个过程:先打线稿,再上色。
顶点着色器这个是告诉电脑如何打线稿的——如何处理顶点、法线等的数据的小程序。
片段着色器这个是告诉电脑如何上色的——如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副图像的小程序。

这个过程可以简单的归纳为七个阶段:

读取顶点数据 -> 执行顶点着色器 -> 组装图元 -> 光栅化图元 -> 执行片段着色器 -> 写入帧缓冲区 -> 显示在屏幕上 。

所谓组装图元可以这样理解:例如一条直线,给定了两个顶点坐标之后,假设为(0,0)(0,10),那么(0,1)...(0,9)这些点就需要openGL来帮我们处理,这就是图元

光栅化(Rasterize/rasteriztion)

栅格化或者像素化。就是把矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,就需要Rasterize这个过程。就是从矢量的点线面的描述,变成像素的描述。

坐标系

实例一:长方形

我们来下一个例子,画一个长方形在屏幕上。上面我们说过,

在 OpenGL 中,只能绘制点、直线以及三角形!

所以,我们可以画两个三角形来合成一个长方形,如图:

1. 顶点数据
我们根据上图,可以来定义顶点数据,本来是一个长方形,我们定义:

  1. float[] tableVertices = {
  2. 0f, 0f,
  3. 0f, 14f,
  4. 9f, 14f,
  5. 9f, 0f
  6. };

也就是,表示了四个顶点的坐标,两个值表示一个坐标。
但是不能画长方形,所以我们分开为两个三角形:

  1. float[] tableVerticesWithTriangles = {
  2. // Triangle 1
  3. 0f, 0f,
  4. 9f, 14f,
  5. 0f, 14f,
  6. // Triangle 2
  7. 0f, 0f,
  8. 9f, 0f,
  9. 9f, 14f
  10. }

在定义三角形时,我们总是以逆时针的顺序排列顶点,这称为卷曲顺序。

2. 顶点数据如何使用

FloatBuffer&ByteBuffer

我们在 java 层定义好了顶点,然而这个定义并不能被 OpenGL 直接使用,我们需要将这些数据复制到本地堆:

  1. private static final int BYTES_PER_FLOAT = 4;
  2. float[] tableVerticesWithTriangles = {
  3. // Triangle 1
  4. 0f, 0f,
  5. 9f, 14f,
  6. 0f, 14f,
  7. // Triangle 2
  8. 0f, 0f,
  9. 9f, 0f,
  10. 9f, 14f
  11. }
  12. FloatBuffer buffer;
  13. buffer = ByteBuffer
  14. .allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
  15. .order(ByteOrder.nativeOrder())
  16. .asFloatBuffer();
  17. buffer.put(tableVerticesWithTriangles);

3. Sharder

3.1. 创建着色器glsl代码
参考:链接
创建一个顶点着色器,在resraw文件夹中创建一个顶点着色器simple_vertex_shader.glsl顶点着色器必须对 gl_Position 赋值。

  1. attribute vec4 a_Position;
  2. void main()
  3. {
  4. gl_Position = a_Position;
  5. gl_PointSize = 10.0;
  6. }

创建一个片段着色器,在resraw 文件夹中创建一个片段着色器simple_fragment_shader.glsl。我们对gl_FragColor进行赋值, OpenGL会使用这个颜色作为当前片段的最终颜色。

  1. precision mediump float;
  2. uniform vec4 u_Color;
  3. void main()
  4. {
  5. gl_FragColor = u_Color;
  6. }

语法参考:链接 链接2

3.2. 创建着色器java代码

  1. private static int compileShader(int type, String shaderCode) {
  2. // Create a new shader object.
  3. // 创建一个着色器,顶点或者片段,返回一个int 可以理解为这个对象的指针
  4. // 0表示失败
  5. final int shaderObjectId = glCreateShader(type);
  6. if (shaderObjectId == 0) {
  7. if (BuildConfig.DEBUG) {
  8. Log.w(TAG, "Could not create new shader.");
  9. }
  10. return 0;
  11. }
  12. // Pass in the shader source.
  13. // 将我们刚刚读取的着色器代码关联到着色器对象
  14. glShaderSource(shaderObjectId, shaderCode);
  15. // Compile the shader.
  16. // 编译上面的着色器代码
  17. glCompileShader(shaderObjectId);
  18. // Get the compilation status.
  19. // 编译过后,我们需要取出编译状态以及在错误时取出信息日志
  20. final int[] compileStatus = new int[1];
  21. glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
  22. if (BuildConfig.DEBUG) {
  23. // Print the shader info log to the Android log output.
  24. Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:"
  25. + glGetShaderInfoLog(shaderObjectId));
  26. }
  27. // Verify the compile status.
  28. // 为0 就是编译失败
  29. if (compileStatus[0] == 0) {
  30. // If it failed, delete the shader object.
  31. glDeleteShader(shaderObjectId);
  32. if (BuildConfig.DEBUG) {
  33. Log.w(TAG, "Compilation of shader failed.");
  34. }
  35. return 0;
  36. }
  37. // Return the shader object ID.
  38. // 返回着色器对象的指针
  39. return shaderObjectId;
  40. }

现在我们已经加载并编译了一个顶点着色器和一个片段着色器,下一步就是将他们绑定到一个单一的程序中。
4. Program

  1. public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
  2. // Create a new program object.
  3. // 创建一个程序对象 返回值 int依然是指针
  4. final int programObjectId = glCreateProgram();
  5. // 指针==0 就是创建失败
  6. if (programObjectId == 0) {
  7. if (BuildConfig.DEBUG) {
  8. Log.w(TAG, "Could not create new program");
  9. }
  10. return 0;
  11. }
  12. // Attach the vertex shader to the program.
  13. // 顶点着色器附加到程序对象
  14. glAttachShader(programObjectId, vertexShaderId);
  15. // Attach the fragment shader to the program.
  16. // 片段着色器附加到程序对象
  17. glAttachShader(programObjectId, fragmentShaderId);
  18. // Link the two shaders together into a program.
  19. // 链接着色器和程序对象
  20. glLinkProgram(programObjectId);
  21. // Get the link status.
  22. // 判断是否链接成功了
  23. final int[] linkStatus = new int[1];
  24. glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
  25. if (BuildConfig.DEBUG) {
  26. // Print the program info log to the Android log output.
  27. Log.v(TAG, "Results of linking program:\n"
  28. + glGetProgramInfoLog(programObjectId));
  29. }
  30. // Verify the link status.
  31. if (linkStatus[0] == 0) {
  32. // If it failed, delete the program object.
  33. glDeleteProgram(programObjectId);
  34. if (BuildConfig.DEBUG) {
  35. Log.w(TAG, "Linking of program failed.");
  36. }
  37. return 0;
  38. }
  39. // Return the program object ID.
  40. // 返回程序对象指针
  41. return programObjectId;
  42. }

调用这个方法:

  1. //将着色器附加到程序对象上并执行链接操作
  2. program = ShaderHelper.linkProgram(vertexShader, fragmentShader);

5. 使用Program

  1. glUseProgram(program)

告诉 OpenGL 在绘制任何东西在屏幕上的时候要使用这里定义的程序。

6. 获取glsl文件定义属性

获取 属性u_Colora_Positionshader 中的指针

  1. attribColor = GLES20.glGetUniformLocation(program, U_COLOR);
  2. attribPosition = GLES20.glGetAttribLocation(program, A_POSITION);

7. 装载顶点数据

参考:链接中的小结

顶点数据,当然会有多个顶点,一个顶点会有多个顶点属性,而一个顶点属性,会有1-4个属性值构成。这里有两种格式:

举例说明:
一个顶点,有三个属性组成:位置纹理1纹理2置对应3个值,纹理分别对应2个值。
一起存储:

  1. float[] arrays = {
  2. 0f, 0f, 1f, //(x,y,z)位置
  3. 9f, 14f, //(s,t)纹理1坐标
  4. 0f, 14f, //(s,t)纹理2坐标
  5. ...
  6. ...
  7. 0f, 0f, 1f, //(x,y,z)位置
  8. 9f, 14f, //(s,t)纹理1坐标
  9. 0f, 14f, //(s,t)纹理2坐标
  10. };

分别存储:

  1. float[] arrays1 = {
  2. 0f, 0f, 1f, //(x,y,z)位置
  3. ...
  4. ...
  5. 0f, 0f, 1f, //(x,y,z)位置
  6. 9f, 14f, //(s,t)纹理坐标1
  7. };
  8. float[] arrays2 = {
  9. 9f, 14f, //(s,t)纹理坐标1
  10. ...
  11. ...
  12. 9f, 14f, //(s,t)纹理坐标1
  13. };
  14. float[] arrays2 = {
  15. 0f, 14f, //(s,t)纹理坐标2
  16. ...
  17. ...
  18. 0f, 14f, //(s,t)纹理坐标2
  19. };

具体来看java中填充数据的方法:

  1. buffer.position(0);//将读取指针复位
  2. GLES20.glVertexAttribPointer(attribPosition,
  3. POSITION_COMOPNENT_COUNT, GLES20.GL_FLOAT, false,0, buffer);
  4. GLES20.glEnableVertexAttribArray(attribPosition);

glVertexAttribPointer()非常重要,指定了顶点属性的数据格式和位置:

参数:
index
指定要修改的顶点属性的指针


size
指定该顶点属性的属性值数量。必须为1、2、3或者4。初始值为4。(如position是由3个(x,y,z)组成,而颜色4个(r,g,b,a)


type
指定数组中每个属性值的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT


normalized
指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)。


stride
指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。如果一起存储,那么例子中这个值就应该是3 * 4 + 2 * 4 + 2 * 4,相当于单个顶点的所有属性的字节总数;如果是分别存储,那么就可以为0


pointer
指针,指定第一个值在数组的第一个顶点属性中的偏移量。该数组与GL_ARRAY_BUFFER绑定,储存于缓冲区中。初始值为0

我们在来看看这个句代码:

  1. GLES20.glVertexAttribPointer(attribPosition, POSITION_COMOPNENT_COUNT, GLES20.GL_FLOAT, false,
  2. 0, buffer);

8. Draw
下面我们开始画这个长方形了,我们在onDrawFrame()添加:

  1. GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
  2. // Draw the table.
  3. // 指定uniform变量attribPosition的值
  4. GLES20.glUniform4f(attribColor, 1.0f, 1.0f, 0.0f, 1.0f);
  5. // 绘制桌子
  6. // 第一个参数 表示绘制的是三角形
  7. // 第二个参数 表示从顶点数据的开头开始读取顶点
  8. // 第三个参数 表示要读入6个顶点
  9. GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);

8. 坐标问题
我们运行程序,但是发现有点问题,显示的位置不太对,那个是因为我们没有转换为openGL的坐标,我们修改顶点数据:

  1. float[] tableVerticesWithTriangles = {
  2. // Triangle 1
  3. -0.5f, -0.5f,
  4. 0.5f, 0.5f,
  5. -0.5f, 0.5f,
  6. // Triangle 2
  7. -0.5f, -0.5f,
  8. 0.5f, -0.5f,
  9. 0.5f, 0.5f,
  10. };

9. 加一条线
接下来,我们加一条线,将长方形分成上下两个。
增加线的两个顶点坐标:

  1. float[] tableVerticesWithTriangles = {
  2. // Triangle 1
  3. -0.5f, -0.5f,
  4. 0.5f, 0.5f,
  5. -0.5f, 0.5f,
  6. // Triangle 2
  7. -0.5f, -0.5f,
  8. 0.5f, -0.5f,
  9. 0.5f, 0.5f,
  10. // Line 1
  11. -0.5f, 0f,
  12. 0.5f, 0f,
  13. };

画线,一条红色的线:

  1. GLES20.glUniform4f(attribColor, 1.0f, 0.0f, 0.0f, 1.0f);
  2. GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);

实例二 通过相机预览录制视频并且编码成一个MP4文件

链接地址

这个代码,没有预览界面,并且在屏幕关闭的时候,依然可以得到摄像头传过来的数据,简直是偷拍的利器啊!!!
主要流程:

  1. new CodecInputSurface(mEncoder.createInputSurface());
    指定MediaCodec的数据来源为CodecInputSurface,传入Surface
  2. 构建EGL相关数据,EGLDisplay&EGLContext,并根据传入的Surface创建EGLSurface,这样MediaCodecinput就与openGL绑定了。
  3. mInputSurface.makeCurrent(),通过eglMakeCurrent()函数来将当前的上下文切换,这样opengl的函数才能启动作用。
  4. SurfaceTextureManager里面,新建STextureRender对象,调用其surfaceCreated(),这个时候,GLES20.glGenTextures(1, textures, 0)会得到openGL纹理ID
  5. SurfaceTextureManager,创建SurfaceTexture对象,参数为上一步的纹理ID,这样SurfaceTexture就和openGL联系起来了。
  6. mCamera.setPreviewTexture(st);,参数为上一步的SurfaceTexture对象,这样SurfaceTexture就和Camera联系起来了。
  7. mCamera.startPreview();开启相机预览,当有数据到来时,会回调SurfaceTextureOnFrameAvailableListener,需要调用updateTexImage,根据内容流中最近的图像更新SurfaceTexture对应的GL纹理对象,也就是在步骤4里面的那个纹理ID,所以这个纹理ID里面是有Camera传过来的图片数据的。
  8. 处理这个SurfaceTexture数据,调用STextureRenderdrawFrame(),这里面就是一堆openGL操作,其中,一般情况,我们glBindTexture之后,会调用glTexImage2D()来绘制bitmap,不过,根据步骤7我们这个纹理ID已经有数据了,所以不需要调用。最后调用glDrawArrays()更新到显存里面,这个时候,MediaCodec就会得到input数据了。
  9. 取得MediaCodecOutput已经编码的数据,然后传给MediaMuxer,调用writeSampleData()写入mp4文件中去。
  10. 对了,这里面还有一个操作:滤镜,每隔15帧会处理一下。处理过程也比较简单,就是替换了片段着色器,做了一个.gbra的处理,这个的作用就是,原本是rgba,所以原来r的地方会变成g,原来g的地方会变成b,原来b的地方会变成r,就这样。

eglMakeCurrent

eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context)函数来将当前的上下文切换,这样opengl的函数才能启动作用。
该接口将申请到的displaydraw(surface)context进行了绑定。也就是说,在context下的OpenGLAPI指令将draw(surface)作为其渲染最终目的地。而display作为draw(surface)的前端显示。调用后,当前线程使用的EGLContexcontext
由于每个OpenGL的上下文和单独的线程绑定,因此,如果我们需要在屏幕上绘制多个TextureView的话,必须要为每个View创建单独的线程。
参考:链接

Surface&SurfaceView&GLSurfaceView&SurfaceTexture&TextureView

参考:链接1
链接2
链接3

Surface

Surface,就是内存中的一段绘图缓冲区。Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都是画在Surface上的,传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行。

SurfaceView

SurfaceViewAndroid 1.0(API level 1)时就有 。它继承自类View,因此它本质上是一个View。但与普通View不同的是,它有自己的Surface,提供了一个专门用于绘制的surfaceSurfaceView就是展示Surface中数据的地方,同时可以认为SurfaceView是用来控制SurfaceView的位置和尺寸的。
SurfaceView是独立于其所属windowview hierarchy的,view hierarchy中的view们共享window那一个surface
surfaceview提供了一个可见区域,只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。也就是说,Surface是用通过SurfaceView才能展示其中的内容,两者的关系如下图:

由于SurfaceView直接将绘图表层绘制到屏幕上,所以和普通的View不同的地方就在与它不能执行TransitionRotationScale等转换,也不能进行Alpha透明度运算。

GLSurfaceView

SurfaceView 出现最早, 解决类似视频播放的问题(可以用单独一个线程来渲染UI)。
后来发现用起来不方便, 渲染线程需要单独编写, 一大堆都可以独立成模板。
所以后来就出现了 GLSurfaceView, 概括一句话就是 使用了模板的 SurfaceView。但是依然还是由SurfaceView的共性缺点:

SurfaceView不能根据屏幕的变化而变化,不能执行TransitionRotationScale等转换,也不能进行Alpha透明度运算

所以有了TextureView

SurfaceTexture

SurfaceTexture从Android3.0(API 11)加入的一个新类,它的主要用途从一个图像流捕获帧数据作为OpenGL ES的纹理。 如果updateTexImage() 被调用,根据内容流中最近的图像更新在创建SurfaceTexture 时指定的纹理对象的内容,接下来,就可以像操作普通GL纹理一样操作它了。
它对图像流的处理并不直接显示,而是转为GL外部纹理,因此可用于图像流数据的二次处理(如Camera滤镜,桌面特效等)。比如Camera的预览数据,变成纹理后可以交给GLSurfaceView直接显示,也可以通过SurfaceTexture交给TextureView作为View heirachy中的一个硬件加速层来显示。首先,SurfaceTexture从图像流(来自Camera预览,视频解码,GL绘制场景等)中获得帧数据,当调用updateTexImage()时,根据内容流中最近的图像更新SurfaceTexture对应的GL纹理对象,接下来,就可以像操作普通GL纹理一样操作它了。

SurfaceTexture这个东西不是用来显示给用户看的, 它是一个Texture, 意识是纹理, 可以想象成一个View的中间件。
举个例子:
1 : Camera 把视频采集的内容交给 SurfaceView, 就变成了照相机。
2:Camera 把视频采集的内容交给 SurfaceTextureSurfaceTexture 在对内容做个美颜, 然后SurfaceTexture 再把内容交给 SurfaceView, 就变成了美图秀秀。

TextureView

TextureView,是Android4.0(API 14)添加进来的,TextureView专门用来渲染像视频或OpenGL场景之类的数据,而且TextureView只能用在具有硬件加速的Window中,如果使用的是软件渲染,TextureView什么也不显示。也就是说对于没有GPU的设备,TextureView完全不可用。

它可以将内容流直接投影到View中,可以用于实现Live preview等功能。和SurfaceView不同,它不会单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。

总结

最后,总结下这几者的区别和联系。
SurfaceView是一个有自己SurfaceView。它的渲染可以放在单独线程而不是主线程中。其缺点是不能做变形和动画。
SurfaceTexture可以用作非直接输出的内容流,这样就提供二次处理的机会。与SurfaceView直接输出相比,这样会有若干帧的延迟。同时,由于它本身管理BufferQueue,因此内存消耗也会稍微大一些。
TextureView是一个可以把内容流作为外部纹理输出在上面的View。它本身需要是一个硬件加速层。事实上TextureView本身也包含了SurfaceTexture。它与SurfaceView+SurfaceTexture组合相比可以完成类似的功能(即把内容流上的图像转成纹理,然后输出)。区别在于TextureView是在View hierachy中做绘制,因此一般它是在主线程上做的(在Android 5.0引入渲染线程后,它是在渲染线程中做的)。而SurfaceView+SurfaceTexture在单独的Surface上做绘制,可以是用户提供的线程,而不是系统的主线程或是渲染线程。另外,与TextureView相比,它还有个好处是可以用Hardware overlay进行显示。

glUniformMatrix4fv

  1. public static native void glUniformMatrix4fv(
  2. int location,
  3. int count,
  4. boolean transpose,
  5. java.nio.FloatBuffer value
  6. );

该函数的第一个参数是该变量在shader中的位置,第二个参数是被赋值的矩阵的数目(因为uniform变量可以是一个数组)。第三个参数表明在向uniform变量赋值时该矩阵是否需要转置。因为我们使用的是glm定义的矩阵,因此不要进行转置。但如果你正在使用一个数组来实现矩阵,并且这个矩阵是按行定义的,那么你就需要设置这个参数为GL_TRUE。最后一个参数就是传递给uniform变量的数据的指针了。

绘制一个图片

参考:纹理
1、着色器
2、program
3、获取各个参数
4、生成texture

  1. int[] texture=new int[1];
  2. GLES20.glGenTextures(1,texture,0);
  3. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
  4. //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
  5. GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);
  6. //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
  7. GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
  8. //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
  9. GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
  10. //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
  11. GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
  12. GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);

5、user program
6、buffer position
7、glVertexAttribPointer
8、绑定texture

  1. //激活0对应的texture
  2. GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  3. //绑定0 texture和mTexture 这个是我们生存的2d纹理 已经有bitmap了
  4. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture);
  5. // 设置uTexture的sampler2D对应位置为0的纹理单元
  6. GLES20.glUniform1i(uTexture, 0);

使用glUniform1i,我们可以给纹理采样器分配一个位置值,这样的话我们能够在一个片段着色器中设置多个纹理。一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元
纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:
glActiveTexture(GL_TEXTURE0); //在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。

这个glBindTexture不是必须的,所谓的glBindTexture,就是说接下来的操作全是针对这个绑定纹理单元,我可以texImage2D重新绘制一个bitmap
glUniform1i比较重要,这里才是真正起作用的,第一个参数是着色器中的纹理对象,也就是在glsl文件里面写的,通常为sampler2D;第二个参数是纹理单元,这个方法,会将该纹理单元绑定到纹理对象,这样就能够显示了。
这时,如果着色器中的纹理对象有多个,纹理单元当然也可以有多个,而我们的纹理对象可以共用一个纹理单元,也就是说纹理单元bitmap画在两个纹理对象上。例如:

  1. //着色器中的纹理对象
  2. int texture1;
  3. int texture2;
  4. int[] texture=new int[2];
  5. GLES20.glGenTextures(2,texture,0);
  6. ... //给texture纹理单元数组赋予不同的bitmap等操作
  7. //激活第0个纹理单元
  8. GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  9. //绑定第0个纹理单元,也可以去掉,如果没有针对这个纹理单元的操作
  10. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
  11. // 绑定texture1纹理对象和0 纹理单元
  12. GLES20.glUniform1i(texture1, 0);
  13. GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
  14. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[1]);
  15. GLES20.glUniform1i(texture2, 1);

这个是正常的写法,这个时候,我们修改最后一句话:

  1. GLES20.glUniform1i(texture2, 0);

这样,texture1和texture2就将会显示同样的纹理单元了,也就会显示同样的bitmap

纹理单元是持有bitmap数据的,可以被重复使用,glBindTexture之后,就可以修改这个纹理单元纹理对象这个是在着色器中定义的,一般为sampler2D
我们调用glUniform1i,通过给纹理对象赋值纹理单元,来显示bitmap.

9、绘制

  1. GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);

参考

逆流的鱼yuiop
Android & OpenGL
opengl android
安卓 OpenGL ES 2.0 完全入门(一):基本概念和 hello world
Android OpenGL ES从白痴到入门(一):概念
《OpenGL ES 应用开发实践指南》读书笔记 No.1
如何理解OpenGL中着色器,渲染管线,光栅化等概念?
Android MediaCodec编解码详解及demo

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注