Opengl ES(五)绘制图像

Opengl ES基础系列文章是音视频学习的图形图像部分,旨在通过这部分学习,能够把Opengl ES和视频结合在一起,最终形成一个完整的知识体系。

什么是纹理?

前面我们一直提到一个概念:纹理,但是一直都没有用。这一节要显示图像,那么就要使用到纹理,在Opengl中纹理被定义成:可以被采样的复杂的数据集合,但这里我们先把纹理简单理解成一块连续的内存,本节就且把它看成一副图像。

在Opengl ES中我们使用最多的有两种类型的纹理:TEXTURE_2D和TEXTURE_EXTERNAL_OES,后面一种纹理主要用于摄像头数据采集,通过SurfaceTexture来转换得到,而摄像头采集获得的数据一般是YUV格式的,事实上,通过查看Opengl的源码我们知道,Android底层对TEXTURE_EXTERNAL_OES这种类型的纹理是经过特殊转换的,前一种纹理就是标准的RGB格式图像数据,这一节我们主要用到的是TEXTURE_2D的纹理,后一种在讲摄像头预览的时候会详细说明。

Opengl ES中怎样使用纹理?

首先看看使用纹理的片段着色器的代码:

varying vec2 vTextureCoor;

uniform simpler2D simpler;
void main(void)
{
gl_FragColor = texture2d(simpler, vTextureCoor);
}

从上面的片段着色器源码我们知道,这里声明了一个simpler2D变量,这代表一个二维纹理对象,在Opengl ES渲染过程中进行插值计算时会通过纹理坐标获取对应纹理坐标的颜色值,这里有一点要注意,纹理坐标vTextureCoor是一个vec2类型的变量,而且其值是通过顶点着色器透传过来的(不明白的可以参考前面章节介绍),下面我们详细介绍怎么样把渲染的图像数据映射到simpler2D的变量。

1)生成纹理对象

int texture[] = { 0 };
GLES20.glGenTextures(1, texture, 0);

GLES20.glGenTextures此方法允许我们一次生成多个纹理对象,其原型如下:

void glGenTextures(int n,int[] textures,int offset);

n:需要生成的纹理对象个数;

textures:保存生成所有纹理对象的数组;

offset:纹理对象数组的偏移(生成的纹理从数组什么位置开始赋值);

返回值:无;

2) 激活并绑定纹理

使用方法GLES20.glActiveTexture(GL_TEXTURE0)来激活对应的纹理单元,不同的系统实现的纹理单元可能是不一样的,在我的系统上,可用纹理单元个数为32,这一步就是告诉Opengl ES我们要使用这个纹理单元,此方法原型如下:

glActiveTexture(int texture);

texture:纹理单元标识,一般是由常量GL_TEXTURE0、GL_TEXTURE1...定义的,表示第几号纹理单元;

返回值:无;

前面章节我们说过,Opengl ES是居于状态的(状态保存在EGLContext中),调用GLES20.glActiveTexture后,就是告诉系统,以后我们对纹理的操作都是居于此纹理单元的。

接下来,使用GLES20.glBindTexture把我们生成的纹理索引和激活的纹理单元关联起来,这一步就是告诉系统以后我们操作纹理索引都是对应在纹理单元上的,GLES20.glBindTexture原型如下:

void glBindTexture(int target,int texture);

target:纹理单元目标类型,表示我们激活的纹理单元对应了什么样的操作类型,比如GL_TEXTURE_1D、GL_TEXTURE_EXTERNAL_OES等。

texture:纹理单元表示,一般是由常量GL_TEXTURE0、GL_TEXTURE1...定义的,表示第几号纹理单元;

返回值:无;

3) 设置纹理属性

绑定纹理后,我们就可以对绑定的纹理进行基本属性设置,比如放大、缩小时的处理逻辑,使用方法GLES20.glTexParameteri进行设置,其原型如下:

void glTexParameteri(int target,int pname,int param);

target:纹理单元目标类型,表示我们激活的纹理单元对应了什么样的操作类型,比如GL_TEXTURE_1D、GL_TEXTURE_EXTERNAL_OES等。

pname:属性名称;

param:属性值;

返回值:无;

4) 为纹理对象分配内存

创建纹理并激活后,接下来就需要给纹理分配存储空间,使用到的函数为GLES20.glTexImage2D,其原型如下:

void glTexImage2D(int target,int level,int internalformat,int width,int height,int border,int format,int type,java.nio.Buffer pixels);

target:活动纹理单元的目标类型,参考glBindTexture第一个参数;

level:缩略图的图像级别,用于图像缩小时图像质量改善;

internalformat:纹理单元中数据格式,GL_ALPHA,GL_LUMINANCE,GL_LUMINANCE_ALPHA,GL_RGB,GL_RGBA其中之一;

width:纹理图像的宽度;

height:纹理图像的高度;

border:指定边框的宽度,这里必须为0;

format:纹理数据的格式,必须匹配internalformat;

type:纹理数据的数据类型,GL_UNSIGNED_BYTE等值;

pixels:指向内存中的图像数据;

返回值:无;

5)把图像数据复制给纹理对象

其实在步骤3,我们也可以在创建纹理数据空间的同时给其赋值,但是如果后期需要更换纹理的渲染数据,还是通过这一步来完成对其赋值操作,使用到的函数为GLES20.glTexSubImage2D,其原型如下:

void glTexSubImage2D(int target,int level,int xoffset,int yoffset,int width,int height,int format,int type,java.nio.Buffer pixels);

target:活动纹理单元的目标类型,参考glBindTexture第一个参数;

level:缩略图的图像级别,用于图像缩小时图像质量改善;

xoffset:纹理数组中x方向的纹素偏移

yoffset:纹理数组中y方向的纹素偏移

width:纹理图像的宽度;

height:纹理图像的高度;

format:纹理数据的格式,必须匹配internalformat;

type:纹理数据的数据类型,GL_UNSIGNED_BYTE等值;

pixels:指向内存中的图像数据;

返回值:无;

6)纹理和片元着色器中的sample2D关联

经过上述步骤,图像数据已经准备好,最后需要把我们图像数据对应的纹理ID关联到片段着色器,这样在对片元进行着色的时候才知道从那里获取数据,这里使用glUniform1i来进行关联,其原型如下:

void glUniform1i(int location,int x);

location:要更改的uniform变量的位置。

x:用来更新指定的uniform变量;

返回值:无;

7)绘制纹理

glDrawArrays(GL_TRIANGLES, 0, count);数据都准备好后,调用此方法进行渲染

进过上面的步骤,我们就可以在屏幕上渲染一个简单的图像了,文章只是说明Opengl ES渲染图像的步骤,对于完整的实现大家可以自行下载对应的项目源码学习。

本系列文章均为原创,主要总结作者多年在软件行业的一些经验,和大家共同学习、进步,转载请注明出处,谢谢!

来源:CSDN,作者:会说话的小鱼,
原文:https://blog.csdn.net/china0851/article/details/85156167
版权声明:本文为博主原创文章,转载请附上博文链接!

最新文章