OpenGL 中的压缩纹理批处理
Compressed texture batching in OpenGL
我正在尝试创建压缩纹理图集,但似乎无法正常工作。这是一个代码片段:
void Texture::addImageToAtlas(ImageProperties* imageProperties)
{
generateTexture(); // delete and regenerate an empty texture
bindTexture(); // bind it
atlasProperties.push_back(imageProperties);
width = height = 0;
for (int i=0; i < atlasProperties.size(); i++)
{
width += atlasProperties[i]->width;
height = atlasProperties[i]->height;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
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);
// glCompressedTexImage2D MUST be called with valid data for the 'pixels'
// parameter. Won't work if you use zero/null.
glCompressedTexImage2D(GL_TEXTURE_2D, 0,
GL_COMPRESSED_RGBA8_ETC2_EAC,
width,
height,
0,
(GLsizei)(ceilf(width/4.f) * ceilf(height/4.f) * 16.f),
atlasProperties[0]->pixels);
// Recreate the whole atlas by adding all the textures we have appended
// to our vector so far
int x, y = 0;
for (int i=0; i < atlasProperties.size(); i++)
{
glCompressedTexSubImage2D(GL_TEXTURE_2D,
0,
x,
y,
atlasProperties[i]->width,
atlasProperties[i]->height,
GL_RGBA,
(GLsizei)(ceilf(atlasProperties[i]->width/4.f) * ceilf(atlasProperties[i]->height/4.f) * 16.f),
atlasProperties[i]->pixels);
x += atlasProperties[i]->width;
}
unbindTexture(); // unbind the texture
}
我正在使用 2 个大小相同的小 KTX 纹理对此进行测试,正如您从代码中看到的那样,我正在尝试将第二个贴图附加到 x 轴上第一个贴图的旁边。
我的 KTX 解析工作正常,因为我可以渲染单个纹理,但是当我尝试批处理时(即当我使用 glCompressedTexSubImage2d 时)我在屏幕上什么也看不到。
如果我用 PNG 替换压缩纹理并将 glCompressedTexImage2d 和 glCompressedTexSubImage2d 与其非压缩版本交换,那么知道所有这些工作正常可能会很有用...
我找不到任何信息的一件事是图集中纹理的 x 和 y 位置。我该如何抵消它们?因此,如果第一个纹理的宽度为 60 像素,我是否只将第二个纹理定位在 61?
我在网上看到一些代码,人们按如下方式计算 x 和 y 位置:
x &= ~3;
y &= ~3;
这是我需要做的吗?为什么?我试过了,但是好像不行。
此外,我正在带有 Vivante GPU 的 ARM i.mx6 Quad 上尝试上面的代码,我从我在网上看到的内容怀疑 glCompressedTexSubImage2d 可能无法在这个板上工作。
谁能帮帮我?
您传递给 glCompressedTexSubImage2D()
的格式必须与用于相应 glCompressedTexImage2D()
的格式相同。来自 ES 2.0 规范:
This command does not provide for image format conversion, so an INVALID_OPERATION error results if format does not match the internal format of the texture image being modified.
因此,要匹配 glCompressedTexImage2D()
调用,glCompressedTexSubImage2D()
调用需要是:
glCompressedTexSubImage2D(GL_TEXTURE_2D,
0, x, y, atlasProperties[i]->width, atlasProperties[i]->height,
GL_COMPRESSED_RGBA8_ETC2_EAC,
(GLsizei)(ceilf(atlasProperties[i]->width/4.f) *
ceilf(atlasProperties[i]->height/4.f) * 16.f),
atlasProperties[i]->pixels);
关于大小和偏移量:
- 您确定整体尺寸的逻辑只有在所有子图像的高度都相同时才有效。或者更准确地说,由于高度设置为最后一个子图像的高度,如果没有其他高度大于最后一个。为了使其更健壮,您可能希望使用所有子图像的最大高度。
- 我很惊讶你不能将 null 作为
glCompressedTexImage2D()
的最后一个参数传递,但这似乎是真的。至少我在规范中找不到任何允许它的东西。但是既然如此,我觉得单纯的把指针传递给第一个子图的数据也不行。那将是不够的数据,并且它会读取超出内存的末端。您可能必须分配并传递足够大以覆盖整个图集纹理的 "data"。您可能可以将其设置为任何值(例如将其归零),因为您无论如何都要替换它。
- 按照我阅读 ETC2 定义(包含在 ES 3.0 规范中)的方式,纹理的 width/height 不一定必须是 4 的倍数。但是,[=13= 的位置] do必须是4的倍数,以及width/height,除非它们延伸到纹理的边缘。这意味着你必须使除最后一个子图像之外的每个子图像的宽度都是 4 的倍数。在这一点上,你还不如对所有东西都使用 4 的倍数。
基于此,我认为尺寸确定应该是这样的:
width = height = 0;
for (int i = 0; i < atlasProperties.size(); i++)
{
width += (atlasProperties[i]->width + 3) & ~3;
if (atlasProperties[i]->height > height)
{
height = atlasProperties[i]->height;
}
}
height = (height + 3) & ~3;
uint8_t* dummyData = new uint8_t[width * height];
memset(dummyData, 0, width * height);
glCompressedTexImage2D(GL_TEXTURE_2D, 0,
GL_COMPRESSED_RGBA8_ETC2_EAC,
width, height, 0,
width * height,
dummyData);
delete[] dummyData;
然后设置子图:
int xPos = 0;
for (int i = 0; i < atlasProperties.size(); i++)
{
int w = (atlasProperties[i]->width + 3) & ~3;
int h = (atlasProperties[i]->height + 3) & ~3;
glCompressedTexSubImage2D(GL_TEXTURE_2D,
0, xPos, 0, w, h,
GL_COMPRESSED_RGBA8_ETC2_EAC,
w * h,
atlasProperties[i]->pixels);
xPos += w;
}
如果你能确保原始纹理图像的大小已经是 4 的倍数,那么整个事情会变得稍微简单一些。然后你可以跳过将 sizes/positions 舍入到 4 的倍数。
毕竟,这是让你想用头撞墙的错误之一。 GL_COMPRESSED_RGBA8_ETC2_EAC
实际上不支持开发板。
我从 headers 复制了它,但它没有查询设备支持的格式。我可以使用 DXT5
格式来处理这段代码。
我正在尝试创建压缩纹理图集,但似乎无法正常工作。这是一个代码片段:
void Texture::addImageToAtlas(ImageProperties* imageProperties)
{
generateTexture(); // delete and regenerate an empty texture
bindTexture(); // bind it
atlasProperties.push_back(imageProperties);
width = height = 0;
for (int i=0; i < atlasProperties.size(); i++)
{
width += atlasProperties[i]->width;
height = atlasProperties[i]->height;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
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);
// glCompressedTexImage2D MUST be called with valid data for the 'pixels'
// parameter. Won't work if you use zero/null.
glCompressedTexImage2D(GL_TEXTURE_2D, 0,
GL_COMPRESSED_RGBA8_ETC2_EAC,
width,
height,
0,
(GLsizei)(ceilf(width/4.f) * ceilf(height/4.f) * 16.f),
atlasProperties[0]->pixels);
// Recreate the whole atlas by adding all the textures we have appended
// to our vector so far
int x, y = 0;
for (int i=0; i < atlasProperties.size(); i++)
{
glCompressedTexSubImage2D(GL_TEXTURE_2D,
0,
x,
y,
atlasProperties[i]->width,
atlasProperties[i]->height,
GL_RGBA,
(GLsizei)(ceilf(atlasProperties[i]->width/4.f) * ceilf(atlasProperties[i]->height/4.f) * 16.f),
atlasProperties[i]->pixels);
x += atlasProperties[i]->width;
}
unbindTexture(); // unbind the texture
}
我正在使用 2 个大小相同的小 KTX 纹理对此进行测试,正如您从代码中看到的那样,我正在尝试将第二个贴图附加到 x 轴上第一个贴图的旁边。
我的 KTX 解析工作正常,因为我可以渲染单个纹理,但是当我尝试批处理时(即当我使用 glCompressedTexSubImage2d 时)我在屏幕上什么也看不到。
如果我用 PNG 替换压缩纹理并将 glCompressedTexImage2d 和 glCompressedTexSubImage2d 与其非压缩版本交换,那么知道所有这些工作正常可能会很有用...
我找不到任何信息的一件事是图集中纹理的 x 和 y 位置。我该如何抵消它们?因此,如果第一个纹理的宽度为 60 像素,我是否只将第二个纹理定位在 61?
我在网上看到一些代码,人们按如下方式计算 x 和 y 位置:
x &= ~3;
y &= ~3;
这是我需要做的吗?为什么?我试过了,但是好像不行。
此外,我正在带有 Vivante GPU 的 ARM i.mx6 Quad 上尝试上面的代码,我从我在网上看到的内容怀疑 glCompressedTexSubImage2d 可能无法在这个板上工作。
谁能帮帮我?
您传递给 glCompressedTexSubImage2D()
的格式必须与用于相应 glCompressedTexImage2D()
的格式相同。来自 ES 2.0 规范:
This command does not provide for image format conversion, so an INVALID_OPERATION error results if format does not match the internal format of the texture image being modified.
因此,要匹配 glCompressedTexImage2D()
调用,glCompressedTexSubImage2D()
调用需要是:
glCompressedTexSubImage2D(GL_TEXTURE_2D,
0, x, y, atlasProperties[i]->width, atlasProperties[i]->height,
GL_COMPRESSED_RGBA8_ETC2_EAC,
(GLsizei)(ceilf(atlasProperties[i]->width/4.f) *
ceilf(atlasProperties[i]->height/4.f) * 16.f),
atlasProperties[i]->pixels);
关于大小和偏移量:
- 您确定整体尺寸的逻辑只有在所有子图像的高度都相同时才有效。或者更准确地说,由于高度设置为最后一个子图像的高度,如果没有其他高度大于最后一个。为了使其更健壮,您可能希望使用所有子图像的最大高度。
- 我很惊讶你不能将 null 作为
glCompressedTexImage2D()
的最后一个参数传递,但这似乎是真的。至少我在规范中找不到任何允许它的东西。但是既然如此,我觉得单纯的把指针传递给第一个子图的数据也不行。那将是不够的数据,并且它会读取超出内存的末端。您可能必须分配并传递足够大以覆盖整个图集纹理的 "data"。您可能可以将其设置为任何值(例如将其归零),因为您无论如何都要替换它。 - 按照我阅读 ETC2 定义(包含在 ES 3.0 规范中)的方式,纹理的 width/height 不一定必须是 4 的倍数。但是,[=13= 的位置] do必须是4的倍数,以及width/height,除非它们延伸到纹理的边缘。这意味着你必须使除最后一个子图像之外的每个子图像的宽度都是 4 的倍数。在这一点上,你还不如对所有东西都使用 4 的倍数。
基于此,我认为尺寸确定应该是这样的:
width = height = 0;
for (int i = 0; i < atlasProperties.size(); i++)
{
width += (atlasProperties[i]->width + 3) & ~3;
if (atlasProperties[i]->height > height)
{
height = atlasProperties[i]->height;
}
}
height = (height + 3) & ~3;
uint8_t* dummyData = new uint8_t[width * height];
memset(dummyData, 0, width * height);
glCompressedTexImage2D(GL_TEXTURE_2D, 0,
GL_COMPRESSED_RGBA8_ETC2_EAC,
width, height, 0,
width * height,
dummyData);
delete[] dummyData;
然后设置子图:
int xPos = 0;
for (int i = 0; i < atlasProperties.size(); i++)
{
int w = (atlasProperties[i]->width + 3) & ~3;
int h = (atlasProperties[i]->height + 3) & ~3;
glCompressedTexSubImage2D(GL_TEXTURE_2D,
0, xPos, 0, w, h,
GL_COMPRESSED_RGBA8_ETC2_EAC,
w * h,
atlasProperties[i]->pixels);
xPos += w;
}
如果你能确保原始纹理图像的大小已经是 4 的倍数,那么整个事情会变得稍微简单一些。然后你可以跳过将 sizes/positions 舍入到 4 的倍数。
毕竟,这是让你想用头撞墙的错误之一。 GL_COMPRESSED_RGBA8_ETC2_EAC
实际上不支持开发板。
我从 headers 复制了它,但它没有查询设备支持的格式。我可以使用 DXT5
格式来处理这段代码。