纹理

每个顶点就会关联着一个纹理坐标,Texture Coordinate:

标注采样的位置,然后在其他片段上进行片段差值。Fragment Interpolation

使用纹理坐标获取纹理颜色叫做采样(Sampling),下面的图片展示了我们是如何把纹理坐标映射到三角形上的。


纹理超出(0,0)到(1,1)时的环绕方式。


纹理环绕方式:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一个参数指定纹理目标,2D
第二个参数指定纹理轴。
最后一个参数传递一个环绕方式。


如果选择GL_CLAMP_TO_BORDER选项,还要设置边缘的颜色,需要使用glTexParameter函数的fv后缀形式。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);


纹理过滤:
因为纹理坐标不依赖于分辨率,所以OpenGL需要知道怎么讲纹理像素映射到纹理坐标。
过滤分两种:
临近过滤:GL_NEAREST,Nearest Neighbor Filtering,是默认处理方式。
选择离中心点最近的那个像素颜色。
线性过滤,计算差值,选择混合色。

下图为两种的区别:

纹理环绕方式:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一个参数指定纹理目标,2D
第二个参数指定纹理轴。
最后一个参数传递一个环绕方式。


如果选择GL_CLAMP_TO_BORDER选项,还要设置边缘的颜色,需要使用glTexParameter函数的fv后缀形式。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);


纹理过滤:
因为纹理坐标不依赖于分辨率,所以OpenGL需要知道怎么讲纹理像素映射到纹理坐标。
过滤分两种:
临近过滤:GL_NEAREST,Nearest Neighbor Filtering,是默认处理方式。
选择离中心点最近的那个像素颜色。
线性过滤,计算差值,选择混合色。


可以为放大Magnify和缩小Minify时设置纹理过滤选项。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);


多级渐远纹理:
简单概念:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理。性能很好。
两个级别Level层之间会产生僵硬,可以用之前提到的两种过滤。
也可以用下面四中过滤方式:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
四种方式为:
过滤方式 描述
GL_NEAREST_MIPMAP_NEAREST 使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
GL_LINEAR_MIPMAP_NEAREST 使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
GL_NEAREST_MIPMAP_LINEAR 在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
GL_LINEAR_MIPMAP_LINEAR 在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样


加载与创建纹理


SOIL又要下载。


后面使用SOIL_load_image函数加载图片。
参数:图片路径,两个int指针返回宽度和高度,图片的通道数量-0,加载方式RGB所以存储一个数组。


生成纹理:
纹理同样使用ID引用。
GLuint texture;
glGenTexture;




片段着色器也应该能访问纹理对象,GLSL提供一个供纹理对象使用的内建数据类型,采样器Sampler,它以纹理类型作为后缀。
之后会把纹理赋值给这个uniform


我们使用内建的texture函数来采样纹理颜色,它第一个参数是纹理采样器,第二个是纹理坐标。

法克,困扰了将近一上午的问题竟然图片路径问题,直接把图片拖到Source Files的话文件并不在该文件夹内。

得到图像:


代码:

source:

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"
#include <SOIL.h>
void key_callback(GLFWwindow* windows, int key, int scancode, int action, int mode);


//顶点着色器源码:

const GLuint WIDTH = 800, HEIGHT = 600;
int main()
{

	std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;

	//这都是初始化GLFW,后面的要看文档
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);


	//创建窗口,
	GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);//通知GLFW将我们窗口的上下文设置为当前线程的主上下文


	//不知道回调函数为什么要加在这里??
	//glfwSetKeyCallback(window, key_callback2);回调函数只有最下面那个有作用
	glfwSetKeyCallback(window, key_callback);


	//初始化GLEW(管理OpenGL的函数指针)
	glewExperimental = GL_TRUE;
	if (glewInit() != GLEW_OK)
	{
		std::cout << "Failed to initialize GLEW" << std::endl;
		return -1;
	}

	
	glViewport(0, 0, WIDTH, HEIGHT);//前两个控制左下角的位置,后面是窗口宽度高度(像素)

	Shader ourShader("shader.vs", "shader.frag");
 
	GLfloat 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    // 左上
	};

	GLuint indices[] = {  // Note that we start from 0!
		0, 1, 3, // First Triangle
		1, 2, 3  // Second Triangle
	};

	


	GLuint VBO, VAO,EBO;
	//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象??
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &EBO);

	//绑定VAO
	glBindVertexArray(VAO);
	//复制顶点数组到缓冲中,glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数
	glBindBuffer(GL_ARRAY_BUFFER, VBO);//glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//这局是把定义好的顶点数据复制到缓冲的内存中。

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	

	//设置顶点属性指针,和颜色属性
	
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
	glEnableVertexAttribArray(0);
	
	//因为现在是两个属性,需要向右移动步长,6个
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
	glEnableVertexAttribArray(1);
	//由于我们添加了额外的顶点属性,我们要告诉OPenGL新的顶点格式。
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
	glEnableVertexAttribArray(2);
	glBindVertexArray(0);
	//安全?
	
	
	
	//生成纹理
	GLuint texture;
	glGenTextures(1, &texture);
	//glGenTextures函数首先需要输入生成纹理的数量,然后储存在GLuint数组中,让之前任何纹理指令都可以配置当前绑定的纹理。
	//下面这一句是绑定纹理。
	glBindTexture(GL_TEXTURE_2D, texture);
	//设置到两个轴上
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEAT (usually basic wrapping method)
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//上面两句是对S和T轴设置环绕方式,环绕方式为重复纹理图像。

	//下面生成纹理。2D纹理,因此纹理目标是GL_TEXTURE_2D
	//下面两句设置方法和缩小的纹理过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	//使用SOIL加载图片,自此到循环前都是加载并生成纹理。
	int width, height;
	std::cout << SOIL_last_result() << std::endl;
	unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
	std::cout << SOIL_last_result() << std::endl;
	if (image)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
		//第一个参数纹理目标Target,会生成与当前绑定纹理对象同样纹理
		//设置纹理指定多级渐远纹理级别,0,基本级别
		//第三个参数,纹理储存为何种格式,RGB
		//四五是设置最终的纹理的宽度和高度,就用之前的变量。
		//0
		//七八位源图的格式和数据类型,使用RGB加载并储存为char(byte)数组
		//最后参数为图像的数据
	}
	else
	{
		std::cout << "Failed to load texture." << std::endl;
	}

	//当前绑定的纹理对象会被附加上纹理图像,然而只有0级别的纹理图像被加载 了,多级的话需要手动设置
	//或者可以,在生成纹理之后调用glGenerateMipmap,这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
	glGenerateMipmap(GL_TEXTURE_2D);
	SOIL_free_image_data(image);
	glBindTexture(GL_TEXTURE_2D, 0);





	//防止退出的循环
	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents();


		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//glClearColor是一个状态设置函数,glClear是一个状态应用函数

		glBindTexture(GL_TEXTURE_2D, texture);
		//绘图
		ourShader.Use();

		

		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);

		glfwSwapBuffers(window);
	}

	//回调函数
	
	//释放:
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);

	glfwTerminate(); //释放/删除之前的分配的所有资源
	return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{

	std::cout << key << std::endl;
	//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
	//关闭应用程序
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
}

顶点着色器:

#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec2 texCoord;
out vec3 ourColor;
out vec2 TexCoord;

void main()
{
	gl_Position = vec4(position, 1.0f);
	ourColor = color;
	TexCoord = texCoord;
}

片段着色器:

#version 330 core
in vec3 ourColor;
in vec2 TexCoord;
out vec4 color;
uniform sampler2D ourTexture;
void main()
{
color = texture(ourTexture, TexCoord);
}


texture函数采样纹理颜色,第一个参数是纹理采样器,第二个是纹理坐标,该函数会使用之前设置好的纹理参数
对响应色值进行采样,输出纹理坐标过滤后的颜色。


采样器Sampler还不是很理解??作用大概是把纹理对象传给片段着色器。

然后可以把纹理颜色和顶点颜色混合

color = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0f);

效果图:



OK,小面的说明正好解决我的疑惑。
没有给uniform赋值的原因:使用glUniform1i,我们可以给纹理采样器分配一个位置值。
这样我们能在一个片段着色器中设置多个纹理,一个纹理称为一个纹理单元。0为默认不用设置!

纹理单元TExture Unit,默认为0,
可以一次绑定多个纹理,通过纹理单元。
glActiveTexture(GL_TEXTURE0);//先激活
glBinfTexture(GL_TEXTURE_2D,texture);//
color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
最终输出颜色现在是两个纹理的结合。GLSL内建的mix函数需要接受两个值作为参数,
并对它们根据第三个参数进行线性插值。。
如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。
0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

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

纹理单元:

多个纹理单元可以混合mix:

多个纹理单元要激活了,而且写法也有了区别。注意下:

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"
#include <SOIL.h>
void key_callback(GLFWwindow* windows, int key, int scancode, int action, int mode);


//顶点着色器源码:

const GLuint WIDTH = 800, HEIGHT = 600;
int main()
{

	std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;

	//这都是初始化GLFW,后面的要看文档
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);


	//创建窗口,
	GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
	if (window == nullptr)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);//通知GLFW将我们窗口的上下文设置为当前线程的主上下文


	//不知道回调函数为什么要加在这里??
	//glfwSetKeyCallback(window, key_callback2);回调函数只有最下面那个有作用
	glfwSetKeyCallback(window, key_callback);


	//初始化GLEW(管理OpenGL的函数指针)
	glewExperimental = GL_TRUE;
	if (glewInit() != GLEW_OK)
	{
		std::cout << "Failed to initialize GLEW" << std::endl;
		return -1;
	}

	
	glViewport(0, 0, WIDTH, HEIGHT);//前两个控制左下角的位置,后面是窗口宽度高度(像素)

	Shader ourShader("shader.vs", "shader.frag");
 
	GLfloat 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    // 左上
	};

	GLuint indices[] = {  // Note that we start from 0!
		0, 1, 3, // First Triangle
		1, 2, 3  // Second Triangle
	};

	


	GLuint VBO, VAO,EBO;
	//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象??
	glGenBuffers(1, &VBO);
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &EBO);

	//绑定VAO
	glBindVertexArray(VAO);
	//复制顶点数组到缓冲中,glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数
	glBindBuffer(GL_ARRAY_BUFFER, VBO);//glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//这局是把定义好的顶点数据复制到缓冲的内存中。

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	

	//设置顶点属性指针,和颜色属性
	
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
	glEnableVertexAttribArray(0);
	
	//因为现在是两个属性,需要向右移动步长,6个
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
	glEnableVertexAttribArray(1);
	//由于我们添加了额外的顶点属性,我们要告诉OPenGL新的顶点格式。
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
	glEnableVertexAttribArray(2);
	glBindVertexArray(0);
	//安全?
	
	
	
	//生成纹理
	GLuint texture1,texture2;
	glGenTextures(1, &texture1);//??
	//glGenTextures(1, &texture2);
	//glGenTextures函数首先需要输入生成纹理的数量,然后储存在GLuint数组中,让之前任何纹理指令都可以配置当前绑定的纹理。
	//下面这一句是绑定纹理。
	//glActiveTexture(GL_TEXTURE0);//多个位置时,使用前需要先激活
	glBindTexture(GL_TEXTURE_2D, texture1);
	//glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);

	//同理设置第二个纹理位置
	//glActiveTexture(GL_TEXTURE1);//多个位置时,使用前需要先激活
	//glBindTexture(GL_TEXTURE_2D, texture2);
	//glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);



	//设置到两个轴上
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEAT (usually basic wrapping method)
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//上面两句是对S和T轴设置环绕方式,环绕方式为重复纹理图像。

	//下面生成纹理。2D纹理,因此纹理目标是GL_TEXTURE_2D
	//下面两句设置方法和缩小的纹理过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	//使用SOIL加载图片,自此到循环前都是加载并生成纹理。
	int width, height;
	std::cout << SOIL_last_result() << std::endl;
	unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
	std::cout << SOIL_last_result() << std::endl;
	if (image)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
		//第一个参数纹理目标Target,会生成与当前绑定纹理对象同样纹理
		//设置纹理指定多级渐远纹理级别,0,基本级别
		//第三个参数,纹理储存为何种格式,RGB
		//四五是设置最终的纹理的宽度和高度,就用之前的变量。
		//0
		//七八位源图的格式和数据类型,使用RGB加载并储存为char(byte)数组
		//最后参数为图像的数据
	}
	else
	{
		std::cout << "Failed to load texture." << std::endl;
	}

	//当前绑定的纹理对象会被附加上纹理图像,然而只有0级别的纹理图像被加载 了,多级的话需要手动设置
	//或者可以,在生成纹理之后调用glGenerateMipmap,这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
	glGenerateMipmap(GL_TEXTURE_2D);
	SOIL_free_image_data(image);
	glBindTexture(GL_TEXTURE_2D, 0);





	glGenTextures(1, &texture2);
	glBindTexture(GL_TEXTURE_2D, texture2);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	// Set texture filtering
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	image = SOIL_load_image("awesomeface.png", &width, &height, 0, SOIL_LOAD_RGB);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
	glGenerateMipmap(GL_TEXTURE_2D);
	SOIL_free_image_data(image);
	glBindTexture(GL_TEXTURE_2D, 0);

	//防止退出的循环
	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents();


		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//glClearColor是一个状态设置函数,glClear是一个状态应用函数

		
		//绘图
		ourShader.Use();

		
		glActiveTexture(GL_TEXTURE0);//这句是激活。
		glBindTexture(GL_TEXTURE_2D, texture1);
		glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, texture2);
		glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);

		//这里因为要使用第二个纹理,改变一点渲染流程,先绑定两个纹理到对应的纹理单元,然后定义哪个uniform采样器应对哪个纹理单元。


		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);

		glfwSwapBuffers(window);
	}

	//回调函数
	
	//释放:
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	glfwTerminate(); //释放/删除之前的分配的所有资源
	return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{

	std::cout << key << std::endl;
	//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
	//关闭应用程序
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
}



图示上下颠倒,原因OpenGL的y轴的(0,0)坐标在图片底部,但图片的y轴(0,0)坐标通常在顶部。

我们可以编辑顶点着色器来自动翻转y坐标,替换TexCoord的值为TexCoord = vec2(texCoord.x, 1.0f - texCoord.y);。

替换后图片正过来了,改的也就是纹理坐标的y轴。



联系:

1.让笑脸朝另一个方向:

把应对于y轴的方法,同样施给x轴。

TexCoord = vec2(1.0f-texCoord.x, 1.0f-texCoord.y);


2.尝试用不同的纹理环绕方式,设定一个从0.0f到2.0f范围内的(而不是原来的0.0f到1.0f)纹理坐标。试试看能不能在箱子的角落放置4个笑脸:参考解答,结果。记得一定要试试其他的环绕方式。

把纹理坐标改成0.5时的效果:


设置成0.0到2.0效果:


第一个纹理目标更换为GL_CLAMP_TO_EDGE,边缘拉伸环绕方式。


镜像的环绕方式:GL_MIRRORED_REPEAT


第四种环绕方式:GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。

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

通过该函数来指定颜色。


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

更改纹理过滤方式:GL_NEAREST   把范围缩的很小:


稍微有那么一点点的evil。

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

这个做出来还是很有成就感的。

更改uniform全局关键字:

glUniform1f(glGetUniformLocation(ourShader.Program, "para"), Offset);
在回调函数中添加操作:

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{

	std::cout << key << std::endl;
	//用户按下ESC键,我们设置window窗口WindowShouldClose属性为true
	//关闭应用程序
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GL_TRUE);
	if (key == GLFW_KEY_UP&&action == GLFW_PRESS)
	{
		Offset += 0.1;
		if (Offset >= 1.0f) Offset = 1.0f;
	}
	if (key == GLFW_KEY_DOWN&&action == GLFW_PRESS)
	{
		Offset -= 0.1;
		if (Offset <= 0.0f) Offset = 0.0f;
	}
}

不要忘了添加一个全局变量 Offset


如果读者有什么问题,可以直接留言或者私信我,我会努力帮忙解决的。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值