美文网首页
OpenGL(5) —— 纹理

OpenGL(5) —— 纹理

作者: 你可记得叫安可 | 来源:发表于2020-02-02 10:12 被阅读0次

概念

纹理可以理解为一张 2D 图片(也有 1D 和 3D 的纹理),它可以添加到物体表面,以增加物体的表面细节,就像屋子里的墙纸一样。为了能把纹理映射到物体表面上,我们需要指定物体各个顶点对应纹理上的哪个坐标(Texture Coordinate)。

纹理坐标系
纹理坐标系以左下角为原点,横向向右为 s 轴,纵向向上为 t 轴,且不论纹理图片大小,横向纵向的最大值都是 1。

如果是 3D 纹理,还有 r 轴。事实上它们和 xyz 轴是一个意思。

代码实践

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");
        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 定义的位图数据,并把它复制到当前绑定的纹理对象
    GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();
    glGenerateMipmap(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE, 0);
    return textureObjectIds[0];
}
glGenTextures

glGenTextures(int n, int[] textures, int offset):要使用纹理,必须要有 id。通过这个函数来生成纹理 id,n 代表生成 id 个数,texturesoffset 一起决定生成的 id 放置的地址。

glBindTexture

通过这个函数将上一步生成的纹理对象绑定到 GL_TEXTURE_2D 这个 texture target 上,绑定后,这个 GL_TEXTURE_2D 就相当于纹理对象的代理了,之后对这个纹理对象的操作和配置都可以通过 GL_TEXTURE_2D 来进行。

  • 这里涉及到一些概念。OpenGL 中至少保证有 16 个 texture unit(纹理单元),我们默认使用的是 GL_TEXTURE0,即 0 号单元。每个 texture unit 又包含多种 texture target(纹理目标),例如上文中的 GL_TEXTURE_2D 就是一种 texture target。事实上我们需要先调用 glActiveTexture 来激活指定位置的 texture unit,再调用 glBindTexture 来将纹理对象绑定到当前激活的 texture unit 下的某个类型 texture target 上。因为我们默认使用的就是 GL_TEXTURE0,因此当我们要使用默认的 texture unit 时就可以不用先调用 glActiveTexture
    当我们第一次调用 glBindTexture 时,就决定了绑定的纹理对象类型,例如上文中我们就将纹理对象绑定到了 GL_TEXUTRE0GL_TEXTURE_2D 类型,那么其纹理对象内部状态就被初始化为 2d texture 状态,并且该对象不能再被绑定到其他 texture target 上。
  • 我们上面的操作并没有关心 texture unit,而是使用默认就完了。那么什么时候我们需要使用不同的 texture unit 呢?当使用多重纹理的时候,也就是在着色器中要同时使用多个 *sampler 时。这是另一个更为复杂的话题了,这里不做讨论。
纹理过滤

纹理坐标被定义成一定是 [0~1],因此纹理坐标上任取一个点,不可能刚好落在纹理图片的一个像素上。那么我们就需要对这个坐标点应该显示什么颜色进行采样。常用的采样方式有两种:GL_NEARESTGL_LINEAR

GL_NEAREST
GL_NEAREST
GL_NEAREST 被称为邻近过滤,是 OpenGL 默认的过滤方式。上图中➕表示纹理坐标,OpenGL 将选择像素中心点最近的那个像素返回。
GL_LINEAR
GL_LINEAR
GL_LINEAR 被称作线性过滤。上图中➕表示纹理坐标,OpenGL 将返回坐标附近像素值的加权平均值。
// 设置缩小的情况下过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// 设置放大的情况下过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

上面的代码就设置了纹理对象在放大(Magnify)和缩小(Minify)时采用的过滤方式。

texImage2D
// 加载纹理到 OpenGL,读入 Bitmap 定义的位图数据,并把它复制到当前绑定的纹理对象
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);

将 bitmap 图片上传到 GPU 端的,我们纹理对象绑定的 GL_TEXTURE_2D 处,这样,我们的纹理对象就会被附加上纹理图像。

glGenerateMipmap

glGenerateMipmap 用于生成多级渐进纹理

多级渐进纹理

  • 想象一下,假设我们有一个包含着上千物体的大房间,每个物体上都有纹理。有些物体会很远,但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。
  • OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。让我们看一下多级渐远纹理是什么样子的: 多级渐进纹理
解绑纹理对象

glBindTexture(GL_TEXTURE, 0);


着色器代码

uniform mat4 u_Matrix;

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main() {
    v_TextureCoordinates = a_TextureCoordinates;
    gl_Position = u_Matrix * a_Position;
}

顶点着色器中我们使用 attribute vec2 a_TextureCoordinates 来接受应用程序中的纹理坐标,并且将其赋值给 varying vec2 v_TextureCoordinates 来向片段着色器共享纹理坐标。

precision mediump float;

uniform sampler2D u_TextureUnit;

varying vec2 v_TextureCoordinates;

void main() {
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}

片段着色器中,我们使用跟顶点着色器中相同的 varying vec2 v_TextureCoordinates 来接受从顶点着色器传输过来的纹理坐标。我们申明一个采样器 uniform sampler2D u_TextureUnit,采样器的作用是在纹理坐标上,按照采样规则进行像素采样,然后通过 texture2D 将像素值附加给光栅化后的片段。

Q: 这里的采样器如何定位到上文中加载的纹理对象呢?
A: 注意上文中的纹理对象,最终是绑定在了 texture unit 0texture target GL_TEXTURE_2D 处。采样器在着色器中的声明 sampler2D 就表明了其 texture targetGL_TEXTURE_2D。因此我们还需要告诉采样器,它所绑定的 texture unit 的位置。


这里面有两个重要的变量,一个是纹理坐标 a_TextureCoordinates,一个是采样器 u_TextureUnit

应用层代码

1. 纹理坐标的传递

我们在 Rectangle 中定义物体的坐标和纹理的坐标,并将其中的纹理坐标取出,传递给着色器中的 a_TextureCoordinates

private static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";
float[] rectangleVertex = {
        // 物体 (x, y),纹理 (s, t)
        0f, 0f, 0.5f, 0.5f,
        -0.5f, -0.8f, 0f, 0.9f,
        0.5f, -0.8f, 1f, 0.9f,
        0.5f, 0.8f, 1f, 0.1f,
        -0.5f, 0.8f, 0f, 0.1f,
        -0.5f, -0.8f, 0f, 0.9f
};
...
aTextureCoordinatesLocation = glGetAttribLocation(program, A_TEXTURE_COORDINATES);

注意到,每一个顶点坐标都需要对应一个纹理坐标,如果其中哪个坐标没有对应的纹理坐标的话,那么最后绘制的物体中所有与该顶点坐标相关的形状都不会被绘制。

注意上面的纹理坐标与所对应的顶点坐标是上下颠倒的。这是因为纹理坐标是左下角为原点,而 Android 系统则是以左上角为坐标原点,因此我们只能在应用层手动将纹理坐标上下颠倒,以正确显示图像纹理。

2. 采样器的传递

下面的代码完成了着色器中的采样器绑定到纹理对象的逻辑。

// 从编译好的着色器中将采样器变量取出
uTextureUnitLocation = glGetUniformLocation(program, U_TEXTURE_UNIT);
// 取得纹理对象 id
int texture = TextureHelper.loadTexture(context, R.drawable.image);
// 激活纹理单元 GL_TEXTURE0
glActiveTexture(GL_TEXTURE0);
// 将纹理对象绑定到当前激活纹理单元的 GL_TEXTURE_2D
glBindTexture(GL_TEXTURE_2D, texture);
// 将着色器中的采样器绑定到纹理单元 GL_TEXTURE0
glUniform1i(uTextureUnitLocation, 0);

https://github.com/fightyz/OpenGLPlayground.git
revision fe3f9efdd078deb0edb323a4fe6e91cf36acb669

相关文章

  • OpenGL(5) —— 纹理

    概念 纹理可以理解为一张 2D 图片(也有 1D 和 3D 的纹理),它可以添加到物体表面,以增加物体的表面细节,...

  • GLKit常用API解析

    GLKTextureInfo 创建OpenGL纹理信息 name: OpenGL上下文中纹理名称 target: ...

  • OpenGL ES GLKit 􏰼􏰜常用API解析

    GLKTextureInfo创建OpenGL纹理信息 name : OpenGL上下文中纹理名称 target :...

  • OpenGL纹理内容

    纹理可以理解为一张图片,OpenGL渲染图片会将图片的像素保存在纹理缓存中。 OpenGL常用纹理函数 载入纹理 ...

  • OpenGL纹理

    纹理可以理解为一张图片,OpenGL渲染图片会将图片的像素保存在纹理缓存中。OpenGL常用纹理函数 载入纹理 纹...

  • OPenGL ES纹理翻转解决方案

    纹理翻转 在使用OpenGL函数加载纹理到图形时,经常遇到纹理上下颠倒的问题。原因是因为OpenGL要求纹理坐标原...

  • OpenGL坐标概念

    openGL 顶点,坐标系,纹理坐标Android OpenGL es 纹理坐标设定与贴图规则对Android o...

  • OpenGL之纹理及应用案例

    纹理介绍 OpenGL使用的图片数据(纹理)都是tga格式的,而iOS/OpenGL ES使用PNG/JPEG格式...

  • OpenGL 纹理翻转策略

    Open GL纹理翻转 在使用OpenGL函数加载纹理到图形时,经常遇到纹理上下颠倒的问题。原因是因为OpenGL...

  • OpenGL ES之旅(三)-- OpenGL ES 纹理翻转解

    纹理翻转概述 在使用OpenGL ES函数加载纹理到图形时,经常遇到纹理上下颠倒的问题。原因是因为OpenGL E...

网友评论

      本文标题:OpenGL(5) —— 纹理

      本文链接:https://www.haomeiwen.com/subject/uwvtxhtx.html