Learn OpenGL 04 纹理

纹理环绕方式

纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL默认的行为是重复这个纹理图像(我们基本上忽略浮点纹理坐标的整数部分),但OpenGL提供了更多的选择:

环绕方式描述
GL_REPEAT对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER超出的坐标为用户指定的边缘颜色。

当纹理坐标超出默认范围时,每个选项都有不同的视觉效果输出。我们来看看这些纹理图像的例子:

前面提到的每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置(st(如果是使用3D纹理那么还有一个r)它们和xyz是等价的):

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

第一个参数指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D。第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是WRAP选项,并且指定ST轴。最后一个参数需要我们传递一个环绕方式(Wrapping),在这个例子中OpenGL会给当前激活的纹理设定纹理环绕方式为GL_MIRRORED_REPEAT。

如果我们选择GL_CLAMP_TO_BORDER选项,我们还需要指定一个边缘的颜色。这需要使用glTexParameter函数的fv后缀形式,用GL_TEXTURE_BORDER_COLOR作为它的选项,并且传递一个float数组作为边缘的颜色值:

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

纹理过滤

纹理过小的情况

GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:

GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:

那么这两种纹理过滤方式有怎样的视觉效果呢?让我们看看在一个很大的物体上应用一张低分辨率的纹理会发生什么吧(纹理被放大了,每个纹理像素都能看到):

GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。GL_LINEAR可以产生更真实的输出,但有些开发者更喜欢8-bit风格,所以他们会用GL_NEAREST选项。

当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项,比如你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。我们需要使用glTexParameter*函数为放大和缩小指定过滤方式。这段代码看起来会和纹理环绕方式的设置很相似:

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

 纹理过大的情况

多级渐远纹理(Mipmap)

OpenGL有一个glGenerateMipmaps函数,在创建完一个纹理后调用它OpenGL就会承担接下来的所有工作了。后面的教程中你会看到该如何使用它。

在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤。为了指定不同多级渐远纹理级别之间的过滤方式,你可以使用下面四个选项中的一个代替原有的过滤方式:

过滤方式描述
GL_NEAREST_MIPMAP_NEAREST使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
GL_NEAREST_MIPMAP_LINEAR在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
GL_LINEAR_MIPMAP_LINEAR在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样

就像纹理过滤一样,我们可以使用glTexParameteri将过滤方式设置为前面四种提到的方法之一:

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

加载与创建纹理

stb_image.h是Sean Barrett的一个非常流行的单头文件图像加载库,它能够加载大部分流行的文件格式。这里我们要使用它来创建和加载纹理

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了。

生成纹理

    unsigned int texture, texture2;glGenTextures(1, &texture);glBindTexture(GL_TEXTURE_2D, texture);// 为当前绑定的纹理对象设置环绕、过滤方式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);// 加载并生成纹理int width, height, nrChannels;stbi_set_flip_vertically_on_load(true);unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);//std::cout << "宽度:" << width << "高度: " << height << "颜色通道数量  " << nrChannels << std::endl;if (data){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。//第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。//第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。//第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。//下个参数应该总是被设为0(历史遗留的问题)。//第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。//最后一个参数是真正的图像数据。glGenerateMipmap(GL_TEXTURE_2D);//生成MipMap    }else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);//释放图像的内存// texture 2// ---------glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);// set the texture wrapping parametersglTexParameteri(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);// set texture filtering parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// load image, create texture and generate mipmapsdata = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);//std::cout << "宽度:" << width << "高度: " << height << "颜色通道数量  " << nrChannels << std::endl;if (data){// note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBAglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);for (int i = 0; i < 50; i++){std::cout << (int)data[i] << std::endl;}}else{std::cout << "Failed to load texture" << std::endl;}stbi_image_free(data);

应用纹理

修改了顶点属性以后顶点属性指针的步长和偏移量都要改,然后顶点着色器也要修改成接受一个新的顶点属性(UV)

GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1Dsampler3D,或在我们的例子中的sampler2D

绑定纹理它会自动把纹理赋值给片段着色器的采样器:

glBindTexture(GL_TEXTURE_2D, texture);

纹理单元

纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:

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

激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。

为了使用第二个纹理(以及第一个),我们必须改变一点渲染流程,先绑定两个纹理到对应的纹理单元,然后定义哪个uniform采样器对应哪个纹理单元:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

我们还要通过使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面:

ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置

OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。很幸运,stb_image.h能够在图像加载时帮助我们翻转y轴,只需要在加载任何图像前加入以下语句即可:

stbi_set_flip_vertically_on_load(true);

完成 

练习

  • 修改片段着色器,让笑脸图案朝另一个方向看,参考解答

相当于对称,采样的u坐标使用1-u就可以了

    FragColor = mix(texture(texture1, TexCoord), texture(texture2, vec2(1-TexCoord.x,TexCoord.y)), 0.2);
  • 尝试用不同的纹理环绕方式,设定一个从0.0f2.0f范围内的(而不是原来的0.0f1.0f)纹理坐标。试试看能不能在箱子的角落放置4个笑脸:参考解答,结果。记得一定要试试其它的环绕方式。

直接对UV坐标*2即可

    FragColor = mix(texture(texture1, TexCoord), texture(texture2, 2*TexCoord), 0.2);

REPEAT                                                    MIRRORED_REPEAT(就设置了x轴)

 

GL_CLAMP_TO_EDGE                                 GL_CLAMP_TO_BORDER(边缘为红色)

  • 尝试在矩形上只显示纹理图像的中间一部分,修改纹理坐标,达到能看见单个的像素的效果。尝试使用GL_NEAREST的纹理过滤方式让像素显示得更清晰:参考解答

修改UV坐标为中间位置坐标就可以采样到中间的位置,设置环绕模式为GL_CLAMP_TO_EDGE

  • 使用一个uniform变量作为mix函数的第三个参数来改变两个纹理可见度,使用上和下键来改变箱子或笑脸的可见度:参考解答。

 修改片段着色器

#version 330 core
out vec4 FragColor;in vec3 ourColor;
in vec2 TexCoord;uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float mixValue;void main()
{FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), mixValue);// FragColor = mix(texture(texture1, TexCoord), texture(texture2, vec2(1-TexCoord.x,TexCoord.y)), mixValue);
}

之后再main.cpp中声明同名字的变量,然后修改输入检测函数

在循环里面修改mixValue的值

void processInput(GLFWwindow* window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//检测是否按下返回键glfwSetWindowShouldClose(window, true);if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS){mixValue += 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)if (mixValue >= 1.0f)mixValue = 1.0f;}if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS){mixValue -= 0.001f; // change this value accordingly (might be too slow or too fast based on system hardware)if (mixValue <= 0.0f)mixValue = 0.0f;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/733286.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LCR 131. 砍竹子 I

解题思路&#xff1a;&#xff08;与砍竹子II的区别是&#xff0c;这里的竹子长度数量级较小&#xff09; 数学推导或贪心 切分规则&#xff1a; 等长&#xff0c;且尽量为3 b0时&#xff0c;pow(3,a) b1时&#xff0c;pow(3,a-1)*4 少一段3&#xff0c;并入b生成一…

YUNBEE云贝:3月9日-PostgreSQL中级工程师PGCE认证培训

课程介绍 根据学员建议和市场需求,规划和设计了《PostgreSQL CE 认证课程》,本课程以内部原理、实践实战为主&#xff0c;理论与实践相结合。课程包含PG 简介、安装使用、服务管理、体系结构等基础知识。同时结合一线实战案例&#xff0c; 面向 PG 数据库的日常维护管理、服务和…

Vue | 基于 vue-admin-template 项目的跨域问题解决方法

目录 一、现存问题 二、解决方法 2.1 修改的第一个地方 2.2 修改的第二个地方 2.3 修改的第三个地方 自存 一、现存问题 报错截图如下&#xff1a; 二、解决方法 2.1 修改的第一个地方 在 .env.development 文件中&#xff1a; # base api # VUE_APP_BASE_API /d…

springboot整合shiro的实战教程(一)

文章目录 1.权限的管理1.1 什么是权限管理1.2 什么是身份认证1.3 什么是授权 2.什么是shiro3.shiro的核心架构3.1 Subject3.2 SecurityManager3.3 Authenticator3.4 Authorizer3.5 Realm3.6 SessionManager3.7 SessionDAO3.8 CacheManager3.9 Cryptography 4. shiro中的认证4.1…

我的 4096 创作纪念日

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

YOLOv8+DeepSort/ByteTrack-PyQt-GUI / yolov5 deepsort 行人/车辆(检测 +计数+跟踪+测距+测速)

YoloV8结合可视化界面和GUI&#xff0c;实现了交互式目标检测与跟踪&#xff0c;为用户提供了一体化的视觉分析解决方案。通过YoloV8算法&#xff0c;该系统能够高效准确地检测各类目标&#xff0c;并实时跟踪它们的运动轨迹。 用户可以通过直观的可视化界面进行操作&#xff…

Unity性能优化篇(七) UI优化注意事项以及使用Sprite Atlas打包精灵图集

UI优化注意事项 1.尽量避免使用IMGUI(OnGUI)来做游戏时的UI&#xff0c;因为IMGUI的开销比较大。 2.如果一个UGUI的控件不需要进行射线检测&#xff0c;则可以取消勾选Raycast Target 3.尽量避免使用完全透明的图片和UI控件。因为即使完全透明&#xff0c;我们看不见它&#xf…

常见BUG如何在测试过程中分析定位

前言 在测试的日常工作中&#xff0c;相信经常有测试的小伙伴遇到类似的情况&#xff1a;在项目上线时&#xff0c;只要出现问题&#xff08;bug&#xff09;&#xff0c;就很容易成为“背锅侠”。 软件测试人员在工作中是无法避免的要和开发人员和产品经理打交道的&#xff…

117.龙芯2k1000-pmon(16)- linux下升级pmon

pmon的升级总是有些不方便&#xff0c;至少是要借助串口和串口工具 如果现场不方便连接串口&#xff0c;是不是可以使用网线升级pmon呢&#xff1f; 答案当然是可行的。 环境&#xff1a;2k1000linux3.10麒麟的文件系统 如今我已经把这个工具开发出来了。 GitHub - zhaozhi…

网络工程师笔记10 ( RIP / OSPF协议 )

RIP 学习路由信息的时候需要配认证 RIP规定超过15跳认定网络不可达 链路状态路由协议-OSPF 1. 产生lsa 2. 生成LSDB数据库 3. 进行spf算法&#xff0c;生成最有最短路径 4. 得出路由表

【探索C++容器:set和map的使用】

[本节目标] 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 1. 关联式容器 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为…

Toyota Programming Contest 2024#3(AtCoder Beginner Contest 344)(A~C)

A - Spoiler 竖线里面的不要输出&#xff0c;竖线只有一对&#xff0c;且出现一次。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(int (i)(j);(i)>(k);--(i)) #define debug(a) cou…

链表|面试题 02.07.链表相交

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode *l NULL, *s NULL;int lenA 0, lenB 0, gap 0;// 求出两个链表的长度s headA;while (s) {lenA ;s s->next;}s headB;while (s) {lenB ;s s->next;}// 求出两个链表长度差if (lenA &…

stm32学习笔记:SPI通信协议原理(未完)

一、SPI简介(serial Peripheral Interface&#xff08;串行 外设 接口&#xff09;) 1、电路模式&#xff08;采用一主多从的模式&#xff09;、同步&#xff0c;全双工 1 所有SPI设备的SCK、MOSI、MISO分别连在一起 2 主机另外引出多条SS控制线&#xff0c;分别接到各从机的S…

DetNet论文速读

paper&#xff1a;DetNet: A Backbone network for Object Detection 存在的问题 最近的目标检测模型通常依赖于在ImageNet分类数据集上预训练的骨干网络。由于ImageNet的分类任务不同于目标检测&#xff0c;后者不仅需要识别对象的类别&#xff0c;而且需要对边界框进行空间…

音视频开发_音频基础知识

如何采集声音——模数转换原理 声音模数转换是将声音信号从模拟形式转换为数字形式的过程。它是数字声音处理的基础&#xff0c;常用于语音识别、音频编码等应用中。 音视频通信流程 音视频采集&#xff1a;首先是从麦克风、摄像头等设备中采集音频和视频数据&#xff0c;将现…

【Windows】VMware虚拟机应用(一):下载安装 VMware Workstation

目录 一、下载 二、注意事项 三、安装 四、密钥激活 4.1 密钥 4.2 激活 一、下载 进入官网下载页 VMware Customer Connect | The All-In-One VMware Product Support Portal 先登录&#xff0c;下载时要求登录。 点【Downloads】 进入产品下载页面&#xff0c;切换到…

手写简易操作系统(三)--加载Loader

前情提要 上一节我们讲了如何启动计算机&#xff0c;这一节我们讲如何加载内核&#xff0c;内核是存在于硬盘上的一段程序&#xff0c;要加载这段程序&#xff0c;那么必然需要从硬盘上读取数据&#xff0c;这里我们就需要使用 ATA PIO 模式 根据ATA规范&#xff0c;所有符合A…

docker部署springboot jar包项目

docker部署springboot jar包项目 前提&#xff0c;服务器环境是docker环境&#xff0c;如果服务器没有安装docker&#xff0c;可以先安装docker环境。 各个环境安装docker&#xff1a; Ubuntu上安装Docker&#xff1a; ubuntu离线安装docker: CentOS7离线安装Docker&#xff1…

可视化场景(2):电商大屏-引爆业绩,直观呈现

hello&#xff0c;我是贝格前端工场&#xff0c;本期分享可视化大屏在电商领域的应用&#xff0c;如需要定制&#xff0c;可以与我们联络&#xff0c;开始了。 电商领域的可视化大屏可以提供实时的销售数据、用户行为分析、库存管理等信息&#xff0c;帮助企业实时监控经营状况…