0

我只是在学习如何在 OpenGL 中进行纹理处理,并且对我得到的一些结果感到有些困惑。

我正在使用 stb_image 加载以下棋盘格 png 图像:

黑白棋盘图像

当我保存 png 图像时,我明确选择将其保存为 32 位。这会让我相信每个组件 (RGBA) 将存储为 8 位,总共 32 位 - 无符号整数的大小。但是,使用以下代码:

unsigned char * texture_data = 
  stbi_load("resources/graphics-scene/tut/textures/checker.png", &w, &h, nullptr, 4);

// ...

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0,
  GL_RGBA, GL_UNSIGNED_INT_8_8_8_8,  texture_data);

产量:

引擎中的黑白棋盘

如果我改为使用 GL_UNSIGNED_BYTE 作为类型参数,我会得到正确的结果。

另外,为了有帮助,我还尝试了以下图片:

彩色棋盘图像

产生

引擎中的彩色棋盘

GL_UNSIGNED_BYTE 在这种情况下也给出了正确的结果。

我不确定这是否是我误解 glTexImage2D 或 stb_image 的情况(它是否将加载的数据转换为 8 位?这对我来说似乎不太可能)。

编辑:我终于找到了一个相关的帖子(已经搜索了一些但没有运气)。但是答案(https://stackoverflow.com/a/4191875/2507444)让我感到困惑。如果是这种情况 - 类型参数指定每个组件有多少字节- 那么 GL_UNSIGNED_BYTE_3_3_2 和 GL_UNSIGNED_INT_8_8_8_8 到底是什么意思?

4

1 回答 1

3

如果是这种情况 - 类型参数指定每个组件有多少字节 - 那么 GL_UNSIGNED_BYTE_3_3_2 和 GL_UNSIGNED_INT_8_8_8_8 到底是什么意思?

它两者都做,取决于实际类型是什么。

如果像素传输类型只是一种数据类型,则它指定每个组件的数据大小。如果其中有数字,则类型指定每像素数据的大小;这些数字指定了该数据类型中各个组件大小。

GL_UNSIGNED_INT_8_8_8_8意味着 OpenGL 会将每个像素解释为无符号整数。第一个组件将是高 8 位,下一个将是接下来的 8 位,依此类推。

但是,您的问题来自于 STB-image不适用于无符号整数这一事实。每个像素以 RGBA 顺序写入 4 个单独的字节。基本上,它这样做:

GLubyte arr[4] = {red, green, blue, alpha};

现在,这听起来可能是一回事。但事实并非如此。原因与字节序问题有关

当您在 C/C++ 中执行此操作时:

GLuint foo = 0;
foo |= (red << 24);
foo |= (green << 16);
foo |= (blue << 8);
foo |= (alpha << 0);

OpenGL 的数据类型要求GLuint是大小正好为 32 位的无符号整数。假设 、redgreenblue都是alphas GLubyte(8 位无符号整数),C/C++ 表示这会将这些red位打包到高 8 位字节中,然后打包green到下一个字节中,依此类推。C 和 C++ 标准要求这样做。

但是,C 和 C++ 标准要求这样做:

GLubyte *ptr = (GLubyte*)&foo;
ptr[0] == ((foo >> 24) & 0xFF);

也就是说, 指向的内存的第一个字节不必foo组件red

在 little endian 字节排序中,32 位整数的低字节首先存储,而不是最后存储。

当 OpenGL 看到GL_UNSIGNED_INT时,这意味着它将完全按照您的 CPU 的方式解释这四个字节。所以GL_UNSIGNED_INT_8_8_8_8会做foo上面的等价物。它看到的第一个内存字节将在小端机器上被解释为低字节,而不是高字节。

STB 图像不输出GL_UNSIGNED_INT_8_8_8_8arr就像上面一样,它将每个像素视为一个 4 字节数组。因此,您必须告诉 OpenGL 这就是您的数据的存储方式。所以你说每个组件都是一个字节。这是做什么GL_UNSIGNED_BYTE的。

于 2013-08-05T20:15:45.147 回答