关于纹理
通常游戏里的物体不必定都是纯色的物体,物体上面会有一些图片贴在上面,好比墙壁,箱子,地板,能够看到砖头、木板和大理石组成的图片,要把图片贴到计算机里的几何图形的话,就要把图片的颜色采样贴到几何图形上,采样是计算机常常干的工做,计算机要处理天然中的数据就须要对数据进行采样,好比说对声音采样就是采集声音的频率和频幅,分别表明声音的音色和声量,固然,采集到的是一个模拟量,然而计算机没法处理模拟量,因此须要将模拟量量化为数字量,也就是二进制数进行存储,同理,颜色也是同样,现代计算机中颜色通常以RGB的形式,分别表明Red,Green,Blud三种颜色在这个颜色中的占比,通常使用3个字节存储,也就是说颜色的数量能够达到2^24种,已经足够用了,想要把采集到的颜色贴到物体上的话,咱们须要指定顶点的纹理坐标,告诉着色器要从纹理的哪一个点开始采样, 纹理坐标的范围是0到1
项目的代码
learnOpenGL里的图解
git
纹理环绕
纹理坐标的范围是0到1,假如超出这个范围的话,在OpenGL里会有几种方式来贴图,这些方式叫作纹理环绕方式github
- 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库spa
//加载纹理 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.vs3d
#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
接下来是就是正常的测试代码了rest
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(); }
测试结果
珂朵莉好可爱~