OpenGL学习笔记(3) 纹理
关于纹理
一般游戏里的物体不一定都是纯色的物体,物体上面会有一些图片贴在上面,比如墙壁,箱子,地板,可以看到砖头、木板和大理石组成的图片,要把图片贴到计算机里的几何图形的话,就要把图片的颜色采样贴到几何图形上,采样是计算机经常干的工作,计算机要处理自然中的数据就需要对数据进行采样,比如说对声音采样就是采集声音的频率和频幅,分别代表声音的音色和声量,当然,采集到的是一个模拟量,然而计算机无法处理模拟量,所以需要将模拟量量化为数字量,也就是二进制数进行存储,同理,颜色也是一样,现代计算机中颜色一般以RGB的形式,分别代表Red,Green,Blud三种颜色在这个颜色中的占比,一般使用3个字节存储,也就是说颜色的数量可以达到2^24种,已经足够用了,想要把采集到的颜色贴到物体上的话,我们需要指定顶点的纹理坐标,告诉着色器要从纹理的哪个点开始采样, 纹理坐标的范围是0到1
项目的代码
learnOpenGL里的图解

纹理环绕
纹理坐标的范围是0到1,假如超出这个范围的话,在OpenGL里会有几种方式来贴图,这些方式叫做纹理环绕方式
- GL_REPEAT 对纹理的默认行为。重复纹理图像。
- GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。
- GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
- GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。
在我的工程里有用来测试的函数SurroundTest,把环绕方式和过滤模式封装成枚举方便调试
//环绕模式
enum class SurroundMode
{
	Repeat = GL_REPEAT,						//重复纹理图像
	MirroredRepeat = GL_MIRRORED_REPEAT,	//镜像重复纹理图像
	ClampToEdge = GL_CLAMP_TO_EDGE,			//将边缘拉伸
	ClampToBoreder = GL_CLAMP_TO_BORDER		//超出的部分为指定边缘颜色
};
//过滤方式
enum class FilteringMode
{
	Nearest = GL_NEAREST,
	Linear = GL_LINEAR,
};
将纹理坐标的分量值设置到[0,1]之外,进行测试
	float vertices[] = {
		//     ---- 位置 ----     - 纹理坐标 -
			 0.5f,  0.5f, 0.0f,  2.0f, 2.0f,    // 右上
			 0.5f, -0.5f, 0.0f,  2.0f, -1.0f,   // 右下
			-0.5f, -0.5f, 0.0f,  -1.0f, -1.0f,  // 左下
			-0.5f,  0.5f, 0.0f,  -1.0f, 2.0f    // 左上
	};
测试结果

过滤模式
可以看到环绕方式测试的图片是有一丢丢模糊的(其实是用错过滤模式了),这是因为用了线性过滤模式(Linear),使用线性过滤模式采样的时候,每个像素的颜色会和周围的颜色进行混合算出一个插值,这个插值近似于这些像素
而采用 邻近过滤(Nearest) 的话,那么像素的颜色就是采样器采到的颜色,如果分辨率小的图片贴到大的物体上的话,就会出现颗粒状的图案,但使用线性过滤模式的话这些颗粒就会变得比较平滑
测试一下

是不是可以很明显地看出Linear方式采样的珂朵莉的眼神比较温柔啊,这就是经过平滑处理过的图片。在这里顺便说下unity的三种过滤模式
- Point 点像素过滤,纹理像素会在附近变为块状。
- Bilinear 双线性过滤,平均纹理样本
- Trilinear 三线性过滤,平均纹理样本,同时也在多级渐远纹理级别之间混合
测试代码
首先是读取图片用的代码,对 learnOpenGL 里的代码进行了一次封装,针对不同位深度的图片进行处理,位深度就是图片记录每个像素点的位数,24位表示3个float,也就是RGB值,32位则是4个float,即RGBA,读取图片用了官方介绍的stb_image库
//加载纹理
template <typename S1 = std::string>
void LoadTexture(unsigned int &texture, S1&& pic)
{
	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((TEXTURE_PATH + std::forward<std::string>(pic)).c_str(), &width, &height, &nrChannels, 0);
	std::cout << "nrChannels = " << nrChannels << endl;
	if (data)
	{
		//位深度为24,3个通道(jpg
		if (nrChannels == 3)
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		//位深度为32,4个通道(png
		else if (nrChannels == 4)
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 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);
	data = nullptr;
}
着色器代码
顶点着色器
vertex_3.vs
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
    gl_Position = vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}
位置0是顶点的坐标,位置1是输入的纹理坐标,要通过顶点着色器将纹理坐标输出到片段着色器
片段着色器
fragment_3.fs
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
    FragColor = texture(ourTexture, TexCoord);
}
TexCoord是从顶点着色器接收的纹理坐标,使用texture函数来采样,第一个参数是采样器(Sampler),第二个参数是纹理坐标,着色器输出过滤后的颜色值FragColor
接下来是就是正常的测试代码了
void NormalTest()
{
	float vertices[] = {
		//     ---- 位置 ----     - 纹理坐标 -
			 0.9f,  0.9f, 0.0f,  1.0f, 1.0f,   // 右上
			 0.9f, -0.9f, 0.0f,  1.0f, 0.0f,   // 右下
			-0.9f, -0.9f, 0.0f,  0.0f, 0.0f,   // 左下
			-0.9f,  0.9f, 0.0f,  0.0f, 1.0f    // 左上
	};
	//索引
	unsigned int indices[] = {
		0,1,3,
		1,2,3
	};
	//编译着色器
	Shader ourShader("vertex_3.vs", "fragment_3.fs");
	ourShader.use();//glUseProgram(shaderProgram);
	unsigned int VAO, VBO, EBO;
	//顶点数组
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);
	//绑定顶点数组缓存
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//索引缓存
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//生成纹理
	unsigned int texture1;
	LoadTexture(texture1, std::move("picture4.png"));	//加载纹理
	glBindTexture(GL_TEXTURE_2D, texture1);//绑定纹理
	// 位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	// 纹理属性
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));	//最后一个参数是数据的起点
	glEnableVertexAttribArray(1);
	while (!glfwWindowShouldClose(glWindow))
	{
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//draw
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);
		glfwPollEvents();
		glfwSwapBuffers(glWindow);
	}
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glfwTerminate();
}
测试结果

珂朵莉好可爱~
OpenGL学习笔记(3) 纹理的更多相关文章
- OpenGL学习笔记3——缓冲区对象
		在GL中特别提出了缓冲区对象这一概念,是针对提高绘图效率的一个手段.由于GL的架构是基于客户——服务器模型建立的,因此默认所有的绘图数据均是存储在本地客户端,通过GL内核渲染处理以后再将数据发往GPU ... 
- OpenGL学习笔记:拾取与选择
		转自:OpenGL学习笔记:拾取与选择 在开发OpenGL程序时,一个重要的问题就是互动,假设一个场景里面有很多元素,当用鼠标点击不同元素时,期待作出不同的反应,那么在OpenGL里面,是怎么知道我当 ... 
- OpenGL学习笔记(四)纹理
		目录 要完成的纹理效果 纹理环绕方式 纹理过滤 多级渐远纹理 加载与创建纹理 stb_image库的使用方法 生成纹理对象 应用纹理 纹理单元 参考资料:OpenGL中文翻译 要完成的纹理效果 纹理是 ... 
- webgl学习笔记五-纹理
		写在前面 建议先阅读下前面我的三篇文章. webgl学习笔记一-绘图单点 webgl学习笔记二-绘图多点 webgl学习笔记三-平移旋转缩放 术语 : 纹理 :图像 图形装配区域 :顶点着色器顶点坐标 ... 
- OpenGL学习笔记2017/8/29
		OpenGL学习日志: 感谢doing5552 的OpenGL入门学习:http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html 相 ... 
- opengl学习笔记(四):openCV读入图片,openGL实现纹理贴图
		在opengl中实现三维物体的纹理贴图的第一步就是要读入图片,然后指定该图片为纹理图片. 首先利用opencv的cvLoadImage函数把图像读入到内存中 img = cvLoadImage(); ... 
- opengl学习笔记(三):经过纹理贴图的棋盘
		opengl纹理贴图的步骤: 1:创建纹理对象,并为它指定一个纹理 2:确定纹理如何应用到每个像素上 3:启用纹理贴图功能 4:绘制场景,提供纹理坐标和几何图形坐标 注意:纹理坐标必须在RGBA模式下 ... 
- OpenGL学习笔记(1) 画一个三角形
		最近找实习有一丢丢蛋疼,沉迷鬼泣5,四周目通关,又不想写代码,写篇笔记复习一下,要好好学图形学啊 用OpenGL画一个三角形 项目的简介 记录一下跟着learnOpenGL学习的过程 笔记里的代码放在 ... 
- opengl学习笔记(一)
		ubuntu下opengl的安装及配置 OpenGL 是一套由SGI公司发展出来的绘图函数库,它是一组 C 语言的函数,用于 2D 与 3D 图形应用程序的开发上.OpenGL 让程序开发人员不需要考 ... 
随机推荐
- sql server 使用链接服务器连接Oracle,openquery查询数据
			对接问题描述:不知道正式库oracle数据库账户密码,对方愿意在对方的客户端上输入账号和密码,但不告诉我们 解决方案:使用一台sql server作为中间服务器,可以通过转存数据到sql serv ... 
- Git学习文档——文件状态git status
			1.已经跟踪的文件有三种状态 已跟踪的文件,即被纳入版本控制的文件,又分为未修改(unmodified).已修改(modified).已暂存(staged)三种状态. 如图: 当在工作目录中新加入一个 ... 
- CF893F:Subtree Minimum Query(线段树合并)
			Description 给你一颗有根树,点有权值,m次询问,每次问你某个点的子树中距离其不超过k的点的权值的最小值.(边权均为1,点权有可能重复,k值每次询问有可能不同,强制在线) Input 第一行 ... 
- 使用Discuz!自带参数防御CC攻击以及原理
			CC攻击确实是很蛋疼的一种攻击方式,Discuz!的配置文件中已经有了一个自带的减缓CC攻击的参数,在配置文件config.inc.php中: $attackevasive = 0; // 论坛防御 ... 
- ArrayList实现原理及源码分析之JDK8
			转载 ArrayList源码分析 一.ArrayList介绍 Java 集合框架主要包括两种类型的容器: 一种是集合(Collection),存储一个元素集合. 一种是图(Map),存储键/值对映射. ... 
- winform程序打包成exe文件
			拿到一个实现功能的winform小程序,如何利用NSIS工具制作安装包? 1.NSIS工具下载地址 点我下载 2.启动NSIS工具,如图点击 3.选择"使用脚本向导创建新的脚本文件" ... 
- Sublime Text常用设置之个人配置
			一.安装 1.安装包下载: http://www.sublimetext.com/3 (傻瓜式安装) 2.Package Control安装: 1)Ctrl+~或者View——Show Consol ... 
- C语言程序设计I—第十二周教学
			第十二周教学总结(19/11-25/11) 教学内容 第4章 循环结构 4.5 循环结构程序设计 课前准备 在蓝墨云班课发布资源: PTA:2018秋第十二周作业4.5 分享码:B7FA52A13B6 ... 
- 以代码爱好者角度来看AMD与CMD(转)
			随着浏览器功能越来越完善,前端已经不仅仅是切图做网站,前端在某些方面已经媲美桌面应用.越来越庞大的前端项目,越来越复杂的代码,前端开发者们对于模块化的需求空前强烈.后来node出现了,跟随node出现 ... 
- HDU 1754 I Hate It(线段树之单点更新 区间最值查询)
			I Hate It Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ... 
