OpenGL ES 之 2D 纹理介绍和使用

一、纹理概念

纹理可以简单理解为物体表面的图案,OpenGL ES 3.0 中纹理有:2D纹理、2D纹理数组、3D纹理和立方图纹理。一个纹理的单独数据元素称为“纹素”(texture pixel 纹理像素的缩写)。

1. 纹理的坐标系

在这里插入图片描述

2. 2D纹理

2D纹理是最基本和常用的纹理,可以把2D纹理想象为一个图像数据的二维数组。2D纹理的纹理坐标用一对2D坐标 (s, t) 指定,有时也称作 (u, v) 坐标。

纹理图像坐标的左下角为 (0.0, 0.0),右上角为 (1.0, 1.0)。在 [0.0, 1.0] 区间之外的坐标也是允许的,在区间外的纹理读取行为由纹理包装模式定义。

3. 2D纹理数组

2D纹理数组常用于存储2D图像的一个动画,数组的每个切片表示纹理动画的一帧。2D纹理数组使用与3D纹理一样的纹理坐标 (s, t, r),其中 r 坐标选择 2D 纹理数组中要使用的切片,(s, t) 坐标选择的方法和 2D 纹理完全一致。

二、纹理对象和纹理加载

1. 创建纹理

首先我们需要创建一个纹理对象,纹理对象就是一个容器,包含渲染所需的纹理数据,例如图像数据、过渡模式、包装模式等。

在 OpenGL ES 中,纹理对象使用一个无符号的整数表示,该整数即纹理对象的一个句柄/ID。

/**
 * @param n 指定要生成的纹理对象数量
 * @param textures 一个保存n个纹理对象ID的无符号整数数组
 */
void glGenTextures(GLsizei n, GLuint *textures);

2. 删除纹理

纹理对象在不再需要时必须删除,这一步骤通常在应用程序关闭或者游戏级别改变时完成。

/**
 * @param n 要删除的纹理对象数量
 * @param textures 一个保存n个纹理对象ID的无符号整数数组
 */
void glDeleteTextures(GLsizei n, const GLuint *textures);

3. 绑定纹理

当我们生成并得到纹理对象ID后,如果想对其进行操作,就必须先绑定这个纹理对象,这样后续的操作影响的僵尸我们绑定的这个纹理对象。

/**
 * @param target 目标纹理,即 GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_2D_ARRAY 或 GL_TEXTURE_CUBE_MAP
 * @param texture 要绑定的纹理对象ID
 */
void glBindTexture(GLenum target, GLuint texture);

4. 解绑纹理

当我们不想再操作这个纹理对象时,可以解绑这个纹理。绑定 0 就是解绑了,同样是使用 glBindTexture 函数,参数传入固定的值 0 即可。

5. 加载图像数据

用于加载2D和立方图纹理的基本函数是 glTexImage2D。此外在 3.0 中还有多种替代方法指定 2D 纹理,如不可变纹理 glTexStorage2DglTexSubImage2D 的结合,将在后续介绍。

/**
 * @param target 目标纹理,即 GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_2D_ARRAY 或 GL_TEXTURE_CUBE_MAP
 * @param level 指定要加载的mip级别。第一个级别为0,后续的mip贴图级别递增
 * @param internalformat 纹理存储的内部格式
 * @param width 图像的像素宽度
 * @param height 图像的像素高度
 * @param border 在OpenGL ES中忽略,保留是为了兼容桌面的OpenGL,传入0
 * @param format 输入的纹理数据格式
 * @param type 输入像素数据的类型
 * @param pixels 包含图像的实际像素数据。像素行必须对齐到用glPixelStroei设置的GL_UNPACK_ALIGNMENT
 */
void glTexImage2D(
        GLenum target, GLint level, GLint internalformat,
        GLsizei width, GLsizei height, GLint border,
        GLenum format, GLenum type, const GLvoid *pixels)

关于纹理格式的介绍,可以参考:
https://blog.csdn.net/afei__/article/details/96158388

6. 相关设置

纹理的相关设置大都是通过 glTexParameter[i|f][v] 接口指定。这里介绍两个最常见的设置。

纹理过滤模式

纹理过滤模式,简单的说就是当纹理需要放大和缩小时,应该怎么处理。有关纹理过滤模式的介绍,可以参考:https://blog.csdn.net/afei__/article/details/96484772

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 缩小的情况
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 放大的情况
纹理坐标包装

前面介绍过,纹理坐标系是 [0, 0, 1, 1],那么当坐标超过这个范围时,应该发生怎样的行为。

纹理坐标包装有三种不同的模式:

  • GL_REPEAT : 重复纹理
  • GL_CLAMP_TO_EDGE : 限定读取纹理的边缘
  • GL_MIRRORED_REPEAT : 重复纹理并镜像
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // s轴
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // t轴

7. 示例

创建一个RGB图像纹理为例:

// 纹理对象的句柄
GLuint textureId;
// 一个2*2的图像,四个像素依次为红、绿、蓝、黄,每个像素大小为3个字节
GLubyte pixels[4 * 3] = {
    255, 0, 0, // Red
    0, 255, 0, // Green
    0, 0, 255, // Blue
    255, 255, 0 // Yellow
};
// 创建一个纹理对象
glGenTextures(1, &textureId);
// 绑定纹理对象
glBindTexture(GL_TEXTURE_2D, textureId);
// 加载纹理
glTexImage2D(
        GL_TEXTURE_2D, // target
        0, // level
        GL_RGB, // internalformat
        2, // width
        2, // height
        0, // border
        GL_RGB, // format
        GL_UNSIGNED_BYTE, // type
        pixels); // pixels
// 设置纹理过滤模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 设置纹理坐标包装
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

上述数据由无符号字节 RGB 三元组组成,范围为 [0, 255]

三、使用纹理

上面我们已经了解到如何创建和加载一个纹理,并得到这样一个纹理id。那么如何在代码中使用这个纹理呢?通过一个实例来一步步了解吧。

1. 着色器脚本

顶点着色器

很简单的一个顶点着色器,仅仅把输入的数据再传给片段着色器即可。

#version 300 es
layout(location = 0) in vec4 vPosition; // 输入的顶点坐标
layout(location = 1) in vec2 vTexCoor; // 输入的纹理坐标
out vec2 v_texCoor; // 输出到片段着色器的纹理坐标
void main() {
    gl_Position = vPosition;
    v_texCoor = vTexCoor;
}
片段着色器

使用纹理的真正地方,片段着色器中,使用到的内建函数 texture 作用是从纹理中指定位置读取一个颜色的 vec4。

#version 300 es
precision mediump float; // 指定精度
uniform sampler2D s_texture; // 采样器对象,当前绑定和激活的纹理
in vec2 v_texCoor; // 顶点着色器中输出的纹理坐标
out vec4 fragColor; // 该片段的颜色
void main() {
        fragColor = texture ( s_texture, v_texCoor ); // 在对应纹理坐标处进行采样并得到对应的颜色
}

2. 创建一个纹理对象

就使用上面的那个示例即可

GLuint loadTexture() {
    GLuint textureId;
    // 2*2 RGB data for test
    GLfloat pixels[4 * 3] = {
            1.0, 0.0, 0.0, // Red
            0.0, 1.0, 0.0, // Green
            0.0, 0.0, 1.0, // Blue
            1.0, 1.0, 0.0 // Yellow
    };
    // 创建一个纹理对象
    glGenTextures(1, &textureId);
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    // 加载数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_FLOAT, pixels);
    // 相关设置
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 解除绑定
    glBindTexture(GL_TEXTURE_2D, 0);
    // 返回纹理id
    return textureId;
}

3. 使用这个纹理对象

我们以绘制一个矩形为例,即绘制两个三角形。

// 1. 创建一个程序(这里省略了详细代码,可在最后的工程地址中找到)
GLuint g_program = CreateProgram(vertexShaderSource, fragmentShaderSource);
glUseProgram(g_program);

// 2. 初始化我们的顶点数据,即传给顶点着色器的数据
// OpenGL的世界坐标系是 [-1, -1, 1, 1],纹理的坐标系为 [0, 0, 1, 1]
// 又由于本例运行在安卓设备上,在安卓中 y 轴是向下的,想用正确的方向观看图像的话,也要注意这一点
GLfloat vertices[] = {
        // 前三个数字为顶点坐标(x, y, z),后两个数字为纹理坐标(s, t)
        // 第一个三角形
        1.0,  1.0,  0.0,     1.0, 0.0,
        1.0,  -1.0, 0.0,     1.0, 1.0,
        -1.0, -1.0, 0.0,     0.0, 1.0,
        // 第二个三角形
        1.0,  1.0,  0.0,     1.0, 0.0,
        -1.0, -1.0, 0.0,     0.0, 1.0,
        -1.0, 1.0,  0.0,     0.0, 0.0
};

// 3. 加载纹理
GLuint textureId = loadTexture();
glActiveTexture(GL_TEXTURE0); // 激活TEXTURE0
glBindTexture(GL_TEXTURE_2D, textureId);
// 找到片段着色器中s_texture的位置,并给它赋值
GLint location = glGetUniformLocation(g_program, "s_texture");
glUniform1i(location, 0); // 因为激活的是TEXTURE0,所以要给这个纹理赋值0

// 4. 加载顶点数据
// 给vPosition赋值,它的location是0,然后size是3,stride是5个float,起始指针是vertices
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), vertices);
glEnableVertexAttribArray(0);
// 给vTexCoor赋值,它的location是1,然后size是2,stride是5个float,起始指针是vertices+3
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), vertices + 3);
glEnableVertexAttribArray(1);

// 5. 绘制
glDrawArrays(GL_TRIANGLES, 0, 6); // 绘制两个三角形,一共是6个点

4. 效果图

由于放大的关系,过渡的颜色信息都是插值生成的,颜色分别为红、绿、蓝、黄四个像素的 2*2 大小的纹理放大后的效果如下:
在这里插入图片描述

四、工程地址

上面没有列出的代码,可以在下面地址中找到:

https://github.com/afei-cn/OpenGLSample/tree/master/texturedemo

另外,这是一个可运行的 Android App 工程。上例中使用的是 C++ 的 API,还有一个使用 Java API 加载图片纹理的例子也在上面的地址中,效果图如下:
在这里插入图片描述

最后,一些 OpenGL 的相关文章: https://blog.csdn.net/afei__/article/category/8502759

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: OpenGL ES是一种用于在移动设备上进行图形渲染的图形库。要实现纹理光照和阴影效果,我们可以使用一系列的技术和算法。 首先,为了实现纹理光照,我们需要在场景中添加光源。可以使用不同的光照模型,如环境光、漫反射光和镜面光,以模拟不同光源的效果。为了计算这些光照效果,我们需要使用光照方程和法线向量来确定光照的强度和方向。在渲染每个物体时,将法线向量传递给着色器,并计算光照强度,再将其与纹理颜色相乘以获得最终的光照效果。 其次,要实现阴影效果,我们可以使用阴影映射技术。这种技术的基本思想是,在渲染场景之前,以光源为中心从不同角度渲染一个深度贴图。然后,在渲染场景时,将该深度贴图作为纹理应用到每个物体上。对于场景中的每个片元,我们可以使用片元的深度与深度贴图中的对应值进行比较,从而确定该片元是否处于阴影中。如果处于阴影中,片元的颜色将进行某种处理,以模拟阴影效果。 最后,为了在OpenGL ES中实现纹理光照和阴影效果,我们需要使用着色器程序。通过编写顶点着色器和片元着色器代码,我们可以实现光照计算和阴影计算,并将其应用于每个顶点和片元。在顶点着色器中,我们可以对顶点和法线进行变换和计算。在片元着色器中,我们可以通过插值法获得光照强度,并使用法线向量和纹理颜色计算片元的最终颜色。 综上所述,要在OpenGL ES中实现纹理光照和阴影,我们需要使用光照方程、法线向量、阴影映射技术和着色器程序来计算和应用光照效果。通过合理的设计和编码,可以实现出令人满意的纹理光照和阴影效果。 ### 回答2: OpenGL ES是一种专门用于嵌入式系统的OpenGL API,并且支持在移动设备上实现图形渲染。在实现纹理光照和阴影效果时,以下是一些关键步骤和方法。 1. 加载纹理:首先,需要加载纹理图像作为场景中的纹理贴图。可以使用OpenGL ES提供的纹理加载函数(如glTexImage2D)将图像数据加载到纹理对象中。 2. 设置光源:为了实现光照效果,需要设置光源的位置和光照属性(如光的颜色和强度)。可以使用OpenGL ES的光照函数(如glLightfv)来设置光源属性。 3. 设置材质:每个物体都有一个材质,它决定了物体如何反射光线。可以使用OpenGL ES的材质函数(如glMaterialfv)来设置物体的材质属性。 4. 配置阴影:要实现实时阴影效果,可以使用阴影贴图技术。首先,需要渲染场景的深度信息到一个特殊的贴图中,这可以通过使用帧缓冲对象(Framebuffer Object)实现。然后,可以使用阴影贴图来计算每个像素点是否受阴影影响,并在渲染时进行相应调整。 5. 渲染场景:根据设置的光源和材质属性,使用着色器对场景中的每个物体进行光照计算。可以使用OpenGL ES提供的着色器语言(如GLSL)编写光照效果的着色器程序。在绘制每个物体时,需要将纹理贴图应用到物体的表面。 通过以上步骤,可以实现纹理光照和阴影效果。OpenGL ES提供了各种函数和工具来协助实现这些效果,但具体的实现方式可能因应用需求和设备性能而有所不同。因此,开发者需要结合具体情况进行调试和优化,以达到最佳的纹理光照和阴影效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值