@TryLoveCatch
2017-09-15T14:01:25.000000Z
字数 14282
阅读 1816
opengl
注意:在 OpenGL 中,只能绘制点、直线以及三角形!
RenderMode
有两种,RENDERMODE_WHEN_DIRTY
和 RENDERMODE_CONTINUOUSLY
。
1. RENDERMODE_WHEN_DIRTY
:按需渲染,需要手动调用 glSurfaceView.requestRender()
才会进行更新。
2. RENDERMODE_CONTINUOUSLY
:连续渲染,会以60fps的速度刷新
参考:链接
在 OpenGL 中,所有的物体都是由顶点集合来构建的,一个顶点就是一个代表几何对象的拐角,有很多附加属性,最重要的属性就是位置,代表了这个顶点在空间中的定位。
任何 OpenGL 程序都需要两种着色器,顶点着色器 ( vertex shader ) 和片段着色器 ( fragment shader ) 。
VerticesShader(顶点着色器)
顶点着色器生成每个顶点的最终位置,针对每个顶点,它都会执行一次,一旦最终确定了位置,OpenGL 就可以根据这些顶点的集合组装成点、直线以及三角形。
FragmentShader(片段着色器)
片段着色器为组成点、线、三角形的每个片段生成最终的颜色,针对每个片段,它都会执行一次,一个片段是一个小的、单一颜色的长方形区域,类似于计算机屏幕上的一个像素。一旦最终颜色生成了,OpenGL 就会把它们写到一块称为帧缓冲区的内存块中,然后 Android 会把这个块显示到屏幕上。
举个例子,画画的时候我们经常有这么一个过程:先打线稿,再上色。
顶点着色器这个是告诉电脑如何打线稿的——如何处理顶点、法线等的数据的小程序。
片段着色器这个是告诉电脑如何上色的——如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副图像的小程序。
这个过程可以简单的归纳为七个阶段:
读取顶点数据 -> 执行顶点着色器 -> 组装图元 -> 光栅化图元 -> 执行片段着色器 -> 写入帧缓冲区 -> 显示在屏幕上 。
所谓组装图元
可以这样理解:例如一条直线,给定了两个顶点坐标之后,假设为(0,0)
和(0,10)
,那么(0,1)...(0,9)
这些点就需要openGL
来帮我们处理,这就是图元
了
栅格化或者像素化。就是把矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,就需要Rasterize这个过程。就是从矢量的点线面的描述,变成像素的描述。
OpenGL 坐标系
三维坐标系,原点在中间,x轴
向右,y轴
向上,z轴
朝向我们,x y z
取值范围都是 [-1, 1]
:
OpenGL 纹理(texture)坐标系
二维坐标系,原点在左下角,s(x)轴
向右,t(y)轴
向上,x y
取值范围都是 [0, 1]
:
我们来下一个例子,画一个长方形在屏幕上。上面我们说过,
在 OpenGL 中,只能绘制点、直线以及三角形!
所以,我们可以画两个三角形来合成一个长方形,如图:
1. 顶点数据
我们根据上图,可以来定义顶点数据,本来是一个长方形,我们定义:
float[] tableVertices = {
0f, 0f,
0f, 14f,
9f, 14f,
9f, 0f
};
也就是,表示了四个顶点的坐标,两个值表示一个坐标。
但是不能画长方形,所以我们分开为两个三角形:
float[] tableVerticesWithTriangles = {
// Triangle 1
0f, 0f,
9f, 14f,
0f, 14f,
// Triangle 2
0f, 0f,
9f, 0f,
9f, 14f
}
在定义三角形时,我们总是以逆时针的顺序排列顶点,这称为卷曲顺序。
2. 顶点数据如何使用
FloatBuffer&ByteBuffer
我们在 java 层定义好了顶点,然而这个定义并不能被 OpenGL 直接使用,我们需要将这些数据复制到本地堆:
private static final int BYTES_PER_FLOAT = 4;
float[] tableVerticesWithTriangles = {
// Triangle 1
0f, 0f,
9f, 14f,
0f, 14f,
// Triangle 2
0f, 0f,
9f, 0f,
9f, 14f
}
FloatBuffer buffer;
buffer = ByteBuffer
.allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
buffer.put(tableVerticesWithTriangles);
ByteBuffer.allocateDirect
方法分配了一块本地内存,这块内存是不会被垃圾回收机制回收的,参数是分配多少字节的内存块。 order(ByteOrder.nativeOrder())
告诉字节缓冲区按照本地字节序组织它的内容。asFloatBuffer()
得到一个可以反映底层字节的 FloatBuffer
类实例。vertexData.put(tableVerticesWithTriangles);
将数据从虚拟机复制到本地内存。3. Sharder
3.1. 创建着色器glsl代码
参考:链接
创建一个顶点着色器,在res
的raw
文件夹中创建一个顶点着色器simple_vertex_shader.glsl
。顶点着色器必须对 gl_Position 赋值。
attribute vec4 a_Position;
void main()
{
gl_Position = a_Position;
gl_PointSize = 10.0;
}
创建一个片段着色器,在res
的raw
文件夹中创建一个片段着色器simple_fragment_shader.glsl
。我们对gl_FragColor
进行赋值, OpenGL
会使用这个颜色作为当前片段的最终颜色。
precision mediump float;
uniform vec4 u_Color;
void main()
{
gl_FragColor = u_Color;
}
3.2. 创建着色器java代码
private static int compileShader(int type, String shaderCode) {
// Create a new shader object.
// 创建一个着色器,顶点或者片段,返回一个int 可以理解为这个对象的指针
// 0表示失败
final int shaderObjectId = glCreateShader(type);
if (shaderObjectId == 0) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "Could not create new shader.");
}
return 0;
}
// Pass in the shader source.
// 将我们刚刚读取的着色器代码关联到着色器对象
glShaderSource(shaderObjectId, shaderCode);
// Compile the shader.
// 编译上面的着色器代码
glCompileShader(shaderObjectId);
// Get the compilation status.
// 编译过后,我们需要取出编译状态以及在错误时取出信息日志
final int[] compileStatus = new int[1];
glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
if (BuildConfig.DEBUG) {
// Print the shader info log to the Android log output.
Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:"
+ glGetShaderInfoLog(shaderObjectId));
}
// Verify the compile status.
// 为0 就是编译失败
if (compileStatus[0] == 0) {
// If it failed, delete the shader object.
glDeleteShader(shaderObjectId);
if (BuildConfig.DEBUG) {
Log.w(TAG, "Compilation of shader failed.");
}
return 0;
}
// Return the shader object ID.
// 返回着色器对象的指针
return shaderObjectId;
}
现在我们已经加载并编译了一个顶点着色器和一个片段着色器,下一步就是将他们绑定到一个单一的程序中。
4. Program
public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
// Create a new program object.
// 创建一个程序对象 返回值 int依然是指针
final int programObjectId = glCreateProgram();
// 指针==0 就是创建失败
if (programObjectId == 0) {
if (BuildConfig.DEBUG) {
Log.w(TAG, "Could not create new program");
}
return 0;
}
// Attach the vertex shader to the program.
// 顶点着色器附加到程序对象
glAttachShader(programObjectId, vertexShaderId);
// Attach the fragment shader to the program.
// 片段着色器附加到程序对象
glAttachShader(programObjectId, fragmentShaderId);
// Link the two shaders together into a program.
// 链接着色器和程序对象
glLinkProgram(programObjectId);
// Get the link status.
// 判断是否链接成功了
final int[] linkStatus = new int[1];
glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
if (BuildConfig.DEBUG) {
// Print the program info log to the Android log output.
Log.v(TAG, "Results of linking program:\n"
+ glGetProgramInfoLog(programObjectId));
}
// Verify the link status.
if (linkStatus[0] == 0) {
// If it failed, delete the program object.
glDeleteProgram(programObjectId);
if (BuildConfig.DEBUG) {
Log.w(TAG, "Linking of program failed.");
}
return 0;
}
// Return the program object ID.
// 返回程序对象指针
return programObjectId;
}
调用这个方法:
//将着色器附加到程序对象上并执行链接操作
program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
5. 使用Program
glUseProgram(program)
告诉 OpenGL 在绘制任何东西在屏幕上的时候要使用这里定义的程序。
6. 获取glsl文件定义属性
获取 属性u_Color
、a_Position
在shader
中的指针
attribColor = GLES20.glGetUniformLocation(program, U_COLOR);
attribPosition = GLES20.glGetAttribLocation(program, A_POSITION);
7. 装载顶点数据
参考:链接中的四
小结
顶点数据,当然会有多个顶点,一个顶点会有多个顶点属性,而一个顶点属性,会有1-4个属性值构成。这里有两种格式:
举例说明:
一个顶点,有三个属性组成:位置
,纹理1
和纹理2
。位
置对应3
个值,纹理
分别对应2
个值。
一起存储:
float[] arrays = {
0f, 0f, 1f, //(x,y,z)位置
9f, 14f, //(s,t)纹理1坐标
0f, 14f, //(s,t)纹理2坐标
...
...
0f, 0f, 1f, //(x,y,z)位置
9f, 14f, //(s,t)纹理1坐标
0f, 14f, //(s,t)纹理2坐标
};
分别存储:
float[] arrays1 = {
0f, 0f, 1f, //(x,y,z)位置
...
...
0f, 0f, 1f, //(x,y,z)位置
9f, 14f, //(s,t)纹理坐标1
};
float[] arrays2 = {
9f, 14f, //(s,t)纹理坐标1
...
...
9f, 14f, //(s,t)纹理坐标1
};
float[] arrays2 = {
0f, 14f, //(s,t)纹理坐标2
...
...
0f, 14f, //(s,t)纹理坐标2
};
具体来看java
中填充数据的方法:
buffer.position(0);//将读取指针复位
GLES20.glVertexAttribPointer(attribPosition,
POSITION_COMOPNENT_COUNT, GLES20.GL_FLOAT, false,0, buffer);
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
;
我们在来看看这个句代码:
GLES20.glVertexAttribPointer(attribPosition, POSITION_COMOPNENT_COUNT, GLES20.GL_FLOAT, false,
0, buffer);
POSITION_COMOPNENT_COUNT = 2
,一个顶点属性有两个值构成GLES20.GL_FLOAT
是浮点数据0
,偏移量,确定下一个属性的位置。应该也可以为2 * 4
。buffer
,FloatBuffer
本身就可以理解为一个指针,还记不记的我们之前buffer.position(0)
,就是指针复位;如果buffer.position(2)
,那么就跳过前面两个值,属性的第一个值就是从数组的第三位开始了。8. Draw
下面我们开始画这个长方形了,我们在onDrawFrame()
添加:
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// Draw the table.
// 指定uniform变量attribPosition的值
GLES20.glUniform4f(attribColor, 1.0f, 1.0f, 0.0f, 1.0f);
// 绘制桌子
// 第一个参数 表示绘制的是三角形
// 第二个参数 表示从顶点数据的开头开始读取顶点
// 第三个参数 表示要读入6个顶点
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
8. 坐标问题
我们运行程序,但是发现有点问题,显示的位置不太对,那个是因为我们没有转换为openGL
的坐标,我们修改顶点数据:
float[] tableVerticesWithTriangles = {
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
};
9. 加一条线
接下来,我们加一条线,将长方形分成上下两个。
增加线的两个顶点坐标:
float[] tableVerticesWithTriangles = {
// Triangle 1
-0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
// Triangle 2
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f,
// Line 1
-0.5f, 0f,
0.5f, 0f,
};
画线,一条红色的线:
GLES20.glUniform4f(attribColor, 1.0f, 0.0f, 0.0f, 1.0f);
GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2);
这个代码,没有预览界面,并且在屏幕关闭的时候,依然可以得到摄像头传过来的数据,简直是偷拍的利器啊!!!
主要流程:
new CodecInputSurface(mEncoder.createInputSurface());
指定MediaCodec
的数据来源为CodecInputSurface
,传入Surface
。- 构建
EGL
相关数据,EGLDisplay
&EGLContext
,并根据传入的Surface
创建EGLSurface
,这样MediaCodec
的input
就与openGL
绑定了。mInputSurface.makeCurrent()
,通过eglMakeCurrent()
函数来将当前的上下文切换,这样opengl
的函数才能启动作用。SurfaceTextureManager
里面,新建STextureRender
对象,调用其surfaceCreated()
,这个时候,GLES20.glGenTextures(1, textures, 0)
会得到openGL
的纹理ID
。SurfaceTextureManager
,创建SurfaceTexture
对象,参数为上一步的纹理ID
,这样SurfaceTexture
就和openGL
联系起来了。mCamera.setPreviewTexture(st);
,参数为上一步的SurfaceTexture
对象,这样SurfaceTexture
就和Camera
联系起来了。mCamera.startPreview();
开启相机预览,当有数据到来时,会回调SurfaceTexture
的OnFrameAvailableListener
,需要调用updateTexImage
,根据内容流中最近的图像更新SurfaceTexture
对应的GL纹理对象,也就是在步骤4
里面的那个纹理ID
,所以这个纹理ID
里面是有Camera
传过来的图片数据的。- 处理这个
SurfaceTexture
数据,调用STextureRender
的drawFrame()
,这里面就是一堆openGL
操作,其中,一般情况,我们glBindTexture
之后,会调用glTexImage2D()
来绘制bitmap
,不过,根据步骤7
我们这个纹理ID
已经有数据了,所以不需要调用。最后调用glDrawArrays()
更新到显存里面,这个时候,MediaCodec
就会得到input
数据了。- 取得
MediaCodec
的Output
已经编码的数据,然后传给MediaMuxer
,调用writeSampleData()
写入mp4
文件中去。- 对了,这里面还有一个操作:滤镜,每隔15帧会处理一下。处理过程也比较简单,就是替换了
片段着色器
,做了一个.gbra
的处理,这个的作用就是,原本是rgba
,所以原来r
的地方会变成g
,原来g
的地方会变成b
,原来b
的地方会变成r
,就这样。
eglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context)
函数来将当前的上下文切换,这样opengl的函数才能启动作用。
该接口将申请到的display
,draw(surface)
和 context
进行了绑定。也就是说,在context
下的OpenGLAPI
指令将draw(surface)
作为其渲染最终目的地。而display
作为draw(surface
)的前端显示。调用后,当前线程使用的EGLContex
为context
。
由于每个OpenGL的上下文和单独的线程绑定,因此,如果我们需要在屏幕上绘制多个TextureView的话,必须要为每个View创建单独的线程。
参考:链接
Surface
,就是内存中的一段绘图缓冲区。Surface
对应了一块屏幕缓冲区,每个window
对应一个Surface
,任何View
都是画在Surface
上的,传统的view
共享一块屏幕缓冲区,所有的绘制必须在UI线程
中进行。
SurfaceView
从Android 1.0(API level 1)
时就有 。它继承自类View
,因此它本质上是一个View
。但与普通View
不同的是,它有自己的Surface
,提供了一个专门用于绘制的surface
。SurfaceView
就是展示Surface
中数据的地方,同时可以认为SurfaceView
是用来控制Surface
中View
的位置和尺寸的。
SurfaceView
是独立于其所属window
的view hierarchy
的,view hierarchy
中的view
们共享window
那一个surface
。
surfaceview
提供了一个可见区域,只有在这个可见区域内 的surface
部分内容才可见,可见区域外的部分不可见。也就是说,Surface
是用通过SurfaceView
才能展示其中的内容,两者的关系如下图:
由于SurfaceView
直接将绘图表层绘制到屏幕上,所以和普通的View不同的地方就在与它不能执行Transition
,Rotation
,Scale
等转换,也不能进行Alpha
透明度运算。
SurfaceView
出现最早, 解决类似视频播放的问题(可以用单独一个线程来渲染UI)。
后来发现用起来不方便, 渲染线程需要单独编写, 一大堆都可以独立成模板。
所以后来就出现了 GLSurfaceView
, 概括一句话就是 使用了模板的 SurfaceView
。但是依然还是由SurfaceView
的共性缺点:
SurfaceView不能根据屏幕的变化而变化,不能执行
Transition
,Rotation
,Scale
等转换,也不能进行Alpha
透明度运算
所以有了TextureView
。
SurfaceTexture
是从Android3.0(API 11)
加入的一个新类,它的主要用途从一个图像流捕获帧数据作为OpenGL ES
的纹理。 如果updateTexImage()
被调用,根据内容流中最近的图像更新在创建SurfaceTexture
时指定的纹理对象的内容,接下来,就可以像操作普通GL纹理一样操作它了。
它对图像流的处理并不直接显示,而是转为GL外部纹理
,因此可用于图像流数据的二次处理(如Camera滤镜
,桌面特效等)。比如Camer
a的预览数据,变成纹理后可以交给GLSurfaceView
直接显示,也可以通过SurfaceTexture
交给TextureView
作为View heirachy
中的一个硬件加速层来显示。首先,SurfaceTexture
从图像流(来自Camera
预览,视频解码,GL绘制场景等)中获得帧数据,当调用updateTexImage()
时,根据内容流中最近的图像更新SurfaceTexture
对应的GL纹理对象
,接下来,就可以像操作普通GL纹理
一样操作它了。
SurfaceTexture
这个东西不是用来显示给用户看的, 它是一个Texture
, 意识是纹理, 可以想象成一个View
的中间件。
举个例子:
1 : Camera
把视频采集的内容交给 SurfaceView
, 就变成了照相机。
2:Camera
把视频采集的内容交给 SurfaceTexture
, SurfaceTexture
在对内容做个美颜, 然后SurfaceTexture
再把内容交给 SurfaceView
, 就变成了美图秀秀。
TextureView
,是Android4.0(API 14)
添加进来的,TextureView
专门用来渲染像视频或OpenGL
场景之类的数据,而且TextureView
只能用在具有硬件加速的Window
中,如果使用的是软件渲染,TextureView
什么也不显示。也就是说对于没有GPU
的设备,TextureView
完全不可用。
它可以将内容流直接投影到View
中,可以用于实现Live preview
等功能。和SurfaceView
不同,它不会单独创建窗口,而是作为View hierachy
中的一个普通View
,因此可以和其它普通View
一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView
必须在硬件加速的窗口中。
最后,总结下这几者的区别和联系。
SurfaceView
是一个有自己Surface
的View
。它的渲染可以放在单独线程而不是主线程中。其缺点是不能做变形和动画。
SurfaceTexture
可以用作非直接输出的内容流,这样就提供二次处理的机会。与SurfaceView
直接输出相比,这样会有若干帧的延迟。同时,由于它本身管理BufferQueue
,因此内存消耗也会稍微大一些。
TextureView
是一个可以把内容流作为外部纹理输出在上面的View
。它本身需要是一个硬件加速层。事实上TextureView
本身也包含了SurfaceTexture
。它与SurfaceView+SurfaceTexture
组合相比可以完成类似的功能(即把内容流上的图像转成纹理,然后输出)。区别在于TextureView
是在View hierachy
中做绘制,因此一般它是在主线程上做的(在Android 5.0
引入渲染线程后,它是在渲染线程中做的)。而SurfaceView+SurfaceTexture
在单独的Surface
上做绘制,可以是用户提供的线程,而不是系统的主线程或是渲染线程。另外,与TextureView
相比,它还有个好处是可以用Hardware overlay
进行显示。
public static native void glUniformMatrix4fv(
int location,
int count,
boolean transpose,
java.nio.FloatBuffer value
);
该函数的第一个参数是该变量在shader中的位置,第二个参数是被赋值的矩阵的数目(因为uniform变量可以是一个数组)。第三个参数表明在向uniform变量赋值时该矩阵是否需要转置。因为我们使用的是glm定义的矩阵,因此不要进行转置。但如果你正在使用一个数组来实现矩阵,并且这个矩阵是按行定义的,那么你就需要设置这个参数为GL_TRUE。最后一个参数就是传递给uniform变量的数据的指针了。
参考:纹理
1、着色器
2、program
3、获取各个参数
4、生成texture
int[] texture=new int[1];
GLES20.glGenTextures(1,texture,0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
//设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);
//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
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);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
5、user program
6、buffer position
7、glVertexAttribPointer
8、绑定texture
//激活0对应的texture
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//绑定0 texture和mTexture 这个是我们生存的2d纹理 已经有bitmap了
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture);
// 设置uTexture的sampler2D对应位置为0的纹理单元
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
画在两个纹理对象上
。例如:
//着色器中的纹理对象
int texture1;
int texture2;
int[] texture=new int[2];
GLES20.glGenTextures(2,texture,0);
... //给texture纹理单元数组赋予不同的bitmap等操作
//激活第0个纹理单元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//绑定第0个纹理单元,也可以去掉,如果没有针对这个纹理单元的操作
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
// 绑定texture1纹理对象和0 纹理单元
GLES20.glUniform1i(texture1, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[1]);
GLES20.glUniform1i(texture2, 1);
这个是正常的写法,这个时候,我们修改最后一句话:
GLES20.glUniform1i(texture2, 0);
这样,texture1和texture2就将会显示同样的纹理单元了,也就会显示同样的bitmap
了
纹理单元
是持有bitmap
数据的,可以被重复使用,glBindTexture
之后,就可以修改这个纹理单元
;纹理对象
这个是在着色器
中定义的,一般为sampler2D
,
我们调用glUniform1i
,通过给纹理对象
赋值纹理单元
,来显示bitmap
.
9、绘制
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