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

OpenGL入门(四)之纹理Texture

纹理

纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节!我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点,来减小开销!

为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。

纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。

一个纹理坐标:

float texCoords[] = {
    0.0f, 0.0f, // 左下角
    1.0f, 0.0f, // 右下角
    0.5f, 1.0f // 上中
};

纹理环绕方式

当纹理坐标超出默认范围时,可以设置环绕方式来展示不同的视觉效果输出!默认为GL_REPEAT,还有GL_MIRRORED_REPEATGL_CLAMP_TO_EDGEGL_CLAMP_TO_BORDER

前边提到过纹理坐标的分量可以通过STPQ访问:

// set the texture wrapping parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

纹理过滤

纹理像素(Texture Pixel):组成一张图片的无数个像素点
纹理坐标:设置的顶点数组

OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色。那么就会有一个问题,在对应查找像素的时候,用什么方式去拿像素颜色,OpenGL提供了纹理过滤来帮助我们选择,这里有两个重要的选项!

  • GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。
  • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。

当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //缩小
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //放大

多级渐变纹理

多级渐变纹理主要是用来解决物体远近,纹理大小,分辨率不同的问题!距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个,且节省内存!

注意: 多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理。

另外:OpenGL提供glGenerateMipmap()函数,在创建完一个纹理后调用它OpenGL就会为纹理图像创建一系列多级渐远纹理!

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

...//创建纹理
glGenerateMipmap(GL_TEXTURE_2D);

加载和创建纹理

这里使用stb_image.h库来加载图片!
https://github.com/nothings/stb

使用非常简单,在我们的cpp文件中添加:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

创建纹理:

int width, height, nrChannels;
//这里会拿到图像的宽高和颜色通道个数
unsigned char *data = stbi_load("../dependency/stb/container.jpg", &width, &height, &nrChannels, 0);

//生成纹理
unsigned int texture1;
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);//绑定

... //设置环绕和过滤

/**
 * @brief 生成纹理
 * 第1个参数:指定纹理target
 * 第2个参数:指定多级渐远纹理的级别  0:基本级别
 * 第3个参数:纹理存储格式。 这里图像只有RGB
 * 第4,5个参数:纹理的宽高
 * 第6个参数:总设置为0(历史问题)
 * 第7,8个参数:源图像的格式和数据类型
 * 第9个参数:图像数据
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

...
stbi_image_free(data);//释放图像内存

应用纹理

这里我们绘制一个矩形,并把图片纹理贴在矩形上

    //绘制矩形+纹理
    float vertices[] = {
    //     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
    };


    const char *vertexShaderSource = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "layout (location = 1) in vec3 aColor;\n"
    "layout (location = 2) in vec2 aTexCoord;\n"
    
    "out vec3 vertexColor;\n"
    "out vec2 texCoord;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "   vertexColor = aColor;\n"
    "   texCoord = aTexCoord;\n"
    "}\0";

    const char *fragmentShaderSource = "#version 330 core\n"
    "in vec3 vertexColor;\n" //接收顶点着色器中的输入变量(名称、类型相同) 
    "in vec2 texCoord;\n" //接收纹理(名称、类型相同)
    "out vec4 color;\n"
    "uniform sampler2D texture1;\n" //声明一个纹理采样器
    "void main()\n"
    "{\n"
    "   color = texture(texture1, texCoord) * vec4(vertexColor, 1.0);\n" //将输出颜色设置为纹理  第一个参数纹理采样器,第二个参数纹理坐标
    "}\0";

...

GLSL内建的texture()函数,可以得到一个纹理颜色,第一个参数纹理采样器,第二个参数纹理坐标。

这样我们就能绘制出一个正确的纹理图片了!

需要注意的点:
在前边我们了解了uniform这个变量,这里在片段着色器中声明了一个采样器,但是在代码中却没有给它赋值,目前看工作正常,能够绘制出图片!这是因为OpenGL中有默认的纹理单元0,且这个纹理单元默认激活!

纹理单元

当我们需要绘制多个纹理时,就要使用纹理单元,把纹理单元赋值给采样器,就能够和纹理一一对应起来!

glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);

激活一个纹理单元之后,调用glBindTexture,就会把纹理绑定到当前已激活的纹理单元!

OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

修改一下我们的片段着色器:

const char *fragmentShaderSource = "#version 330 core\n"
    "in vec3 vertexColor;\n" //接收顶点着色器中的输入变量(名称、类型相同) 
    "in vec2 texCoord;\n" //接收纹理(名称、类型相同)
    "out vec4 color;\n"
    "uniform sampler2D texture1;\n" //声明一个纹理采样器
    "uniform sampler2D texture2;\n" 
    "void main()\n"
    "{\n"
    "   color = mix(texture(texture1, texCoord), texture(texture2, texCoord), 0.2) * vec4(vertexColor, 1.0);\n" //将输出颜色设置为纹理  第一个参数纹理采样器,第二个参数纹理坐标
    "}\0";

...
//有两个纹理时,就需要设置纹理单元
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);//指定采样器属于哪个纹理单元
glUniform1i(glGetUniformLocation(shaderProgram, "texture2"), 1);

...
//渲染
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
...

GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

另外一个需要注意的问题:纹理上下颠倒了!这是因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。
stb_image.h能够在图像加载时帮助我们翻转y轴,在加载前调用:

stbi_set_flip_vertically_on_load(true);

这时我们将得到两个纹理叠加的效果!

相关文章:

  • UDP和TCP协议发送接收数据
  • Apache Doris 快速学习大纲
  • FastFlow(5)---软件加速器 software accelerator
  • 华为OD:0019-0020:-最小步骤数—删除字符串中出现次数最少的字符
  • Python学生成绩管信息理系统(面向对象)(学生信息篇)
  • 国稻种芯百团计划行动 丰收节贸促会·袁隆平:水稻国际竞争
  • 面试精选:3、史上最详细的Linux精选面试题(二)
  • 2.21 haas506 2.0开发教程 - TTS - Text To Speech (320开发板)
  • Promethues-如何监控容器
  • 测试人生 | 从小团队的业务到独角兽的测开,涨薪超过60%,90后小哥哥凤凰涅槃了
  • 技术门槛高?来看 Intel 机密计算技术在龙蜥社区的实践
  • 532. 数组中的 k-diff 数对
  • 通过mybatis自定义参数类型转换器,进行数据库字段加密脱敏
  • Win10修复IPv6优先访问
  • 盘点市面上七款好用的代码加密混淆工具,你都用过哪款?
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • HTTP那些事
  • PAT A1120
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • SpringBoot几种定时任务的实现方式
  • vue-router的history模式发布配置
  • 二维平面内的碰撞检测【一】
  • 飞驰在Mesos的涡轮引擎上
  • 诡异!React stopPropagation失灵
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 再谈express与koa的对比
  • No resource identifier found for attribute,RxJava之zip操作符
  • Nginx实现动静分离
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (差分)胡桃爱原石
  • (第二周)效能测试
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • .form文件_一篇文章学会文件上传
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net Core与存储过程(一)
  • .net 程序发生了一个不可捕获的异常
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • [52PJ] Java面向对象笔记(转自52 1510988116)
  • [Android] Amazon 的 android 音视频开发文档
  • [C#]DataTable常用操作总结【转】
  • [codevs 1288] 埃及分数 [IDdfs 迭代加深搜索 ]
  • [CSDN首发]鱿鱼游戏的具体玩法详细介绍
  • [ERROR] ocp-server-ce-py_script_start_check-4.2.1 RuntimeError: ‘tenant_name‘
  • [HNOI2008]水平可见直线
  • [java] 23种设计模式之责任链模式