在设计物体表面时,很多时候我们不满足于一种颜色或者几种简单颜色,我们希望是丰富多彩的图案,或者说我们提供给它的图片。这样一个顶点一个顶点的去指定那是行不通了,我们不可能把所有顶点用数字去表达出来,必须用一种新的方式去设置颜色。这就是纹理,像用一张画去贴在物体的表面一样,这样就不用指定太多的点,只需要设置“边界”就可以了。我们把这种行为叫做映射。 我们不可能随便映射,我们必须告诉程序三个东西:1.纹理图片剪裁多少(边界坐标位置)2.纹理图片对应的3d面的边界(顶点坐标位置)3将纹理图片的坐标与3d面的坐标一一对应。 (一)纹理图片设定:
和标准化坐标一样,纹理的坐标也是从0——1,不过这回没有负值了,不管图片有多么大,我们都将它们认为是在0-1的图片
坐标系中,其中左下顶点是原点。我们需要三个点构成一个面,所以我们找三个顶点作为表示边界的数据。
float texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
};
(二)处理坐标超出(0,1)设定,纹理环绕方式:
不同于openGL的标准化坐标,在超出(-1,1)时候,直接不显示,当纹理坐标超出(0,1)的时候,纹理处理会将图片进行变
化,使图片将贴合的那个面完全包含,OpenGL默认的行为是重复这个纹理图像(我们基本上忽略浮点纹理坐标的整数部分)。
接下来介绍一个函数,它用来设置种情况,它要放在你创建了一个纹理对象,并且绑定到当前上下文的后面,保证它对这个纹理对象起作用。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一个参数是表示2d渲染,第二个参数表示这是对那个坐标轴进行的(可以猜测这样可以有好多的组合情况发生),
最后一个参数需要我们传递一个环绕方式,这里设置的是GL_MIRRORED_REPEAT,和GL_REPEAT一样,但每次重复图片是镜像放置的。
(大多数情况都是这样,但有时候我们还需要指定填充颜色,这个我们一般用这个函数的变形函数,没办法c语言不支持重载)
(三)处理放大,缩小情况,纹理过滤: 我们对于物体不可避免要应对放大和缩小的情况。首先我们屏幕的像素点数量是不可能改变的,也就是说,有的时候当你缩小物体的时候
你需要用更小的像素点数量去承载图片,放大的时候你需要用更多的像素点去表现物体,这就是纹理过滤的问题。此外还有一个情况就是当一
个分辨率很高的物体,在很远的地方。我们只能用很少的像素点去表现它。这一系列问题都是这个纹理过滤的原由 这里需要三个属性作为分析。1.纹理像素 2.纹理顶点 3.屏幕像素
首先纹理像素,就是一张图片不断放大后,能发现它是由一个一个点组成的,这就是纹理像素,它由拍摄这张图片的仪器决定,照片大小不变
它不变。
之后是纹理顶点,这个从(0,1)的绝对坐标组,不受分辨率影响,所以所以OpenGL需要知道怎样将纹理像素(Texture Pixel,也叫Texel,
译注1)映射到纹理坐标。
最后是屏幕像素,OpenGL根据纹理顶点坐标,查找纹理图片上的像素,再根据纹理像素判断分析,提取出一个颜色值,放置到屏幕像素上。
纹理过滤主要就是考虑如何分析判断。目前提供两种过滤GL_NEAREST(颗粒状的图案),GL_LINEAR(更平滑的图案)。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
(四)处理物体远近情况,多级渐远纹理:
接下来我们考虑之前说的一个问题,当一个纹理分辨率很高的物体,在很远的地方,我们只能用很少的屏幕像素点去表示它。
OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物
体上这会产生不真实的感觉,对它们使用高分辨率纹理浪费内存的问题。这就是用多级渐远纹理的原因。
这是一种用“空间换时间”的手段。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,
即最适合物体的距离的那个。
注意每个二分之一的图片它们的纹理像素只有之前的四分之一多。这一步必须在加载图片成功之后再执行。
这个负责函数是glGenerateMipmaps(),此外也可以为这种缩小或者放大,指定纹理过滤形式。
glGenerateMipmap(GL_TEXTURE_2D);
这个参数跟VBO,VAO类似,代表了绑定在这个属性上的纹理对象。
(五)加载与创建纹理:
接下来我们要把存储在文件中的图片转化成二进制流,让OpenGL识别,由于图片格式有很多种,我们要写很多读取函数去读,
这些函数我们当然不必自己去写,引用一个开源的支持多种流行格式的图像加载库就好了。stb_image.h库是我们用的。
我们引用一个stbi_load函数去加载图片文件好了。
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
第一个参数接受一个图像文件的位置,剩下三个是宽度、高度和颜色通道的个数,最后一个填0.暂时不管。
(六)生成纹理:
使用前面载入的图片数据生成一个纹理
接下来就是生成纹理了,首先我们需要一个纹理对象作为处理对象,毕竟前面我们一直在做图片的工作,那些属性的设置都需要一个
对象去承载它们。
//ID引用生成对象
unsigned int texture;
glGenTextures(1, &texture);
//绑定2d纹理目标(属性),之前的哪些纹理过滤也是这个时候用
glBindTexture(GL_TEXTURE_2D, texture);
//使用前面载入的图片数据生成一个纹理,用的生成函数为glTexImage2D,用处就是根据数据生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
//第一个参数指定了纹理目标(Target)。第二个参数为纹理指定多级渐远纹理的级别。这里我们填0,也就是基本级别(不自动生成)。
//第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值
//第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
//下个参数应该总是被设为0(历史遗留的问题)。
//第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
//最后一个参数是真正的图像数据。
生成纹理和多纹渐进纹理之后,我们就不需要图片数据了,这时释放它们,不要占用内存了 stbi_image_free(data);
(七)将纹理对象传给着色器,显示到屏幕上:
严格来讲之前我们只是去处理了图片数据和纹理对象,如果我们想看到纹理的样子,我们就必须用着色器去显示。
我们要把纹理对象传递到着色器内部,然后告诉它如何去显示到3d图形的每个点上。
顶点着色器:
#version 330 core layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord; void main() {
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
} 把数据(纹理数据坐标)读进来,传到片段着色器,此外我们这个时候要从程序CPU把纹理对象传入着色器了,这个最好用之前的uniform
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main() {
FragColor = texture(ourTexture, TexCoord);
}
texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。
texture函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。 那怎么把纹理对象赋值给着色器呢,这个不用咱们去做,咱们只需要绑定纹理对象,绑定VAO,就可调用绘制函数了,纹理对象会自动传入的。
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
LearnOpenGL学习笔记(五)——纹理的更多相关文章
-
webgl学习笔记五-纹理
写在前面 建议先阅读下前面我的三篇文章. webgl学习笔记一-绘图单点 webgl学习笔记二-绘图多点 webgl学习笔记三-平移旋转缩放 术语 : 纹理 :图像 图形装配区域 :顶点着色器顶点坐标 ...
-
C#可扩展编程之MEF学习笔记(五):MEF高级进阶
好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...
-
(转)Qt Model/View 学习笔记 (五)——View 类
Qt Model/View 学习笔记 (五) View 类 概念 在model/view架构中,view从model中获得数据项然后显示给用户.数据显示的方式不必与model提供的表示方式相同,可以与 ...
-
java之jvm学习笔记五(实践写自己的类装载器)
java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...
-
Learning ROS for Robotics Programming Second Edition学习笔记(五) indigo computer vision
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
-
Typescript 学习笔记五:类
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
-
ES6学习笔记<;五>; Module的操作——import、export、as
import export 这两个家伙对应的就是es6自己的 module功能. 我们之前写的Javascript一直都没有模块化的体系,无法将一个庞大的js工程拆分成一个个功能相对独立但相互依赖的小 ...
-
muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor
目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...
-
python3.4学习笔记(五) IDLE显示行号问题,插件安装和其他开发工具介绍
python3.4学习笔记(五) IDLE显示行号问题,插件安装和其他开发工具介绍 IDLE默认不能显示行号,使用ALT+G 跳到对应行号,在右下角有显示光标所在行.列.pycharm免费社区版.Su ...
-
Go语言学习笔记五: 条件语句
Go语言学习笔记五: 条件语句 if语句 if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ } 竟然没有括号,和python很像.但是有大括号,与python又不一样. 例子: pa ...
随机推荐
-
C/C++:C++中static,extern和extern ";C";关键字
1. extern 变量 extern 表明该变量在别的地方已经定义过了,在这里要使用那个变量. 当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern in ...
-
IE兼容CSS3圆角border-radius的方法(同时兼容box-shadow,text-shadow)
IE兼容CSS3圆角border-radius,box-shadow,text-shadow的方法 1.下载ie-css3.htc 2.CSS box { -moz-border-radius: 15 ...
-
如何在C#中使用全局鼠标、键盘Hook
今天,有个同事问我,怎样在C#中使用全局钩子?以前写的全局钩子都是用unmanaged C或C++写个DLL来实现,可大家都知道,C#是基于.Net Framework的,是managed,怎么实现全 ...
-
ros-Qt代码环境的搭建
1 建立package catkin_create_pkg beginner_tutorials roscpp 2 导入Qt Qt中打开整个工作空间的src/CMakeLists.txt 在倒数第二行 ...
-
boost库在windows下的编译和使用
因为跨平台的原因,现在要使用到boost库,boost库非常大,现在处于摸索阶段. 首先来说boost库在window下的安装和使用. 一.下载 首先从boost官方主页http://www.boos ...
-
时间同步方法及几个可用的NTP服务器地址
大家都知道计算机电脑的时间是由一块电池供电保持的,而且准确度比较差经常出现走时不准的时候.通过互联网络上发布的一些公用网络时间服务器NTP server,就可以实现自动.定期的同步本机标准时间. 依靠 ...
-
关于ECMAScript 2016, 2017, 和2018中新增功能(摘抄)
ECMAScript 2016 1. Array.prototype.includes includes是数组上的简单实例方法,并有助于轻松查找某个项是否在Array中(包括NaN不像indexOf) ...
-
Nancy in .Net Core学习笔记 - 初识Nancy
前言 去年11月份参加了青岛MVP线下活动,会上老MVP衣明志介绍了Nancy, 一直没有系统的学习一下,最近正好有空,就结合.NET Core学习总结了一下. 注: 本文中大部分内容都是对官网文档的 ...
-
MYSQL事务隔离级别详解附加实验
参考: https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html http://xm-king.iteye.com/blog/77072 ...
-
[OC] 使用 cocoaPods 导入 AFNetworking
AFNetworking的GitHub地址: https://github.com/AFNetworking/AFNetworking 假设我们建立了一个叫做AFNWlearning的工程. 1.打开 ...