# OpenGL 中的 Texture(纹理)

翻译自 Android 课程 3:基础纹理 英语好的小伙伴可以直接去这个网站学习 OpenGL ES2

# 纹理介绍

纹理映射技术是构建一个真实 3D 世界最重要的方式。没有纹理映射的话,所以的东西都是光滑的渐变,看起来像人造的,像是 90 年代的控制台游戏

首先大量使用了纹理技术的游戏,比如 Doom 和 Duke Nukem 3D,通过添加的视觉影响能极大的增强了游戏的真实性。

# 纹理坐标系

在 OpenGL 中,纹理坐标系使用(s,t)来代替(x,y)坐标,代表纹理上的点,最终映射到几何形上。另外需要注意的是纹理坐标系和其他的 OpenGL 坐标系一样,t(y)轴指向上,因此位置越高值越大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 (img-AiDSpFC1-1648314308409)(http://img4.imgtn.bdimg.com/it/u=2226136843,1693007895&fm=26&gp=0.jpg)]

# 纹理映射

在这课中注意看 2D 纹理 (GL_TEXTURE_2D)。OpenGL ES 也提供了其他的纹理模型让你做不同和专业的效果。

# 顶点着色器

从之前博客中的顶点着色器,增加一些修改:

1
2
3
4
5
6
7
attribute vec2 a_TexCoordinate;
...
varying vec2 v_TexCoordinate;
...

//将纹理坐标信息传递给片段着色器
v_TexCoordinate = a_TexCoordinate;

在顶点着色器中我们添加了一个新的 attribute 类型 2 维向量来携带纹理的坐标信息作为输入数据。这会是基于每个顶点的,就像是位置,颜色,法线数据一样。我们还添加了一个新的 varying 型的变量来将数据传递进片段着色器中,通过三角形表面线性插入。

# 片段着色器

1
2
3
4
5
6
7
8
9
10
uniform sampler2D u_Texture;
...
varing vec2 v_TexCoordinate;
..
diffuse = diffuse * (1.0/(1.0+(0.1.*distance)));
...

diffuse = diffuse + 0.3;

gl_FragColor = (c_Color*diffuse*texture2D(u_Texture,v_TexCoordinate));

我们添加了一个新的 uniform 类型 sampler2D 代表了实际的纹理数据 (),v_TexCoordinate 从顶点着色器中获取到了纹理坐标数据,之后我们调用 texture2D (texture,textureCoordinate) 来读出纹理在当前坐标处的值。获取到了这个值后将它与其它项相乘就得到了最后的输出颜色。这种方式添加的纹理会有时在整个环境中较暗,所以可以将环境光上调一些来减少光的衰减。

# 从一个图像中加载纹理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static int loadTexture(Bitmap bitmap){
int[] textureHandle = new int[1];
//由OpenGL ES生成一个纹理句柄并存放到textureHandle中
GLES20.glGenTextures(1,textureHandle,0);
if(textureHandle[0]!=0){
//将纹理绑定到OpenGL上
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureHandle[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

// Recycle the bitmap, since its data has been loaded into OpenGL.
bitmap.recycle();

}

if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}

return textureHandle[0];
}

传入一个 Bitmap 对象并将其加载到 OpenGL 中

# 定义纹理坐标

我们知道纹理坐标系左下是(0,0),而右上是(1,1),而我们的之前定义一个矩形是

-1,1,

-1,-1,

1,1,

-1,-1,

1,-1,

1,1

这样的顺序定义的,
我们的纹理坐标应该是怎么样的呢:

0f,0f,

0f,1f,

1f,0f,

0f,1f,

1f,1f,

1f,0f.

这个坐标的对应顺序可以自己在纸上画一下来增强理解.
(我个人理解和这个文章的作者不一样,在 Android 上,纹理坐标系好像是和 Android 的 2d 坐标系相同,Android 的 2D 坐标系是 y 轴向下为正,x 轴向右为正,坐标原点为左上,以左上角为 (0,0),所以将纹理映射到 OpenGL 坐标系中就是上面这样)

# 使用纹理

在 GLES20.glUseProgram 之后添加下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//纹理坐标句柄
mTextureHandle = GLES20.glGetAttribLocation(Program,"a_TexCoordinate");
//纹理采样器句柄
mTextureCoordHandle = GLES20.glGetUniformLocation(Program,"u_Texture");

//将纹理坐标传递进着色器程序
mTriangleTexture.position(0);
GLES20.glVertexAttribPointer(mTextureHandle,2 ,GLES20.GL_FLOAT,false,0,mTriangleTexture);
GLES20.glEnableVertexAttribArray(mTextureHandle);

//激活第一个纹理单元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//绑定纹理到指定的纹理通道,GL_TEXTURE_2D在着色器中就会使用simpler2D。
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mTextureDataHandle);
//只使用一个纹理的话给Simpler2D传0就可以
GLES20.glUniform1i(mTextureCoordHandle,0);