OpenGL学习之纹理

一、纹理相关

1.纹理概念

用简单光照明模型生成真实感图像,由于表面过于光滑单调,反而显得不真实。现实物体表面有各种表面细节,这些细节就叫纹理

纹理:体现物体表面的细节

2.纹理类型

颜色纹理:物体表面(平面或者曲面)花纹、图案

几何纹理:基于物体表面的微观几何形状

3.生成纹理的一般方法,是预先定义纹理模式,然后建立物体表面的点与纹理模式的点之间的对应。

4.纹理映射

当物体表面的可见点确定之后,以纹理模式的对应点参与光照模型进行计算,就可把纹理模式附加到物体表面上。这种方法称为纹理映射

OpenGL学习之纹理_第1张图片

5.纹理模式

图像纹理:将二维纹理图案映射到三维物体表面,绘制物体表面上的一点时,采用相应的纹理图案中相应的颜色值。

函数纹理:用数学函数定义简单的二维纹理图案,如方格地毯;或用数学函数定义随机高度场,生成表面粗糙纹理即几何纹理。

6.纹素(texel)和纹理坐标

使用纹素而不是像素来表示纹理对象中的显示元素,主要是为了强调纹理对象的应用方式。纹理对象通常是通过纹理图片读取到的,这个数据保存到一个二维数组中,这个数组中的元素称为纹素(texel),纹素包含颜色值和alpha值。纹理对象的宽度和高度应该是2的整数幂(如16,32,64,128,256)。要想获得纹理对象中的纹素,需要使用纹理坐标指定。

纹理坐标与纹理对象的大小无关,这样指定的纹理坐标当纹理对象大小变更时,依然能够工作。

纹理坐标使用规范化的值,大小范围为[0,1],纹理坐标使用uv表示,u轴从左到右,v轴从底向上,右上角为(1,1),左下角为(0,0)。

OpenGL学习之纹理_第2张图片

通过指定纹理坐标,可以映射到纹素,如一个256*256大小的二维纹理,坐标(0.5,1.0)对应的纹素即是(128,256).

7.纹理映射方法

建立物体空间表面和纹理空间之间的对应关系。

第一步:根据物体空间的表面坐标(x,y,z)计算其纹理空间坐标(u,v)值:对物体表面坐标(x,y,z)用u、v进行参数化

第二步:反求出参数u、v用物体表面坐标(x,y,z)的表达

第三步:根据纹理空间定义的纹理(u,v)得到该处的纹理值,并用此值取代光照模型中的相应项,实现纹理映射。

二、基于OpenGL的颜色纹理

1.加载与创建纹理

根据learn OpenGL CN教程,我们使用stb_image.h库(stb_image.h下载地址)来实现图像加载,把该库文件放到我们的工程文件目录底下后,将stb_image.h加入到我们的工程中,并在我们的.cpp中加入

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

然后我们可以找一张.jpg格式的图片(如木箱),使用它的stb_load函数。

int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);

2.指定纹理坐标

 // 指定顶点属性数据 顶点位置 颜色 纹理
    GLfloat vertices[] = {
        -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,0.0f, 0.0f,  // 0
        0.5f,  -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,1.0f, 0.0f,  // 1
        0.5f,  0.5f,  0.0f, 0.0f, 0.0f, 1.0f,1.0f, 1.0f,  // 2
        -0.5f, 0.5f,  0.0f, 1.0f, 1.0f, 0.0f,0.0f, 1.0f   // 3
    };
    GLushort indices[] = {
        0, 1, 2,  // 第一个三角形
        0, 2, 3   // 第二个三角形
    };

3.生成纹理

unsigned int texture;
glGenTextures(1, &texture);

//绑定纹理
glBindTexture(GL_TEXTURE_2D, texture);

//生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

 

void GL_APIENTRY glTexImage2D(GLenum target, GLint level, GLenum internalformat,
GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels);

 

第一个参数指定了纹理目标(Target),这个值必须是GL_TEXTURE_2D;

level执行细节级别,0是最基本的图像级别,n表示第N级贴图细化级别

internalformat指定纹理中的颜色组件。可选的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE,GL_LUMINANCE_ALPHA等几种。

width指定纹理图像的宽度,必须是2的n次方。纹理图片至少要支持64个材质元素的宽度

height:指定纹理图像的高度,必须是2的m次方。纹理图片至少要支持64个材质元素的高度

border:指定边框的宽度,必须为0

format:像素数据的颜色格式,不需要和internalformatt取值相同

倒数第二个type:指定像素数据的数据类型。可以使用的值有:GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_5_5_5_1

pixel:指定内存中指向图像数据的指针

4.设置纹理属性

OpenGL学习之纹理_第3张图片

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

5.顶点着色器

#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;
}

6.片元着色器

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

 

7.纹理环绕方式

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

 

当纹理坐标超出默认范围时,每个选项都有不同的的视觉效果。

 

OpenGL学习之纹理_第4张图片

可以使用glTexParamenter*函数对单独的一个坐标轴设置(s、t(三维纹理有r),和x,y,z等价)

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

第一个参数指定了纹理目标,由于使用的是2D纹理,则为GL_TEXTURE_2D

第二个参数需要我们指定设置的选项与应用的纹理轴,我们打算配置的是WRAP选项,并且指定s和t轴

第三个参数为纹理环绕方式

8.纹理过滤

当使用纹理坐标映射到纹素坐标时,正好得到对应纹素的中心位置的情况很少出现。当纹理坐标映射到一个浮点位的纹素位置时,我们需要进行纹理过滤

 

一种方式是对这个坐标取整,使用最佳逼近点来获取纹素,这种方式即点采样,也就是最近邻滤波(nearest neighbor filtering),这种方式容易导致走样误差,明显有像素块的感觉。其示意图如下(其中目标纹素位置距离左上角的纹素最近,因此它被选为样本颜色):

OpenGL学习之纹理_第5张图片

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

OpenGL学习之纹理_第6张图片

下图为两种滤波方法的效果图

OpenGL学习之纹理_第7张图片

可以看出,最近邻方法获取的纹素有明显的像素块,而线性滤波方法获取的纹素看起来比较平滑。两种方法各自应用于不同的场合,没有绝对的好坏,当需要制作8位图形效果(8 bit graphic,每个像素用8位字节表示)需要使用最近邻滤波。

当进行放大(Magnify)和缩小(Minify)操作的时候可以设置过滤的选项。

OpenGL学习之纹理_第8张图片

使用glTexParameter*函数为放大和缩小指定过滤方式。

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

9.多级渐进纹理

当物体在场景中离观察者很远,最终只用一个屏幕像素来显示时,这个像素纹理确定成为一个问题。OpenGL使用多级渐进纹理(Mipmap)来解决这个问题,即提前计算一系列的纹理图像,后一个纹理是前一个的二分之一。

原始纹理 256×256

Mip 1 = 128×128

Mip 2 = 64×64

Mip 3 = 32×32

Mip 4 = 16×16

Mip 5 = 8×8

Mip 6 = 4×4

Mip 7 = 2×2

Mip 8 = 1×1

多级渐进纹理的理念是:距观察者的距离超过一定的阈值,OpenGL会使用不同的渐进纹理,即最适合物体距离的那个。

Mipmap纹理示意图如下:

OpenGL学习之纹理_第9张图片

OpenGL中通过函数glGenerateMipmap(GL_TEXTURE_2D);来生成Mipmap,前提是已经指定了原始纹理。原始纹理必须自己通过读取纹理图片来加载,上面第一步我们已经实现了。

如果直接在不同等级的MipMap之间切换,会形成明显的边缘,因此对于Mipmap也可以同纹素一样使用滤波方法在不同等级的Mipmap之间滤波。要在不同等级的MipMap之间滤波,需要将之前设置的GL_TEXTURE_MIN_FILTER选项更改为以下选项之一:

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

使用设置glTexParameteri过滤方式(主要用于纹理缩小,用于纹理放大时会出现GL_INVALID_ENUM错误):

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

10.main函数(shader.cpp和shader.h不变,同OpenGL学习之着色器下)

#include 
#include 

#include 
#include"Shader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
	// glfw: initialize and configure
	// ------------------------------
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
	//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

														 // glfw window creation
														 // --------------------
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// glad: load all OpenGL function pointers
	// ---------------------------------------
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	// build and compile our shader zprogram
	// ------------------------------------
	Shader ourShader("VertexSource.txt", "fragmentSource.txt");

	// set up vertex data (and buffer(s)) and configure vertex attributes
	// ------------------------------------------------------------------
	float vertices[] = {
		// positions          // colors           // texture coords
		0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
		0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // bottom right
		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
		-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left 
	};
	unsigned int indices[] = {
		0, 1, 3, // first triangle
		1, 2, 3  // second triangle
	};
	unsigned int VBO, VAO, EBO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);

	glBindVertexArray(VAO);

	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	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);

	// position attribute
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	// color attribute
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	// texture coord attribute
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);


	// load and create a texture 
	// -------------------------
	unsigned int texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object
										   // set the texture wrapping parameters
	glTexParameteri(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 parameters
	glTexParameteri(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 mipmaps
	int width, height, nrChannels;
	// The FileSystem::getPath(...) is part of the GitHub repository so we can find files on any IDE/platform; replace it with your own image path.
	unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	stbi_image_free(data);


	// render loop
	// -----------
	while (!glfwWindowShouldClose(window))
	{
		// input
		// -----
		processInput(window);

		// render
		// ------
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// bind Texture
		glBindTexture(GL_TEXTURE_2D, texture);

		// render container
		ourShader.use();
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

		// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
		// -------------------------------------------------------------------------------
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// optional: de-allocate all resources once they've outlived their purpose:
	// ------------------------------------------------------------------------
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	// glfw: terminate, clearing all previously allocated GLFW resources.
	// ------------------------------------------------------------------
	glfwTerminate();
	return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	// make sure the viewport matches the new window dimensions; note that width and 
	// height will be significantly larger than specified on retina displays.
	glViewport(0, 0, width, height);
}

11.结果图 

OpenGL学习之纹理_第10张图片

 

12. 纹理与颜色混合

在顶点着色器中修改

FragColor = texture(texture1, TexCoord) * vec4(ourColor, 1.0);

结果如下

 OpenGL学习之纹理_第11张图片

 

13.多个纹理单元 

在一个程序中可以使用多个纹理单元加载多个2D纹理。

(1)纹理单元

一个纹理的位置通常称为纹理单元,一个纹理的默认激活纹理单元是0。通过把纹理单元赋值给采样器,一次可以绑定多个纹理。

ourShader.use();
// 使用0号纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Id, "texture1"), 0); 
// 使用1号纹理单元
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Id, "texture2"), 1); 

(2)着色器

#version 330 core
...

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

上面用到的GLSL内建函数mix()需要接受两个值作为参数,并对它们根据第三个参数进行线性插值,此处的0.2表示返回80%第一个输入颜色和20%第二个输入颜色。

(3)main函数

在释放第一张纹理图片后绑定第二张纹理(awesomeface.png)

unsigned int texture2;
	glGenTextures(1, &texture2);
	glBindTexture(GL_TEXTURE_2D, texture2); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object
											// set the texture wrapping parameters
	glTexParameteri(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 parameters
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	stbi_set_flip_vertically_on_load(true);

	// load image, create texture and generate mipmaps
	data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
	if (data)
	{
		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);

	ourShader.use();
	glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
	ourShader.setInt("texture2", 1);
	// render loop
	// -----------
	while (!glfwWindowShouldClose(window))
	{
		// input
		// -----
		processInput(window);

		// render
		// ------
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// bind Texture
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture1);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, texture2);

		ourShader.use();
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

		
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

以上代码中

stbi_set_flip_vertically_on_load(true);

 使用的原因是:OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部,所以我们在加载图片时翻转y轴。

源代码中加载的第二张图片如果是一张没有透明成分的图就会报错,而用有透明成分的png图片甚至是不存在的图片都可以通过。所以说若要正确运行,第二张图片不仅要用png格式而且图片中要有透明的成分。由于我们加的第二个纹理图片是png格式,有RGBA四个通道,所以这一句修改为

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

(4)结果图 

OpenGL学习之纹理_第12张图片

参考文章: https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/#_8

                  https://blog.csdn.net/wangdingqiaoit/article/details/51457675

 

 

你可能感兴趣的:(opengl学习,三维渲染,纹理,OpenGL,纹理环绕,纹理过滤,纹理单元)