在 OpenGL 中将数组通过纹理的方式传入着色器读取渲染
在 OpenGL 中将数组通过纹理的方式传入着色器读取渲染
构建一组数组数据写入文件,再读出文件中的数据存放到纹理中,通过纹理传入着色器渲染,实现自定义数据在纹理保存并被正确渲染。
使用三通道的数组纹理渲染出来结果如下图所示。
代码(三通道版本)
根据功能需求写入不同格式的数据,下面展示的代码是输入色值,通过纹理采样得到纹理中保存的三通道 rgb 色值并渲染。
构造数据
自定义一组数据写入 bin 文件,选择 bin 写入和读取的数据结构更自由。数据的大小和数据类型很重要,因为这份数据要写入纹理并被纹理坐标采样,你要保证纹理坐标和数据的大小是匹配的。
int WriteDataVec3() {
// 按照分辨率写入数据,数据的大小可以自己定义
int pixNum = 800 * 600 * 3;
std::cout<< pixNum <<std::endl;
std::vector<unsigned char> buffer(pixNum, 0);
for(int i = 0; i < pixNum-3; ){
// 按照 rgb 颜色格式写入色值
buffer[i] = (i / (pixNum-3.0) * 255.0);
buffer[i+1] = (155.0);
buffer[i+2] = (i / (pixNum-3.0) * 255.0);
// std::cout<< i << ":" << buffer[i] << ":" << buffer[i+1] << ":" << buffer[i+2] <<std::endl;
i=i+3;
}
std::ofstream out(FileName, std::ios::binary);
if(!out){
std::cerr << "cannot create file" << std::endl;
return 1;
}
out.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
out.close();
return 0;
}
读取数据放入纹理
// 生成纹理
GLuint myTexture;
glGenTextures(1, &myTexture);
glBindTexture(GL_TEXTURE_2D, myTexture);
// 上传数据到纹理,使用刚刚采用的 RGB 通道
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, readData);
// 设置纹理参数,按照你数据解析的需要去设置
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, myTexture);
// 纹理连接着色器,关于纹理和着色器的关系,参考下面的理论储备
glUniform1i(glGetUniformLocation(shaderProgram, "arrayTex"), 0);
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
std::cout << "OpenGL Error: " << error << std::endl;
}
着色器中读取
着色器中读取是依据纹理坐标采样,也可以计算新的采样坐标来采样数组纹理。
#version 330 core
out vec4 FragColor;
in vec2 texCoord;
uniform sampler2D arrayTex;
void main() {
// 用纹理坐标采样,取 rgb 通道的色值
vec3 d = texture(arrayTex, texCoord).rgb;
FragColor = vec4(d, 1.0f);
}
理论储备
OpenGL texture 函数的采样原理
在 OpenGL 里面,纹理符合 OpenGL 的生成方式,以对象(OpenGL Objects)的方式管理。通过标准的对象生成和管理方法, glGenTextures
-> glBindTexture
然后把数据和参数配置给纹理对象,着色器对象并不直接同纹理对象相连接,而是通过激活纹理位置(glActiveTexture)。引用纹理图像单元的索引访问纹理数据,即我们在片段着色器里面写的 texture
函数。
完整的 C++ 和着色器代码如下👇
// 生成并绑定一个纹理
GLuint mytexture;
glGenTextures(1, &mytexture);
glBindTexture(GL_TEXTURE_2D, mytexture);
// 将数据上传到纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RED, GL_UNSIGNED_BYTE, data);
// 设置纹理参数
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_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 将纹理绑定到上下文中
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mytexture);
glUniform1i(glGetUniformLocation(shaderProgram, "mytexture"), 0);
在着色器中我们使用 sampler
和 texture
函数对纹理进行采样,一个 2D 纹理的采样代码如下所示。
out vec4 FragColor;
uniform sampler2D mytexture;
void main(){
FragColor = texture(mytexture, coord);
}
sampler2D
是着色器程序访问 OpenGL 纹理图像单元的途径,uniform
关键字代表在所有着色器中都是统一的数据。
uniform sampler2D mytexture;
一个 uniform sampler
类型代表着一个特定类型的纹理。着色器程序访问纹理的方式并不直观,是通过渲染上下文完成的映射。OpenGL 的渲染上下文中有许多可供绑定的独立位置,被称为纹理图像单元(texture image unit)。我们通过glActiveTexture
和 glBindTexture
将自己的纹理绑定到渲染上下文的纹理图像单元上。
// glActiveTexture 激活当前上下文的纹理图像单元
// 绑定的纹理图像单元为 GL_TEXTURE0
glActiveTexture(GL_TEXTURE0);
// glBindTexture 将纹理图像单元绑定到纹理类型为 GL_TEXTURE_2D 的 mytexture 纹理上
glBindTexture(GL_TEXTURE_2D, mytexture);
在着色器里面能够使用 sampler 的地方是 纹理 lookup 函数(texture lookup function),根据纹理坐标获取纹理值,返回范围在 $[0, 1]$ 的颜色分量。
vec4 texture(sampler2D sampler, vec2 coord [, float bias])
vec4 texture(samplerCube sampler, vec3 coord [, float bias])
尽管我们通常都在片段着色器中访问纹理, 但纹理的访问并不仅限在片段着色器中,非片段着色器访问纹理有特定的限制。详细内容请参考 Texture_lookup_functions
什么是纹理
从数据上来理解,纹理是一个图片(image)容器(container),图片是特定大小的一维或多维数组,存有特定的格式的像素数据。而纹理对图片的格式有自己的要求,包含三个特性:纹理类型(纹理内部图像的排列方式),纹理大小(纹理所保存的图像的大小),图片格式(定义了纹理内部的图片共同的格式)。
纹理类型(types of textures)和 GLSL 采样
纹理类型其实是我们在写 OpenGL 的时候配置的参数 GL_TEXTURE_2D
,在 LearnOpenGL 里面被称作纹理目标(texture target)。每个纹理类型有不同的性质,定义了不同的纹理大小(texture size), 以及着色器中如何处理这些纹理类型。
可支持的纹理类型包含三个维度,这三个维度对应着色器中的不同采样接口。
- GL_TEXTURE_1D: 纹理中的图片是一维的,意味着只包含宽度,没有高度和深度
- GL_TEXTURE_2D: 纹理中的图片是二维的,意味着只包含宽度和高度,没有深度
- GL_TEXTURE_3D: 纹理中的图片是三维的,意味着包含宽度高度和深度三维数据
// GL_TEXTURE_1D
vec4 texture(sampler1D, float)
ivec4 texture(isampler1D, float)
uvec4 texture(usampler1D, float)
// GL_TEXTURE_2D
vec4 texture(sampler2D, vec2)
ivec4 texture(isampler2D, vec2)
uvec4 texture(usampler2D, vec2)
// GL_TEXTURE_3D
vec4 texture(sampler3D, vec3)
ivec4 texture(isampler3D, vec3)
也就是说,一个 uniform sampler 代表一个可操作的纹理,sampler 的类型对应着一个 OpenGL 纹理类型,着色器对象并不直接同纹理对象相连接,而是引用纹理图像单元的索引访问纹理数据,即我们在片段着色器里面写的 texture
函数。
OpenGL 的纹理对象内部结构
在 OpenGL 中一个完整的纹理对象内部包含三部分,分别对应不同的纹理操作。如果纹理对象不完整,是不能够传入着色器或者对其操作的。
参考资料
以下摘自 opengl wiki
- GL_TEXTURE_1D: Images in this texture all are 1-dimensional. They have width, but no height or depth.
- GL_TEXTURE_2D: Images in this texture all are 2-dimensional. They have width and height, but no depth.
- GL_TEXTURE_3D: Images in this texture all are 3-dimensional. They have width, height, and depth.
- GL_TEXTURE_RECTANGLE: The image in this texture (only one image. No mipmapping) is 2-dimensional. Texture coordinates used for these textures are not normalized.
- GL_TEXTURE_BUFFER: The image in this texture (only one image. No mipmapping) is 1-dimensional. The storage for this data comes from a Buffer Object.
- GL_TEXTURE_CUBE_MAP: There are exactly 6 distinct sets of 2D images, each image being of the same size and must be of a square size. These images act as 6 faces of a cube.
- GL_TEXTURE_1D_ARRAY: Images in this texture all are 1-dimensional. However, it contains multiple sets of 1-dimensional images, all within one texture. The array length is part of the texture's size.
- GL_TEXTURE_2D_ARRAY: Images in this texture all are 2-dimensional. However, it contains multiple sets of 2-dimensional images, all within one texture. The array length is part of the texture's size.
- GL_TEXTURE_CUBE_MAP_ARRAY: Images in this texture are all cube maps. It contains multiple sets of cube maps, all within one texture. The array length * 6 (number of cube faces) is part of the texture size.
- GL_TEXTURE_2D_MULTISAMPLE: The image in this texture (only one image. No mipmapping) is 2-dimensional. Each pixel in these images contains multiple samples instead of just one value.
- GL_TEXTURE_2D_MULTISAMPLE_ARRAY: Combines 2D array and 2D multisample types. No mipmapping.