一、环绕方式



环绕方式

描述

GL_REPEAT

对纹理的默认行为。重复纹理图像。

GL_MIRRORED_REPEAT

和GL_REPEAT一样,但每次重复图片是镜像放置的。

GL_CLAMP_TO_EDGE

纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。

GL_CLAMP_TO_BORDER

超出的坐标为用户指定的边缘颜色。




Android Opengl OES 纹理渲染到 GL_TEXTURE_2D opengl 纹理格式_加载


对每个坐标轴进行设置:str与xyz等价

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
  • 第一个参数指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D。
  • 第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是WRAP选项,并且指定S和T轴。
  • 最后一个参数需要我们传递一个环绕方式(Wrapping),在这个例子中OpenGL会给当前激活的纹理设定纹理环绕方式为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);

二、纹理过滤

纹理放大


Android Opengl OES 纹理渲染到 GL_TEXTURE_2D opengl 纹理格式_加载_02


  • GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。这一般会造成明显的颗粒感。
  • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。

纹理缩小

当一个像素覆盖多个纹素时,也会造成不真实的硬边界。因此引入mipmap多级渐远纹理方法,以多出1/3原面积代价,得到比较好的解决效果。


过滤方式

描述

GL_NEAREST_MIPMAP_NEAREST

使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样

GL_LINEAR_MIPMAP_NEAREST

使用最邻近的多级渐远纹理级别,并使用线性插值进行采样

GL_NEAREST_MIPMAP_LINEAR

在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样

GL_LINEAR_MIPMAP_LINEAR

在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样


代码:

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

三、加载、创建与应用纹理

加载stb_image.h

stb_image.h是Sean Barrett的一个非常流行的单头文件图像加载库,它能够加载大部分流行的文件格式,并且能够很简单得整合到你的工程之中。stb_image.h可以在这里下载。下载这一个头文件,将它以stb_image.h的名字加入你的工程,并另创建一个新的C++文件,输入以下代码:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了。

加载纹理

stbi_set_flip_vertically_on_load(true);
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
  • 首先需要设置将y轴进行翻转。因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。
  • stbi_load函数首先接受一个图像文件的位置作为输入。接下来它需要三个int作为它的第二、第三和第四个参数,stb_image.h将会用图像的宽度、高度和颜色通道的个数填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。

创建纹理

unsigned int texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
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);
stbi_image_free(data);
  • glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中。
  • GL_TEXTURE0默认是激活的,这里手动激活。

GL_TEXTURE0到 GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过 GL_TEXTURE0 + 8的方式获得 GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

  • 然后将纹理绑定到相应的纹理单元。
  • 当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
  • 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
  • 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
  • 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
  • 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
  • 下个参数应该总是被设为0(历史遗留的问题)。
  • 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
  • 最后一个参数是真正的图像数据。
  • glGenerateMipmap为当前绑定的纹理自动生成所有需要的多级渐远纹理。
  • 释放图像的内存

完整代码

unsigned int texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
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);
// 对y轴进行翻转
stbi_set_flip_vertically_on_load(true);
// 加载并生成纹理
int width, height, nrChannels;
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);

纹理应用

还要通过使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面:

ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置

while(...) 
{
    [...]
}

着色器中的设置:

#version 330 core
...

uniform sampler2D texture1;

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