当前位置: 首页 > news >正文

Opengl ES之纹理贴图

纹理可以理解为一个二维数组,它可以存储大量的数据,这些数据可以发送到着色器上。一般情况下我们所说的纹理是表示一副2D图,此时纹理存储的数据就是这个图的像素数据。

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

纹理坐标

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

纹理坐标在x和y轴上,范围为0到1之间。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hPdHA9m-1663898686466)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/%E7%BA%B9%E7%90%86%E5%9D%90%E6%A0%87.png)]

纹理环绕

纹理坐标的值介于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	//超出的坐标为用户指定的边缘颜色。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tR9KTMYR-1663898686466)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/%E7%BA%B9%E7%90%86%E7%8E%AF%E7%BB%95.png)]

以上特性可以通过函数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的顶点坐标一一对应起来即可,也就是如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2BMotnw-1663898686467)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/%E7%BA%B9%E7%90%86%E5%9D%90%E6%A0%87%E6%98%A0%E5%B0%84.png)]

我们按照这个映射关系建立贴图:

// 顶点坐标,使用绘制两个三角形组成一个矩形的形式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
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 // 左上
};

运行发现图是贴上去了,但是看到的贴图却是倒置的,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbf1shjY-1663898686467)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/%E7%BA%B9%E7%90%86%E8%B4%B4%E5%9B%BE%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C-%E5%80%92%E7%BD%AE.png)]

这是为什么呢?

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

也就说正确的映射关系是需要先将以左下角为原点的纹理坐标进行倒置,然后再建立映射关系,这也是为什么有些博客说纹理坐标的原点是在左上角的原因(其实这是不对的,纹理坐标就是在图片的左下角,说在左上角的就是一个技巧),那么纹理坐标倒置后再映射如图:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eLB7j4ru-1663898686467)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/android%E7%BA%B9%E7%90%86%E6%98%A0%E5%B0%84.jpg)]

废话少说,放码过来…


#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析构函数");
}

仔细看注释多理解…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7rHAbkO-1663898686467)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/%E7%BA%B9%E7%90%86%E8%B4%B4%E5%9B%BE%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.png)]

往期笔记

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

关注我,一起进步,人生不止coding!!!

相关文章:

  • 淘宝天猫商品全网搜索接口
  • 01背包-一维dp数组学习笔记
  • Rebex Syslog for.NET R6.6,Adds support for multicast addresses
  • day02-Java基础-注释,关键字,字面量,变量,数据类型,标识符,键盘录入,IDEA相关
  • 免密登录跳板机和linux服务器
  • HashMap原理硬核分析,细到每一行代码!
  • STK12与Python联合仿真(三):分析星座覆盖性能
  • 基于OpenHarmony/HarmonyOS操作系统的ArkUI框架深入学习——开篇1
  • WebSocket| Netty netty-websocket-spring-boot-starter
  • C语言for循环结构经典练习
  • 【老生谈算法】matlab实现元胞自动机算法源码——元胞自动机
  • wireshark协议或者协议内容解码异常
  • nginx部署vue项目(包括一个nginx部署多个vue项目)
  • 【py】[打包exe]用auto-py-to-exe将py程序打包为exe文件
  • 【数据库迁移系列】使用pgloader将数据从MySQL迁移到openGauss的最佳实践
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • 30天自制操作系统-2
  • interface和setter,getter
  • java2019面试题北京
  • laravel5.5 视图共享数据
  • mysql外键的使用
  • Object.assign方法不能实现深复制
  • Python学习之路13-记分
  • windows下如何用phpstorm同步测试服务器
  • 关于 Cirru Editor 存储格式
  • 基于web的全景—— Pannellum小试
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 你不可错过的前端面试题(一)
  • 如何在GitHub上创建个人博客
  •  一套莫尔斯电报听写、翻译系统
  • 用quicker-worker.js轻松跑一个大数据遍历
  • ​如何在iOS手机上查看应用日志
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • (ZT)一个美国文科博士的YardLife
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (力扣)1314.矩阵区域和
  • (十八)三元表达式和列表解析
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (图)IntelliTrace Tools 跟踪云端程序
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .NET Core 中的路径问题
  • .net 中viewstate的原理和使用
  • .net解析传过来的xml_DOM4J解析XML文件
  • ::before和::after 常见的用法
  • ?.的用法
  • @Autowired @Resource @Qualifier的区别
  • @Bean有哪些属性
  • [ JavaScript ] JSON方法
  • [20160902]rm -rf的惨案.txt
  • [23] 4K4D: Real-Time 4D View Synthesis at 4K Resolution
  • [AutoSar NVM] 存储架构
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现
  • [GPT]Andrej Karpathy微软Build大会GPT演讲(上)--GPT如何训练