接下来探索纹理了。java
纹理,简单的理解就是一副图像。而把一副图像映射到图形上的过程,叫作纹理映射
。微信
好比有以下图形和三角形,想要把图形中的一部分映射到三角形上。函数
结果就是这样的:ui
这就是纹理映射的一个小小例子。spa
要注意到,OpenGL 绘制的物体是 3D 的,而纹理是 2D 的,那么纹理映射就是将 2D 的纹理映射到 3D 的物体上,能够想象成用一张纸裹着一个物体同样,不过要按照必定规律来。.net
OpenGL 中绘制的物体是有坐标系的,每一个点都对应 x、y、z 坐标,而纹理也有着它的坐标,只要 3D 物体中的每一个点都对应了 2D 纹理中的某个点,那么就能够把纹理映射到 3D 物体上去了。3d
纹理的坐标,叫作纹理坐标系
。它的范围只有 到
。code
它的坐标原点位于左下角,水平向右为 S 轴,竖直向上为 Y 轴。不论实际的纹理图片尺寸大小如何,横向、纵向坐标最大值都是 1 。orm
例如:实际图为 512 x 256 像素分辨率,则横向第 512 个像素对应纹理坐标为 1 ,纵向第 256 个像素对应纹理坐标为 1 。不过,纹理图最好是采用像素为 2 的 n 次方的纹理图。cdn
纹理映射的基本思想就是:首先为图元中的每一个顶点指定恰当的纹理坐标,而后经过纹理坐标在纹理图中能够肯定选中的纹理区域,最后将选中纹理区域中的内容根据纹理坐标映射到指定的图元上。
纹理映射在 OpenGL 的渲染管线上的体现:在渲染管线中,先进行顶点着色器,绘制出物体的大体形状,以后会进行光栅化
,将物体光栅化为许多片断组成,而后再进行片断着色器,将图形的每一个片断进行着色。
那么就须要在 顶点着色器 中将纹理的坐标传入,在光栅化阶段,纹理坐标将根据 顶点着色器 对它的处理以及 片断和各顶点的位置关系 插值产生,而后才是将插值计算后的结果传入到片断着色器中。
相比直接绘制图形,使用纹理后,着色器也要改变了。
顶点着色器:
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
uniform mat4 u_ModelMatrix;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjectionMatrix;
uniform mat4 u_Matrix;
void main() {
v_TextureCoordinates = a_TextureCoordinates ;
gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
}
复制代码
在顶点着色器中多了 v_TextureCoordinates
变量,它是 varying
类型,意思为可变类型,在光栅化处理时会对该变量进行处理,随后传入到片断着色器中。
片断着色器
precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;
void main(){
// 未使用纹理的颜色赋值 : gl_FragColor = u_Color;
gl_FragColor = texture2D(u_TextureUnit,v_TextureCoordinates);
}
复制代码
v_TextureCoordinates1
变量就是接受来自顶点着色器传的值,u_TextureUnit
变量就是使用的采样器,类型是sampler2D
。
使用纹理后的片断着色器要使用 texture2D
函数给颜色赋值。
texture2D
函数的做用就是采样,从纹理中采起像素赋值给 gl_FragColor
变量,也就是最后的颜色。
大体了解了着色器代码,接着就是上层的 Java 代码了。
和要建立一个 OpenGL ProgramId 相似,使用纹理也须要建立一个纹理 ID。
/** * 返回加载图像后的 OpenGl 纹理的 ID * @param context * @param resourceId * @return */
public static int loadTexture(Context context, int resourceId) {
final int[] textureObjectIds = new int[1];
glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
Timber.d("Could not generate a new OpenGL texture object.");
return 0;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
if (bitmap == null) {
Timber.d("resource Id could not be decoded");
glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
// 设置缩小的状况下过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// 设置放大的状况下过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载纹理到 OpenGL,读入 Bitmap 定义的位图数据,并把它复制到当前绑定的纹理对象
// 当前绑定的纹理对象就会被附加上纹理图像。
texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
// 为当前绑定的纹理自动生成全部须要的多级渐远纹理
// 生成 MIP 贴图
glGenerateMipmap(GL_TEXTURE_2D);
// 解除与纹理的绑定,避免用其余的纹理方法意外地改变这个纹理
glBindTexture(GL_TEXTURE_2D, 0);
return textureObjectIds[0];
}
复制代码
glGenTextures
建立纹理 ID。glDeleteTextures
删除并退出。glBindTexture
函数将纹理 ID 和纹理目标绑定。texImage2D
将纹理目标和 Bitmap 图片绑定。glGenerateMipmap
函数生成多级渐远纹理和 MIP 纹理贴图。glBindTexture
函数解除绑定。这里要重点说一下 glBindTexture 函数。
它的做用是绑定纹理名到指定的当前活动纹理单元,当一个纹理绑定到一个目标时,目标纹理单元先前绑定的纹理对象将被自动断开。纹理目标默认绑定的是 0 ,因此要断开时,也再将纹理目标绑定到 0 就行了。
因此在代码的最后调用了 glBindTexture(GL_TEXTURE_2D, 0)
来解除绑定。
当一个纹理被绑定时,在绑定的目标上的 OpenGL 操做将做用到绑定的纹理上,而且,对绑定的目标的查询也将返回其上绑定的纹理的状态。
也就是说,这个纹理目标成为了被绑定到它上面的纹理的别名,而纹理名称为 0 则会引用到它的默认纹理。因此,当后续对纹理目标调用 glTexParameteri
函数设置过滤方式,其实也是对纹理设置的过滤方式。
建立而且设置了纹理着色器ID以后,就须要绑定并设置在着色器语言中的变量了。
// 绑定着色器脚本中的变量
uTextureUnitAttr = glGetUniformLocation(mProgram, U_TEXTURE_UNIT)
mTextureId = TextureHelper.loadTexture(mContext,R.drawable.texture)
// 激活纹理单元
glActiveTexture(GL_TEXTURE0)
// 绑定纹理目标
glBindTexture(GL_TEXTURE_2D, mTextureId)
// 给片断着色器中的采样器变量 sample2D 赋值
glUniform1i(uTextureUnitAttr, 0)
复制代码
在着色器脚本中定义了 uniform
类型的采样器变量 sampler2D
,在上层的应用代码须要将它绑定并赋值。而 varying
类型的变量由顶点着色器传过去,不须要另外赋值了。
接下来要使用 glActiveTexture 函数激活纹理单元。在一个系统中,纹理单元的数据是有限的,在源码中从 GL_TEXTURE0 到 GL_TEXTURE31 共定义了三十二个纹理单元,但具体数量根据机型而定。
经过 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 常量能够查询到。
var intBuffer:IntBuffer = IntBuffer.allocate(1)
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,intBuffer)
LogUtil.d("max combined texture image units " + intBuffer[0])
复制代码
激活了纹理单元,还须要再绑定纹理目标。
一个纹理单元包含了多个类型纹理目标,如:GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP 等等。
由于纹理单元是纹理的一个别名,因此对纹理单元所作的操做,都至关于对纹理操做的。把一些对纹理所作的操做提取到函数里,最后再加载纹理,并绑定到纹理目标上。
使用glUniform1i
函数为采样器进行赋值为 0 ,这是和激活纹理单元相对应的。由于激活的纹理单元为 0 ,因此赋值也是为 0 。若是这里不一致,直接就看不到任何东西了。
当绑定并设置好片断着色器中的值以后,接下来的流程就和绘制基本图形同样了。
具体的绘制操做都在片断着色器里面定义了,而在上层代码中就不用花费不少心思了,在顶点着色器不变的状况下,甚至能够只改变片断着色器的值来绘制不一样的纹理效果。
在上面既是纹理单元又是纹理目标的很容易搞混,梳理一下概念:
形如 GL_TEXTURE0、GL_TEXTURE一、GL_TEXTURE2 的就是纹理单元,一台机子上纹理单元数量是有限的,依具体机型而定。而 glActiveTexture 则是激活具体的纹理单元。
一个纹理单元又包含多个类型的纹理目标,有:GL_TEXTURE_1D、GL_TEXTURE_2D、CUBE_MAP 等等。
经过 glGenTextures 函数生成的 int 类型的值就是纹理,经过 glBindTexture 函数将纹理目标和纹理绑定后,对纹理目标所进行的操做都反映到对纹理上。
纹理目标须要经过 texImage2D 函数附加上 Bitmap 位图。
最后,若是以为文章不错,欢迎关注微信公众号:【纸上浅谈】