关于android:Opengl-ES之纹理贴图

1次阅读

共计 6014 个字符,预计需要花费 16 分钟才能阅读完成。

纹理能够了解为一个二维数组,它能够存储大量的数据,这些数据能够发送到着色器上。个别状况下咱们所说的纹理是示意一副 2D 图,此时纹理存储的数据就是这个图的像素数据。

所谓的纹理贴图,就是应用 Opengl 将这个纹理数据渲染进去,这个过程有点像装修工人给墙体贴瓷砖,而瓷砖好比作纹理。

纹理坐标

如果为了将一副纹理图贴到 Opengl 绘制的一个矩形上,那么就须要解决一个问题,如何晓得矩形的具体某个点对应纹理图的某个点呢?为了解决这个问题就引出了纹理坐标,
通过矩形的顶点坐标与纹理坐标关联,这样就明确了每个顶点应该显示纹理图的那局部像素数据。

纹理坐标在 x 和 y 轴上,范畴为 0 到 1 之间。应用纹理坐标获取纹理色彩叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角,如下图所示:

纹理盘绕

纹理坐标的值介于 0 到 1 之间,如果咱们把纹理坐标设置成大于 1 那么会产生什么呢?OpenGL 默认的行为是反复这个纹理图像,那么利用这个默认的个性咱们能做些什么呢?那么比拟火的抖音四分屏、九分屏滤镜不就是能够用这个个性奇妙地实现吗。

以下是通过扭转纹理坐标实现四分屏和九分屏的一个小技巧:

// 4 分屏
const static GLfloat TEXTURE_COORD[] = {
        2.0f,2.0f, // 右下
        2.0f,0.0f, // 右上
        0.0f,2.0f, // 左下
        0.0f,0.0f // 左上
};

// 九分屏
const static GLfloat TEXTURE_COORD[] = {
        3.0f,3.0f, // 右下
        3.0f,0.0f, // 右上
        0.0f,3.0f, // 左下
        0.0f,0.0f // 左上
};

当然,当纹理坐标超过 1 这个范畴时,Opengl 也提供了其余的抉择,例如:

GL_REPEAT    // 对纹理的默认行为。反复纹理图像。GL_MIRRORED_REPEAT    // 和 GL_REPEAT 一样,但每次反复图片是镜像搁置的。GL_CLAMP_TO_EDGE    // 纹理坐标会被束缚在 0 到 1 之间,超出的局部会反复纹理坐标的边缘,产生一种边缘被拉伸的成果。GL_CLAMP_TO_BORDER    // 超出的坐标为用户指定的边缘色彩。

以上个性能够通过函数 glTexParameteri 设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

纹理过滤

纹理过滤理论就是纹理在放大放大的过程中像素的解决形式。其中在 Opengl ES 罕用的两种纹理过滤形式是 GL_NEAREST(邻近过滤)和 GL_LINEAR(也叫线性过滤)。

  • GL_NEAREST 是 OpenGL 默认的纹理过滤形式。当设置为 GL_NEAREST 的时候,OpenGL 会抉择中心点最靠近纹理坐标的那个像素。
  • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标左近的纹理像素,计算出一个插值,近似出这些纹理像素之间的色彩。一个纹理像素的核心间隔纹理坐标越近,那么这个纹理像素的色彩对最终的样本色彩的奉献越大。

GL_NEAREST 产生了颗粒状的图案,咱们可能清晰看到组成纹理的像素,而 GL_LINEAR 可能产生更平滑的图案,很难看出单个的纹理像素。

同理,纹理过滤个性也是通过函数 glTexParameteri 设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

纹理单元

纹理单元的次要目标是让咱们在着色器中能够应用多于一个的纹理。通过把纹理单元赋值给采样器,咱们能够一次绑定多个纹理,只有咱们首先激活对应的纹理单元。
例如应用 Opengl ES 对视频解码后的 YUV 进行渲染就须要用到纹理单元的相干知识点。

Opengl 中纹理的应用

在 Opengl 中应用纹理次要有以下几个步骤:

  • 创立纹理glGenTextures
  • 激活纹理glActiveTexture
  • 绑定纹理glBindTexture,传递特定的纹理 id 进行绑定
  • 上传纹理数据glTexImage2D
  • 解除纹理绑定,glBindTexture,传递 0 进行解除绑定

纹理坐标映射关系

在理解纹理贴图之前咱们先回顾一下三个坐标零碎,别离是纹理坐标零碎、手机屏幕坐标零碎、Opengl 坐标零碎。这三个坐标零碎的的原点各不相同,纹理坐标零碎咱们下面曾经介绍过了,这里不再反复。而手机屏幕坐标零碎则是原点位于左上角,X 轴向右为正,Y 轴向下为正的坐标零碎。
而 Opengl 坐标零碎则是原点位于核心,X 轴向右为正,Y 轴向下为正,其值介于 - 1 到 1 之间的一套坐标零碎。

既然纹理贴图就像装修工人贴瓷砖一样,那么间接将纹理坐标和 Opengl 的顶点坐标一一对应起来即可,也就是如下图:

咱们依照这个映射关系建设贴图:

// 顶点坐标,应用绘制两个三角形组成一个矩形的模式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
        0.5f,-0.5f, // 右下
        0.5f,0.5f, // 右上
        -0.5f,-0.5f, // 左下
        -0.5f,0.5f // 左上
};

// 纹理坐标(原点在左下角,这样贴图看到的会是倒置的
const static GLfloat TEXTURE_COORD[] = {
        1.0f,0.0f, // 右下
        1.0f,1.0f, // 右上
        0.0f,0.0f, // 左下
        0.0f,1.0f // 左上
};

运行发现图是贴上去了,然而看到的贴图却是倒置的,如下:

这是为什么呢?

因为纹理的生成是由图片像素来生成的,而图像的存储是从左上角开始的,然而纹理坐标原点却是在左下角的(笔者也不晓得为什么要这么奇葩),所以就产生了倒置景象,因而正确的映射关系应该是以图片的左上角为原点做映射才对,而这也刚好与手机屏幕坐标零碎匹配。

也就说正确的映射关系是须要先将以左下角为原点的纹理坐标进行倒置,而后再建设映射关系,这也是为什么有些博客说纹理坐标的原点是在左上角的起因(其实这是不对的,纹理坐标就是在图片的左下角,说在左上角的就是一个技巧),那么纹理坐标倒置后再映射如图:
!

废话少说,放码过去 …


#include "TextureMapOpengl.h"

#include "../utils/Log.h"

// 顶点着色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 TexCoord;\n"
                         "void main() {\n"
                         "TexCoord = aTexCoord;\n"
                         "gl_Position = aPosition;\n"
                         "}";

// 片元着色器
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "out vec4 FragColor;\n"
                              "in vec2 TexCoord;\n"
                              "uniform sampler2D ourTexture;\n"
                              "void main()\n"
                              "{\n"
                              "FragColor = texture(ourTexture, TexCoord);\n"
                              "}";


// 应用绘制两个三角形组成一个矩形的模式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
        0.5f,-0.5f, // 右下
        0.5f,0.5f, // 右上
        -0.5f,-0.5f, // 左下
        -0.5f,0.5f // 左上
};

// 纹理坐标(原点在左下角,这样贴图看到的会是倒置的
//const static GLfloat TEXTURE_COORD[] = {
//        1.0f,0.0f, // 右下
//        1.0f,1.0f, // 右上
//        0.0f,0.0f, // 左下
//        0.0f,1.0f // 左上
//};

// 贴图纹理坐标(参考手机屏幕坐标零碎,原点在左上角)// 因为对一个 OpenGL 纹理来说,它没有外在的方向性,因而咱们能够应用不同的坐标把它定向到任何咱们喜爱的方向上,然而大多数计算机图像都有一个默认的方向,它们通常被规定为 y 轴向下,X 轴向右
const static GLfloat TEXTURE_COORD[] = {
        1.0f,1.0f, // 右下
        1.0f,0.0f, // 右上
        0.0f,1.0f, // 左下
        0.0f,0.0f // 左上
};

// 四分屏  GL_REPEAT 盘绕形式
//const static GLfloat TEXTURE_COORD[] = {
//        2.0f,2.0f, // 右下
//        2.0f,0.0f, // 右上
//        0.0f,2.0f, // 左下
//        0.0f,0.0f // 左上
//};

// 九分屏 GL_REPEAT 盘绕形式
//const static GLfloat TEXTURE_COORD[] = {
//        3.0f,3.0f, // 右下
//        3.0f,0.0f, // 右上
//        0.0f,3.0f, // 左下
//        0.0f,0.0f // 左上
//};


TextureMapOpengl::TextureMapOpengl():BaseOpengl() {initGlProgram(ver,fragment);
    positionHandle = glGetAttribLocation(program,"aPosition");
    textureHandle = glGetAttribLocation(program,"aTexCoord");
    textureSampler = glGetUniformLocation(program,"ourTexture");
    LOGD("program:%d",program);
    LOGD("positionHandle:%d",positionHandle);
    LOGD("textureHandle:%d",textureHandle);
    LOGD("textureSample:%d",textureSampler);
}

void TextureMapOpengl::setPixel(void *data, int width, int height, int length) {LOGD("texture setPixel");
    glGenTextures(1, &textureId);

    // 激活纹理,留神以下这个两句是搭配的,glActiveTexture 激活的是那个纹理,就设置的 sampler2D 是那个
    // 默认是 0,如果不是 0 的话,须要在 onDraw 的时候从新激活一下?//    glActiveTexture(GL_TEXTURE0);
//    glUniform1i(textureSampler, 0);

// 例如,一样的
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);
    // 为以后绑定的纹理对象设置盘绕、过滤形式
    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);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    // 生成 mip 贴图
    glGenerateMipmap(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, textureId);

    // 解绑定
    glBindTexture(GL_TEXTURE_2D, 0);
}

void TextureMapOpengl::onDraw() {glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);

    // 激活纹理
    glActiveTexture(GL_TEXTURE2);
    glUniform1i(textureSampler, 2);

    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureId);

    /**
     * size 几个数字示意一个点,显示是两个数字示意一个点
     * normalized 是否须要归一化,不必,这里曾经归一化了
     * stride 步长,间断顶点之间的距离,如果顶点间接是间断的,也可填 0
     */
    // 启用顶点数据
    glEnableVertexAttribArray(positionHandle);
    glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);

    // 纹理坐标
    glEnableVertexAttribArray(textureHandle);
    glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);

    // 4 个顶点绘制两个三角形组成矩形
     glDrawArrays(GL_TRIANGLE_STRIP,0,4);

    glUseProgram(0);

    // 禁用顶点
    glDisableVertexAttribArray(positionHandle);
    if(nullptr != eglHelper){eglHelper->swapBuffers();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}

TextureMapOpengl::~TextureMapOpengl() {LOGD("TextureMapOpengl 析构函数");
}

认真看正文多了解 …

往期笔记

Opengl ES 之 EGL 环境搭建
Opengl ES 之着色器
Opengl ES 之三角形绘制
Opengl ES 之四边形绘制

关注我,一起提高,人生不止 coding!!!

正文完
 0