现实生活中,纹理(Texture)最通常的作用是装饰 3D 物体,它就像贴纸一样贴在物体表面,丰富了物体的表面和细节。
在 OpenGLES 开发中,纹理除了用于装饰物体表面,还可以用来作为存储数据的容器。
那么在 OpenGLES 中,纹理实际上是一个可以被采样的复杂数据集合,是 GPU 的图像数据结构,纹理分为 2D 纹理、 立方图纹理和 3D 纹理。
2D 纹理是 OpenGLES 中最常用和最常见的纹理形式,是一个图像数据的二维数组。纹理中的一个单独数据元素称为纹素或纹理像素。
立方图纹理是一个由 6 个单独的 2D 纹理面组成的纹理。立方图纹理像素的读取通过使用一个三维坐标(s,t,r)作为纹理坐标。
3D 纹理可以看作 2D 纹理作为切面的一个数组,类似于立方图纹理,使用三维坐标对其进行访问。
启用纹理映射功能后,如果想把一副纹理应用到相应的几何图元,就必须告知渲染系统如何进行纹理的映射。
告知的方式就是为图元中的顶点指定恰当的纹理坐标,纹理坐标用浮点数来表示,范围一般从0.0到1.0。
-
左侧是一幅纹理图,其位于纹理坐标系中。纹理坐标系原点在左上侧,向右为S轴,向下为T轴,两个轴的取值范围都是0.0~1.0。也就是说不论实际纹理图的尺寸如何,其横向、纵向坐标最大值都是1。若实际图为512像素×256像素,则横边第512个像素对应纹理坐标为1,竖边第256个像素对应纹理坐标为1,其他情况依此类推。
-
右侧是一个三角形图元,其3个顶点A、B、C都指定了纹理坐标,3组纹理坐标正好在右侧的纹理图中确定了需要映射的三角形纹理区域。
从上述两点可以看出,纹理映射的基本思想就是,首先为图元中的每个顶点指定恰当的纹理坐标,然后通过纹理坐标在纹理图中可以确定选中的纹理区域,最后将选中纹理区域中的内容根据纹理坐标映射到指定的图元上。
从上述两点可以看出,纹理映射的基本思想就是,首先为图元中的每个顶点指定恰当的纹理坐标,然后通过纹理坐标在纹理图中可以确定选中的纹理区域,最后将选中纹理区域中的内容根据纹理坐标映射到指定的图元上。
进行纹理映射的过程实际上就是为右侧三角形图元中的每个片元着色,用于着色的颜色需要从左侧的纹理图中提取,具体过程如下:
-
首先图元中的每个顶点都需要在顶点着色器中通过out变量将纹理坐标传入片元着色器。
-
经过顶点着色器后渲染管线的固定功能部分会根据情况进行插值计算,产生对应到每个片元的用于记录纹理坐标的out变量值。
-
最后每个片元在片元着色器中根据其接收到的记录纹理坐标的in变量值到纹理图中提取出对应位置的颜色即可,提取颜色的过程一般称为纹理采样。
在OpenGL中简单理解就是一张图片,在学习之前需要明白这几个概念,不然很容易迷糊,不知道为什么要这样去调用api,到底是什么意思.
-
纹理Id:句柄,纹理的直接引用
-
纹理单元:纹理的操作容器,有GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2等,纹理单元的数量是有限的,最多16个。所以在最多只能同时操作16个纹理。在切换使用纹理单元的时候,使用glActiveTexture方法。也就是说OpenGL ES中内置了很多个纹理单元,并且是连续的,我们在使用的时候要选择其中一个,一般默认选择第一个(GLES_TEXTURE0),并且如果不选的话OpenGL默认激活的也就是第一个纹理单元。采样器统一变量将加载一个指定纹理绑定的纹理单元的数值,例如,用数值0指定采样器表示从单元GL_TEXTURE0读取,指定数值1表示从GL_TEXTURE1读取,以此类推。激活纹理单元后需要把它和纹理Id绑定,然后再通过GLES30.glUniform1i()方法将纹理单元,与GLSL中的采样器属性相关联。
-
纹理目标:一个纹理单元中包含了多个类型的纹理目标,有GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP等。本章中,将纹理ID绑定到纹理单元0的GL_TEXTURE_2D纹理目标上,之后对纹理目标的操作都是对纹理Id对应的数据进行操作。
OpenGL要操作一个纹理,那么是将纹理ID装进纹理单元这个容器里,然后再通过操作纹理单元的方式去实现的。
这样的话,我们可以加载出很多很多个纹理ID(但要注意爆内存问题),但只有16个纹理单元,在Fragment Shader里最多同时能操作16个单元。
纹理贴图是在栅格化的模型表面上覆盖图像的技术。它是为渲染场景添加真实感的最基本和最重要的方法之一。 纹理贴图非常重要,因此硬件也为它提供了支持,使得它具备实现实时的照片级真实感的超高性能。纹理单元是专为纹理设计的硬件组件,现代显卡通常带有数个纹理单元。 为了在OpenGL/GLSL中有效地完成纹理贴图,需要协调好以下几个不同的数据集和机制: ·用于保存纹理图像的纹理对象(在本章中我们仅考虑2D图像); ·特殊的统一采样器变量,以便顶点着色器访问纹理; ·用于保存纹理坐标的缓冲区; ·用于将纹理坐标传递给管线的顶点属性; ·显卡上的纹理单元。
渐变色:光栅化过程中计算出颜色值,然后在使用片段着色器的时候可以直接赋值。
纹理:光栅化过程中,计算出当前片段在纹理上的坐标位置,然后在片段着色器中根据这个纹理上的坐标,去纹理中取出相应的颜色值。
OpenGL中,2D纹理也有自己的坐标体系,取值范围在(0,0)到(1,1)内,两个维度分别为S、T,所以一般称为ST纹理坐标。
有些时候也叫UV坐标。
纹理左边的方向性和Android上的视图坐标系一致,都是顶点在左上角,范围为0到1之间。
纹理上的每个顶点与定点坐标上的顶点一一对应。如下图,左边是顶点坐标,右边是纹理坐标,只要两个坐标的ABCD定义顺序一致,就可以正常地映射出对应的图形。顶点坐标内光栅化后的每个片段,都会在纹理坐标内取得对应的颜色值。
在OpenGL中,纹理实际上是一个可以被采样的复杂数据集合,是GPU使用的图像数据结构,纹理分为2D纹理、立方图纹理和3D纹理。
2D纹理是OpenGLES中最常用和最常见的纹理形式,是一个图像数据的二维数组。纹理中的一个单独数据元素称为纹素或纹理像素。
立方图纹理是一个由6个单独的纹理面组成的纹理。立方图纹理像素的读取通过使用一个三维坐标(s,t,r)作为纹理坐标。
3D纹理可以看作2D纹理作为切面的一个数组,类似于立方图纹理,使用三维坐标对其进行访问。
在OpenGLES中,纹理映射就是通过为图元的顶点坐标指定恰当的纹理坐标,通过纹理坐标在纹理图中选定特定的纹理区域,最后通过纹理坐标与顶点的映射关系,将选定的纹理区域映射到指定图元上。
纹理映射也称为纹理贴图,简单地说就是将纹理坐标(纹理坐标系)所指定的纹理区域,映射到顶点坐标(渲染坐标系或OpenGLES 坐标系)对应的区域。
纹理坐标系:
渲染坐标系或OpenGLES 坐标系:
4个纹理坐标分别为:
T0(0,0),T1(0,1),T2(1,1),T3(1,0)
4个纹理坐标对于的顶点坐标分别为:
V0(-1,0.5),V1(-1, -0.5),V2(1,-0.5),V3(1,0.5)
由于OpenGLES绘制是以三角形为单位的,设置绘制的2个三角形为V0V1V2和V0V2V3。
当我们调整纹理坐标的顺序顶点坐标顺序不变,如T0T1T2T3 -> T1T2T3T0,绘制后将得到一个顺时针旋转90度的纹理贴图。
所以调整纹理坐标和顶点坐标的对应关系可以实现纹理图简单的旋转。
纹理坐标是对纹理图像(通常是2D图像)中的像素的引用。纹理图像中的像素被称为纹元(texel),以便将它们与在屏幕上呈现的像素区分开。纹理坐标用于将3D模型上的点映射到纹理中的位置。除了将它定位在3D空间中的坐标(x,y,z)之外,模型表面上的每个点还具有纹理坐标(s,t),用来指定纹理图像中的哪个纹元为它提供颜色。这样,物体的表面被按照纹理图像“涂画”。纹理在对象表面上的朝向由分配给对象顶点的纹理坐标确定。 要使用纹理贴图,必须为要添加纹理的对象中的每个顶点提供纹理坐标。OpenGL将使用这些纹理坐标,查找存储在纹理图像中的引用的纹元的颜色,来确定模型中每个栅格化像素的颜色。为了确保渲染模型中的每个像素都使用纹理图像中的适当纹元进行绘制,纹理坐标也需要被放入顶点属性中,以便由光栅着色器进行插值。
OpenGL不能直接加载jpg或者png这类被编码的压缩格式,需要加载原始数据,也就是bitmap。
我们在内置图片到工程中,应该将图片放到drawable-nodpi中,避免读取时被压缩,通过BitmapFactory解码读取图片时,要设置为非缩放的方式,即options.isScaled=false。
纹理坐标不依赖于分辨率,它可以是任意浮点值,所以OpenGL需要知道怎样将纹理像素映射到纹理坐标。
当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。
当我们通过光栅化将图形处理成一个个小片段的时候,再将纹理采样,渲染到指定位置上时,通常会遇到纹理元素和小片段并非一一对应。
这时候,会出现纹理的压缩或者放大。那么在这两种情况下,就会有不同的处理方案,这就是纹理过滤了。
纹理过滤有很多选项,这里只说最重要的两项: GL_NEAREST和GL_LINEAR。
-
GL_NEAREST: 临近过滤,是OpenGL默认的纹理过滤方式,会选择中心点最接近纹理坐标的那个像素。
-
GL_LINEAR: 线性过滤,会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。
如果在一个很大的物体上应用一张低分辨率的纹理会发生什么呢(纹理被放大了,每个纹理像素都能看到)?
GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素。
而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。GL_LINEAR可以产生更真实的输出。
纹理应用的第一步是创建一个纹理对象。纹理对象是一个容器对象,保存渲染所需的纹理数据,例如图像数据、过滤模式和包装模式。
在OpenGL ES中,纹理对象用一个无符号整数表示,该整数是纹理对象的一个句柄,用于生成纹理对象的函数是glGenTextures。
-
glGenTextures(GLsizei n, GLunit *textures)
生成一个空的纹理对象,参数n是要生成的纹理对象的数量,textures是一个保存n个纹理对象ID的无符号整数数组。
-
glBindTexture(GLenum target, GLunit texture)
将纹理对象绑定纹理目标,绑定目标后的下一个步骤是真正的加载图像数据。参数target是GL_TEXTURE_2D GL_TEXTURE_3D GL_TEXTURE_2D_ARRAY GL_TEXTURE_CUBE_MAP等目标。texture是要绑定的纹理对象句柄。
-
glTexImage2D(GLenum target, GLinit level, GLenum internalFormat, GLsizei width, GLsizei height, GLinit boder,
GLenum format, GLenum type, const void* pixels)
加载2D和立方图纹理图像数据,生成最终纹理。
下面是一个工具类方法,相对通用,能解决大部分需求,这个方法可以将内置的图片资源加载出对应的纹理ID。
public class TextureUtil {
private static final String TAG = "TextureHelper";
public static int createOESTextureId(){
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
GLES20.glTexParameterf(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR
);
GLES20.glTexParameterf(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR
);
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE
);
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE
);
return textures[0];
}
/**
* 根据资源ID获取相应的OpenGL纹理ID,若加载失败则返回0
* <br>必须在GL线程中调用
*/
public static TextureBean loadTexture(Context context, int resourceId) {
TextureBean bean = new TextureBean();
final int[] textureObjectIds = new int[1];
// 1. 创建纹理对象
GLES20.glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
Log.w(TAG, "Could not generate a new OpenGL texture object.");
return bean;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(
context.getResources(), resourceId, options);
if (bitmap == null) {
Log.w(TAG, "Resource ID " + resourceId + " could not be decoded.");
// 加载Bitmap资源失败,删除纹理Id
GLES20.glDeleteTextures(1, textureObjectIds, 0);
return bean;
}
// 2. 将纹理绑定到OpenGL对象上
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureObjectIds[0]);
// 3. 设置纹理过滤参数:解决纹理缩放过程中的锯齿问题。若不设置,则会导致纹理为黑色
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
// 4. 通过OpenGL对象读取Bitmap数据,并且绑定到纹理对象上,之后就可以回收Bitmap对象
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// Note: Following code may cause an error to be reported in the
// ADB log as follows: E/IMGSRV(20095): :0: HardwareMipGen:
// Failed to generate texture mipmap levels (error=3)
// No OpenGL error will be encountered (glGetError() will return
// 0). If this happens, just squash the source image to be
// square. It will look the same because of texture coordinates,
// and mipmap generation will work.
// 5. 生成Mip位图
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
// 6. 回收Bitmap对象
bean.setWidth(bitmap.getWidth());
bean.setHeight(bitmap.getHeight());
bitmap.recycle();
// 7. 将纹理从OpenGL对象上解绑,现在OpenGL已经完成了纹理的加载,不需要再绑定此纹理了,后面使用此纹理时通过纹理对象的ID即可
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
// 所以整个流程中,OpenGL对象类似一个容器或者中间者的方式,将Bitmap数据转移到OpenGL纹理上
bean.setTextureId(textureObjectIds[0]);
return bean;
}
/**
* 纹理数据
*/
public static class TextureBean {
private int mTextureId;
private int mWidth;
private int mHeight;
public int getTextureId() {
return mTextureId;
}
void setTextureId(int textureId) {
mTextureId = textureId;
}
public int getWidth() {
return mWidth;
}
public void setWidth(int width) {
mWidth = width;
}
public int getHeight() {
return mHeight;
}
public void setHeight(int height) {
mHeight = height;
}
}
}
上面的工具类要求图片必须是2次幂的尺寸,不过大部分情况下应该不会有问题。
-
顶点着色器
将之前颜色向量
vec4 aColor
变为了纹理向量vec2 aTextureCoord
;#version 300 es layout (location = 0) in vec4 vPosition; layout (location = 1) in vec2 aTextureCoord; uniform mat4 u_Matrix; //输出纹理坐标(s,t) out vec2 vTexCoord; void main() { gl_Position = u_Matrix*vPosition; gl_PointSize = 10.0; vTexCoord = aTextureCoord; }
-
片段着色器
片段着色器也应该能访问纹理对象,但是我们怎样能把纹理对象传给片段着色器呢?
GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1D、sampler3D,或在我们的例子中的sampler2D。
我们可以简单声明一个uniform sampler2D把一个纹理添加到片段着色器中,稍后我们会把纹理赋值给这个uniform。
我们使用GLSL内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。
texture函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。
之前直接输出顶点着色器来的颜色,现在变为经过纹理处理最终成为输出颜色。
#version 300 es
precision mediump float;
// 采样器(sampler)是用于从纹理贴图读取的特殊统一变量。采样器统一变量将加载一个指定纹理绑定的纹理单元额数据,java代码里面需要把它设置为纹理单元对应的index值。
uniform sampler2D uTextureUnit;
//接收刚才顶点着色器传入的纹理坐标(s,t)
in vec2 vTexCoord;
out vec4 vFragColor;
void main() {
// 100 es版本中是texture2D,texture函数会将传进来的纹理和坐标进行差值采样,输出到颜色缓冲区。
vFragColor = texture(uTextureUnit,vTexCoord);
}
为了最大限度地提高性能,我们希望在硬件中执行纹理处理。这意味着片段着色器需要一种访问我们在C++/OpenGL应用程序中创建的纹理对象的方法。它的实现机制是通过一个叫作统一采样器变量的特殊GLSL工具。这是一个变量,用于指示显卡上的纹理单元,从加载的纹理对象中提取或“采样”纹元。 要实际执行纹理处理,我们需要修改片段着色器输出颜色的方式。以前,我们的片段着色器要么输出一个固定的颜色常量,要么从顶点属性获取颜色,而这次我们需要使用从顶点着色器(通过光栅着色器)接收的插值纹理坐标来对纹理对象进行采样。调用texture()函数如下:
in vec2 tc; // 纹理坐标
...
color = texture(samp, tc);
- render实现类
区别就是把前面绘制矩形时的color部分换成了纹理,其他都是一样的。
纹理的绘制:
//激活纹理单元,设置当前活动的纹理单元为单元0
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
//绑定纹理,将纹理id绑定到当前活动的纹理单元上
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
// 将纹理单元传递片段着色器的采样器u_TextureUnit,0就代表GL_TEXTURE0, 1代表GL_TEXTURE1,以此类推
GLES20.glUniform1i(aTextureLocation, 0);
这里有没有很奇怪,sampler2D的变量是uniform的,但是我们并不是用glUniform()方法给他赋值,而是使用glUniform1i()。
这是因为可以给纹理采样器分配一个位置值,这样我们就能够在一个片段着色器中设置多个纹理单元。一个纹理的话,纹理单元是默认为0,它是默认激活的,纹理单元的主要目的就是给着色器多一个使用的纹理。
通过纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们在使用的时候激活纹理。
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTURE15。
它们都是按顺序定义的,所以我们也可以通过GL_TEXUTURE0 + 8的方式获得GL_TEXTURE8,这在需要循环一些纹理单元的时候会很有用。
public class TextureRender extends BaseGLSurfaceViewRenderer {
private final FloatBuffer vertextBuffer;
private final FloatBuffer textureBuffer;
private int textureId;
private int aPositionLocation;
private int aTextureLocation;
private int uSamplerTextureLocation;
/**
* 坐标占用的向量个数
*/
private static final int POSITION_COMPONENT_COUNT = 2;
// 逆时针顺序排列
private static final float[] POINT_DATA = {
-0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f,
0.5f, -0.5f,
};
/**
* 颜色占用的向量个数
*/
private static final int TEXTURE_COMPONENT_COUNT = 2;
// 纹理坐标(s, t),t坐标方向和顶点y坐标反着
private static final float[] TEXTURE_DATA = {
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
public TextureRender() {
vertextBuffer = BufferUtil.getFloatBuffer(POINT_DATA);
textureBuffer = BufferUtil.getFloatBuffer(TEXTURE_DATA);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
handleProgram(MyApplication.getInstance(), R.raw.texture_vertex_shader, R.raw.texture_fragment_shader);
aPositionLocation = glGetAttribLocation("vPosition");
aTextureLocation = glGetAttribLocation("aTextureCoord");
uSamplerTextureLocation = glGetUniformLocation("uTextureUnit");
textureId = TextureUtil.loadTexture(MyApplication.getInstance(), R.drawable.img).getTextureId();
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertextBuffer);
glVertexAttribPointer(aTextureLocation, TEXTURE_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, textureBuffer);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
orthoM("u_Matrix", width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
glClear(GLES30.GL_COLOR_BUFFER_BIT);
/****************/
// 中间这一部分代码的意思: 采样器统一变量将加载一个指定纹理绑定的纹理单元的数值,
// 例如,用数值0指定采样器表示从单元GL_TEXTURE0读取,指定数值1表示从GL_TEXTURE1读取,以此类推。
// 激活纹理单元后需要把它和纹理Id绑定,然后再通过GLES30.glUniform1i()方法传递给着色器中。
// 下面这两句表示纹理如何绑定到纹理单元。设置当前活动的纹理单元为纹理单元0,并将纹理ID绑定到当前活动的纹理单元上
glActiveTexture(GLES30.GL_TEXTURE0);
glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
// 将采样器绑定到纹理单元,0就表示GLES30.GL_TEXTURE0
glUniform1i(uSamplerTextureLocation, 0);
/****************/
glEnableVertexAttribArray(aPositionLocation);
glEnableVertexAttribArray(aTextureLocation);
glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, POINT_DATA.length / POSITION_COMPONENT_COUNT);
glDisableVertexAttribArray(aPositionLocation);
glDisableVertexAttribArray(aTextureLocation);
}
}
- 单纹理单元,多次绘制
多次调用glDrawArrays绘制纹理顶点的方式来实现,这样就是一张一张的按先后顺序,一层一层的绘制到当前的一帧画面。
着色器与上面的完全一致,唯一不同的是要提供两个顶点位置的坐标,然后分别设置这两个坐标,并绑定两次纹理,然后调用两次glDrawArrays进行绘制。
public class MultiTextureRender extends BaseGLSurfaceViewRenderer {
private final FloatBuffer vertextBuffer;
private final FloatBuffer vertextBuffer2;
private final FloatBuffer textureBuffer;
private int textureId;
private int textureId2;
private int aPositionLocation;
private int aTextureLocation;
private int uSamplerTextureLocation;
/**
* 坐标占用的向量个数
*/
private static final int POSITION_COMPONENT_COUNT = 2;
// 逆时针顺序排列
private static final float[] POINT_DATA = {
-1f, -1f,
-1f, 1f,
1f, 1f,
1f, -1f,
};
private static final float[] POINT_DATA2 = {
-0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f,
0.5f, -0.5f,
};
/**
* 颜色占用的向量个数
*/
private static final int TEXTURE_COMPONENT_COUNT = 2;
// 纹理坐标(s, t),t坐标方向和顶点y坐标反着
private static final float[] TEXTURE_DATA = {
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
public MultiTextureRender() {
vertextBuffer = BufferUtil.getFloatBuffer(POINT_DATA);
vertextBuffer2 = BufferUtil.getFloatBuffer(POINT_DATA2);
textureBuffer = BufferUtil.getFloatBuffer(TEXTURE_DATA);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
handleProgram(MyApplication.getInstance(), R.raw.texture_vertex_shader, R.raw.texture_fragment_shader);
aPositionLocation = glGetAttribLocation("vPosition");
aTextureLocation = glGetAttribLocation("aTextureCoord");
uSamplerTextureLocation = glGetUniformLocation("uTextureUnit");
textureId = TextureUtil.loadTexture(MyApplication.getInstance(), R.drawable.img).getTextureId();
textureId2 = TextureUtil.loadTexture(MyApplication.getInstance(), R.drawable.img).getTextureId();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
orthoM("u_Matrix", width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
glClear(GLES30.GL_COLOR_BUFFER_BIT);
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertextBuffer);
glVertexAttribPointer(aTextureLocation, TEXTURE_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, textureBuffer);
/****************/
// 中间这一部分代码的意思: 采样器统一变量将加载一个指定纹理绑定的纹理单元的数值,
// 例如,用数值0指定采样器表示从单元GL_TEXTURE0读取,指定数值1表示从GL_TEXTURE1读取,以此类推。
// 激活纹理单元后需要把它和纹理Id绑定,然后再通过GLES30.glUniform1i()方法传递给着色器中。
// 下面这两句表示纹理如何绑定到纹理单元。设置当前活动的纹理单元为纹理单元0,并将纹理ID绑定到当前活动的纹理单元上
glActiveTexture(GLES30.GL_TEXTURE0);
glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
// 将采样器绑定到纹理单元,0就表示GLES30.GL_TEXTURE0
glUniform1i(uSamplerTextureLocation, 0);
/****************/
glEnableVertexAttribArray(aPositionLocation);
glEnableVertexAttribArray(aTextureLocation);
// 画第一次
glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, POINT_DATA.length / POSITION_COMPONENT_COUNT);
//设置第二个纹理的坐标数据
GLES30.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT,
false, 0, vertextBuffer2);
//绑定纹理,前面已经激活了,就不用再调了
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId2);
// 画第二次
glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, POINT_DATA2.length / POSITION_COMPONENT_COUNT);
glDisableVertexAttribArray(aPositionLocation);
glDisableVertexAttribArray(aTextureLocation);
}
- 多纹理单元,单次绘制
OpenGL可以同时操作的纹理单元是16个,那么我们可以利用多个纹理单元来进行绘制同一个图层,从而达到目的。多纹理单元需要修改片段着色器的代码,顶点着色器不用改变,这种方式的优点是可以控制多个纹理的关系,做出复杂的效果。缺点是多个纹理单元的顶点坐标必须是一样的。
片段着色器代码增加一个纹理采样器(multi2_texture_fragment_shader.glsl):
#version 300 es
precision mediump float;
// 采样器(sampler)是用于从纹理贴图读取的特殊统一变量。采样器统一变量将加载一个指定纹理绑定的纹理单元额数据,java代码里面需要把它设置为0
uniform sampler2D uTextureUnit;
uniform sampler2D uTextureUnit2;
// 接收刚才顶点着色器传入的纹理坐标(s, t)
in vec2 vTexCoord;
out vec4 vFragColor;
void main() {
// 100 es版本中是texture2D,texture函数会将传进来的纹理和坐标进行差值采样,输出到颜色缓冲区。
vec4 texture1 = texture(uTextureUnit, vTexCoord);
vec4 texture2 = texture(uTextureUnit2, vTexCoord);
if (texture1.a != 0.0) {
vFragColor = texture1;
} else {
vFragColor = texture2;
}
}
相关功能介绍:
-
窗口,Window:主窗口,用于界面显示
-
渲染,Render:GPU,硬件加速
-
纹理,texture:存储描述信息
-
画布,surface:内存中的一块地址,存储像素数据
-
几何着色器
几何着色器阶段位于曲面细分着色器和栅格化之间,位于用于图元处理的管线内。 顶点着色器允许一次操作一个顶点,而片段着色器一次可以操作一个片段(实际上是一个像素),但几何着色器却可以一次操作一个图元。
纹理色彩通道的灵活组合。通过其可以对输入的RGBA纹理颜色分量如何映射到着色器中对应的颜色分量进行细节控制。实现纹理色彩通道的灵活组合主要是通过glTexParameterf方法进行设置。
实际设置时,glTexParameterf方法的第一个参数一般为“GLES30.GL_TEXTURE_2D”,表示对2D纹理进行设置。第二个参数有4种选择,具体含义如下所列:
- GL_TEXTURE_SWIZZLE_R表示映射到着色器中对应采样器的红色通道。
- GL_TEXTURE_SWIZZLE_G表示映射到着色器中对应采样器的绿色通道。
- GL_TEXTURE_SWIZZLE_B表示映射到着色器中对应采样器的蓝色通道。
- GL_TEXTURE_SWIZZLE_A表示映射到着色器中对应采样器的透明度通道。
第三个参数则有6种选择,具体含义如下所列:
- GL_RED表示是映射纹理图中的红色通道。
- GL_GREEN表示是映射纹理图中的绿色通道。
- GL_BLUE表示是映射纹理图中的蓝色通道。
- GL_ALPHA表示是映射纹理图中的透明度通道。
- GL_ONE表示采用1值进行映射。
- GL_ZERO表示采用0值进行映射。
从OpenGL ES 3.0开始支持的纹理类型——2D纹理数组。通过使用2D纹理数组,在同一个着色器中需要使用多个2D纹理的情况下可以简化开发。可以想象出,如果没有2D纹理数组技术,当一个着色器中需要使用多个2D纹理时就需要声明多个采样器变量。
而使用2D纹理数组实现,同样功能时只需声明一个sampler2DArray类型的变量即可,方便高效。对于2D纹理数组进行采样时,需要提供的纹理坐标有3个分量,前两个与普通2D纹理坐标含义相同,为S、T分量,第三个分量则为2D纹理数组中的索引。
需要注意的是,使用2D纹理数组时,一般会使数组中的纹理保持相同的尺寸。
需要注意的是,glBindTexture方法和glTexParameterf方法的第一个参数由原来的GLES30.GL_TEXTURE_2D变为GLES30.GL_TEXTURE_2D_ARRAY。
- 邮箱 :charon.chui@gmail.com
- Good Luck!