好……曾经的我就是在这里躺尸的…………,因为这节要装个库,用来读取图片,然鹅我原来似乎没装上……图片怎么都读不进来…………。这次……试试吧……我相信我可以的……。
原教程讲了一堆东西,然后才开始安装库,我就很不高兴。所以我先安装库(为了可以自己试验,要不然,你啥都干不了,只能看着他干讲)
好吧,还是先说一下贴图(纹理,我搞不清这两个的区别,就先当一个东西吧,但是他们应该不是一个意思。英文名字是: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);
不过要注意一下,步长又要改变了,之前的颜色属性,和位置属性也要记得改。
巩固一下对理解。
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);
}
运行结果: