OpenGL:纹理

news/2025/3/10 15:43:35/文章来源:https://www.cnblogs.com/warren-j1an/p/18237710

我们已经了解到,我们可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销,因为每个模型都会需求更多的顶点,每个顶点又需求一个颜色属性。

艺术家和程序员更喜欢使用纹理(Texture)。纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节;你可以想象纹理是一张绘有砖块的纸,无缝折叠贴合到你的3D的房子上,这样你的房子看起来就像有砖墙外表了。因为我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。

除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上,但是这不是我们现在的主题。

纹理坐标

为了将纹理映射至顶点上,每个顶点实际上都会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(采集片段颜色)。

如果使用的是2D纹理,那么纹理坐标在x轴和y轴上,其范围为(0,1).(0,0)通常指代纹理的起始坐标(左下角),(1,1)代表着纹理的终止坐标(右上角)。

将纹理坐标传入片段着色器中,片段着色器会通过插值的方式进行采样。

纹理环绕方式

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

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

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

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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);

纹理过滤

纹理坐标不依赖于分辨率(Resolution),它可以是任意浮点值,所以OpenGL需要知道怎样将纹理像素(Texture Pixel,也叫Texel)映射到纹理坐标。当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。你可能已经猜到了,OpenGL也有对于纹理过滤(Texture Filtering)的选项。纹理过滤有很多个选项,但是现在我们只讨论最重要的两种:GL_NEARESTGL_LINEAR

GL_NEAREST即选择中心点最接近纹理坐标的像素,GL_LINEAR则根据周围像素中心点距离纹理坐标的远近进行加权求和。其表现分别如下:

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

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

纹理单元

纹理单元的主要目的是让我们在着色器中可以使用多个纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。一般来说windous上的现代显卡有32个纹理单元:GL_TEXTURE0GL_TEXTURE32。其中GL_TEXTURE3可以写为GL_TEXTURE0 + 3

我们可以使用glActiveTexture激活纹理单元,再绑定纹理,这样就能将纹理绑定至对应的纹理单元上:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

在片段着色器需要使用uniform设置对应纹理单元的序号(通常是一个int类型,从0到32),texture()函数是根据纹理单元和纹理坐标采样纹理用的:

#version 330 core
...uniform sampler2D texture1;
uniform sampler2D texture2;void main()
{FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

加载与创建纹理

纹理坐标可以写入vbo中,通过顶点属性设置,然后在顶点着色器中输入显存以及传输至片段着色器中。(这里不再演示)

生成一个纹理的代码应该看起来如下:

unsigned int texture;
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(1);
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D); // 可选
}
else
{std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

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

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

相关文章

北京端午游(送小微)

北京端午游 1、奥林匹克森林公园 🅿️交通:🚇地铁8号线森林公园南门站出🚗开车不推荐,游园还得回起点拿车,有点绕不值当🕙时间:半天or全天,根据路线决定🍚餐饮:自带或出国奥村门吃,太多好吃的,推荐日坛涮肉,眉州和满久居奥森北园北门出来有个林奥city!里面…

北京端午游

北京端午游 1、奥林匹克森林公园 🅿️交通:🚇地铁8号线森林公园南门站出🚗开车不推荐,游园还得回起点拿车,有点绕不值当🕙时间:半天or全天,根据路线决定🍚餐饮:自带或出国奥村门吃,太多好吃的,推荐日坛涮肉,眉州和满久居奥森北园北门出来有个林奥city!里面…

快速幂

快速幂大家好,我是Weekoder! 今天的内容是快速幂!(实际上是为了讲矩阵快速幂赶出来的嘻嘻 \[\texttt{Part 1 用处} \]快速幂,顾名思义就是快速地计算出某个数的幂,形如 \(a^n\)。 \[\texttt{Part 2 思想} \]为什么普通的幂运算慢?假设要计算 \(a^n\),则需要拆分成 \(a\…

JavaScript中的async/await

async/await是什么? async 是一个修饰符,async 定义的函数会默认的返回一个Promise对象resolve的值,因此对async函数可以直接进行then操作,返回的值即为then方法的传入函数。await 也是一个修饰符,await 关键字 只能放在 async 函数内部, await关键字的作用 就是获取 Prom…

Mysql 8.4.0 结合 Docker 搭建GTID主从复制,以及传统主从复制

注意:本教程不适用旧版本,Mysql 8.4.0 和 旧版本,主从复制相关命令有所变化,具体区别请看文末参考 软件版本 Docker:26.1.3 Mysql:8.4.0GTID主从复制 1.准备主从两台服务器 2.两台服务器分别创建DockerCompose文件 services:mysql:image: mysql:8.4.0ports:- "3306:…

Vue Router 4与路由管理实战

这篇文章介绍了如何在Vue.js应用中利用Vue Router实现单页面应用的路由管理,包括配置路由、导航守卫的使用、路由懒加载以优化性能以及动态路由的实现方法,旨在提升用户体验和应用加载效率title: Vue Router 4与路由管理实战 date: 2024/6/7 updated: 2024/6/7 excerpt: 这篇…

Body SweptSolid CompositeCurve Geometry

Body SweptSolid CompositeCurve Geometry 下图显示了应用此概念时使用的泛型类和关系。此外,概念可能对通用或标准化的行业实践和场景具有特别重要的意义。对于这些特定的使用场景,下表显示了用户可能采用的一般使用模式的推荐列表。 #####################################…

设备树学习

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(DeviceTree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。具体如下图…

平稳交付 20+ 医院,卓健科技基于 OpenCloudOS 的落地实践

本文将会阐述卓健科技运用 OpenCloudOS 的背景情况,深入探究其背后的缘由以及详细的实践流程。导语:随着数字化转型于各个行业领域当中持续地深入推进,充当底层支撑的操作系统正发挥着愈发关键且重要的作用。卓健科技把 OpenCloudOS 当作首要的交付系统,达成了项目交付速度…