老子不信我学不会OpenGL系列!005 纹理……!

好……曾经的我就是在这里躺尸的…………,因为这节要装个库,用来读取图片,然鹅我原来似乎没装上……图片怎么都读不进来…………。这次……试试吧……我相信我可以的……。

原教程讲了一堆东西,然后才开始安装库,我就很不高兴。所以我先安装库(为了可以自己试验,要不然,你啥都干不了,只能看着他干讲)

好吧,还是先说一下贴图(纹理,我搞不清这两个的区别,就先当一个东西吧,但是他们应该不是一个意思。英文名字是:Texture)是个啥吧:


Texture:

如果我们想通过给每个点赋予颜色的方法,产生一张细节很丰富的图片(比如:砖砌的墙面)。可想而知,我们会需要很多很多的顶点,这个开销可是不能小看的。

为了解决上面的问题,我们就想了另外一直办法,就是把Texture传给shader。Texture就是一张2D的图片(有时也可以是3D,1D的),然后把这张图片的每个像素上的颜色,涂到给模型的对应位置上。这样做的效果就像是把这张图片“贴”到了模型的一个面上(或者几个面上,跟折纸一样,把图片折一下就好了)。

为了把纹理贴到一个三角形上(就先说一个三角形吧,其他的也是由一个个三角形构成的),需要告诉OpenGL,三角形的每个点,映射到纹理的什么位置。我们不需要对每个像素点(实际上是片元,fragment),都指定映射位置;我们只需要对三个顶点指定好映射位置就好了,剩下的事情,graphics pipeline会帮我们做(就是做个线性插值嘛)。每个顶点的映射位置叫做:纹理坐标。通过纹理坐标,在纹理上取出颜色的过程叫做:采样(sampling)。

纹理坐标 可以映射到图片的的范围是从 (0,0)对应于纹理图片的左下角 到 (1,1)对应于右上角:


当纹理坐标超出这个范围的时候,会映射成什么,我们是可以自己配置的,下面会说。

采样:当texture被放大或缩小的时候,一个片元,与一个texture上的像素就不是一一对应的关系了。这时片元如何选择texture中的像素,就有多种方法:nearest,linear。现在只说明这两种的区别,具体的配置方法下面会说。

nearest是选择纹理坐标最近的像素(就是纹理坐标所在的像素)(每个片元都有一个纹理坐标):


linear是对附近的像素进行插值,距离纹理坐标越近的像素,其对这个片元颜色的贡献就越大:


下面是两种效果的对比:

对于图片放大(多个fragment对应一个texture中的像素)和缩小(一个fragment覆盖(对应)了多个texture中的像素),我们可以(也必须)分别设置他们的采样方式。而对于缩小来说,会出现问题(不只是消耗内存的问题),所以引入了mipmap,下面会说。



1.原始数据:

要为每个点增加纹理坐标的属性:

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

导入stb_image.h并读取图片,这里下载他:


然后放在之前的那个my_include文件夹里:(你放在工程目录里我也没意见,这个随意啦,我想放在这里而已)


然后导入(这里还要多一行,我也不知道干啥用的):(我试了一下,第一行还是加上吧,我不加出错了,加上了就没事)

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

然后是读取图片:(这里是一张盒子的图片,直接右键另存为就行)

	int imageW, imageH, imageNChs;
	unsigned char *data = stbi_load("..\\my_OtherFiles\\container.jpg", &imageW, &imageH, &imageNChs, 0);

stbi_load(文件路径,存储图像宽度的变量地址,高度的,通道个数的,写0吧……我也不知道这个干啥的)


2.创建texture对象:

与创建VBO,EBO类似,3步:1.声明ID,为这个ID分配空间,2.为这个ID代表的对象绑定类型,并激活,3.为这个对象赋值

	GLuint tex;
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageW, imageH, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);

glTexImage2D(接口,mipmap层级,将texture用什么颜色类型存储,图片宽的像素数,高的,写0,texture的颜色类型,texture的数据类型,texture的数据)

(我这里:texture,纹理,贴图 是一个意思,是一张图片;texture对象,纹理对象 是另一个意思,是指刚刚我们创建出来的那个东西)

之后还要让OpenGL自动生成mipmap:

	glGenerateMipmap(GL_TEXTURE_2D);

mipmap:在图片被缩小(放大之后没什么关系)之后(这个物体远离我们的时候,texture就会被缩小),我们一个fragment实际上会覆盖了很多的texture上的像素,然而fragment的纹理坐标实际上只能指向单一的一个像素,这时候用这一个像素代表一群像素明显会出问题。解决这个问题的办法就是用一列尺寸从大到小的texture来代替单一的一个texture,然后再选用合适尺寸的texture就可以了。实际上可以简单的理解成,根据距离来选取:当物体距离我们远的时候,就选择尺寸小的texture,近就选大的。这时候,每个不同尺寸的texture就是一个mipmap。看起来是这个样子:


在选不同层的mipmap的时候也有两种方法:nearest就是选择尺寸最接近的,linear就是在两种尺寸的颜色直接进行插值(应该是颜色,我不清楚,反正就是做个插值)。

在实际应用的时候,切换mipmap时,nearest会有一个突变的感觉,linear会好一些。

最后不要忘了对第一步中读进来的data释放内存:

stbi_image_free(data);


3.配置Texture:

刚刚我们说了什么采样,什么nearest,linear等等,怎么将这些属性设置成我们想要的呢?现在就来说。

配置Texture用的都是一类函数:glTexParameter啥( 接口,属性,属性值 )。“啥”是代表了 属性值 的类型,i就代表int,fv就代表float类型的向量(即数组)。这个函数会对绑定在当前接口上的texture对象进行设置。所以要记得先绑定再配置。

我们先把刚刚讲过的配置好:

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

如果你不想用mipmap(我们这里没必要用,毕竟没有z方向的变化)

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

第二个参数:

  • GL_TEXTURE_MIN_FILTER:是配置图片缩小时候的采样方式
  • GL_TEXTURE_MAG_FILTER:是放大时候的。

第三个参数:

  • GL_后面是配置 平面的采样方式,就是与一张图片旁边像素相关的(是直接用纹理坐标中心的(NEAREST),还是用旁边像素到纹理坐标中心的距离进行插值(LINEAR))
  • MIPMAP_后面是配置 两张mipmap直接如何进行采样的。

这里要注意,mipmap只可以用来配置图片缩小时候的采样方式,如果你用他来配置放大时候的,会报错。

wrapping:我们的纹理坐标是在(0,0)到(1,1)之间的,只有纹理坐标在这个之间,才会被映射的到texture上。但是我们却可以把纹理坐标设置成任何一个值。当纹理坐标超出这个范围之后,会用什么来进行采样呢?我们需要自己来配置。

而且我们可以对两个方向上分别配置:用GL_TEXTURE_WRAP_S来配置横向,GL_TEXTURE_WRAP_T来配置纵向【待更正】。

他们可以取下面这些值:(默认是repeat)

配置的方法如下:

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,我们还需要配置他的背景色:

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


4.配置VAO:

这一步就是再激活一个指针,用来指向纹理坐标:

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

不过要注意一下,步长又要改变了,之前的颜色属性,和位置属性也要记得改。

Image of VBO with interleaved position, color and texture data with strides and offsets shown for configuring vertex attribute pointers.

巩固一下对理解。

5.写Shader:

我们要做2件事:1.传入纹理坐标,传给fragment shader2.传入纹理对象,应用纹理对象。

1.传入纹理坐标,并传给fragment shader(全是vertex shader的事情):

#version 330 core
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;
layout (location = 2) in vec2 inTexCoor;

out vec3 col;
out vec2 texCoor;

void main()
{
    gl_Position = inPos;
    col = inCol;
    texCoor = inTexCoor;
}

2.传入纹理对象,并应用(全是fragment shader的事情):

#version 330 core

in vec2 texCoor;
in vec3 col;

out vec4 fragCol;

uniform sampler2D texData;

void main()
{
    fragCol = texture(texData,texCoor) * vec4(col,1);
}

这里sampler2D是GLSL内置的Texture类型,如果是3D的纹理就换成sampler3D。1D的类似。直接在fragment shader中加一个uniform变量就可以把纹理读进来了,不需要在main.cpp中对他赋值(大多数显卡应该都有个默认的赋值,如果你的代码最后没有效果,那么请看下一章节,手动赋值就好)。

texture( 纹理数据,纹理坐标 )


6.绘制:

		glUseProgram(myShaderProg.ID);
		glBindTexture(GL_TEXTURE_2D, tex);//VAO不能存储texture对象,我试了
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);

要记得绑定Texture对象。


告一段落:

下面是代码:

main.cpp:

//  1.头文件:
#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include "Shader.h"

//  5.OpenGL与窗口(main之前):
void CBK_framebuffer_size(GLFWwindow* window, int w, int h)
{
	glViewport(0, 0, w, h);
}

void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

int main()
{
	//  2.在创建窗口之前……:
	glfwInit();

	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//  3.创建窗口:
	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	//  4.在OpenGL之前……:
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//  5.OpenGL与窗口:
	glViewport(0, 0, 800, 600);
	glfwSetFramebufferSizeCallback(window, CBK_framebuffer_size);

	//  OpenGL的配置:
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//设置背景色

	//  绘图需要用的数据:
	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 
	};

	GLuint indeces[] = {
		1,2,3,
		3,0,1
	};

	int imageW, imageH, imageNChs;
	unsigned char *imageData = stbi_load("..\\my_OtherFiles\\container.jpg", &imageW, &imageH, &imageNChs, 0);
	
	//  Texture 贴图
	GLuint tex;
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageW, imageH, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
	stbi_image_free(imageData);
	glGenerateMipmap(GL_TEXTURE_2D);

	//  顶点相关的配置
	GLuint VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	GLuint EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indeces), indeces, GL_STATIC_DRAW);

	GLuint VAO;
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);
	glBindVertexArray(0);

	//  ShaderProgram
	Shader myShaderProg("vtx.vs", "frag.fs");


	//  6.Render Loop:
	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		glClear(GL_COLOR_BUFFER_BIT);//绘制背景色

		//  这下面就可以写绘图的代码了
		glUseProgram(myShaderProg.ID);
		glBindTexture(GL_TEXTURE_2D, tex);//VAO不能存储texture对象,我试了
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
		//  这上面是绘图的代码

		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	//  7.程序结束之前:
	glfwTerminate();

	return 0;
}
vertex shader:

#version 330 core
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;
layout (location = 2) in vec2 inTexCoor;

out vec3 col;
out vec2 texCoor;

void main()
{
    gl_Position = vec4(inPos,1);
    col = inCol;
    texCoor = inTexCoor;
}

vertex shader:

#version 330 core
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inCol;
layout (location = 2) in vec2 inTexCoor;

out vec3 col;
out vec2 texCoor;

void main()
{
    gl_Position = vec4(inPos,1);
    col = inCol;
    texCoor = inTexCoor;
}

fragment shader:

#version 330 core

in vec2 texCoor;
in vec3 col;

out vec4 fragCol;

uniform sampler2D texData;

void main()
{
    fragCol = texture(texData,texCoor) * vec4(col,1);
}

运行结果:



猜你喜欢

转载自blog.csdn.net/yymhqe123/article/details/80514231