Chciałem dowiedzieć się w jaki sposób spakować tekstury do jednej, większej. Znalazłem ten artykuł https://straypixels.net/texture-packing-for-fonts/. Mniej więcej wszystko zrozumiałem i zabrałem się do testowania. Chcę spakować 7 tekstur 256x256 do atlasu, który ma miejsce tylko na 4 tekstury, tzn. bufor atlasu ma wielkość 512x512. Pierwsze 4 tekstury pakują się bardzo dobrze, potem wyrzuca błąd.
Tworzę teksturę w taki sposób
void GenerateTexture(uchar* data, int width, int height)
{
int x, y;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
data[(y * width + x) * 4 + 0] = (x & y) & 0xFF;
data[(y * width + x) * 4 + 1] = (x | y) & 0xFF;
data[(y * width + x) * 4 + 2] = (x ^ y) & 0xFF;
data[(y * width + x) * 4 + 3] = 255;
}
}
}
lekko zmodyfikowałem oryginalny kod ze strony żeby móc spakować texture RGBA
struct TextureNode
{
TextureNode(const Vector2<uint32>& origin, const Vector2<uint32>& size)
: origin(origin), size(size)
{
}
Vector2<uint32> origin = 0; // Top left of the rectangle this node represents
Vector2<uint32> size = 0; // Size of the rectangle this node represents
bool empty = true; // true if this node is a leaf and is filled
uint32 bytesPerPixel = 4;
std::unique_ptr<TextureNode> left = nullptr; // Left (or top) subdivision
std::unique_ptr<TextureNode> right = nullptr; // Right (or bottom) subdivision
};
struct TextureAtlas
{
TextureAtlas() = default;
~TextureAtlas()
{
buffer.clear();
}
void Create(const ::Vector2<uint32>& size, uint32 bytesPerPixel)
{
textureSize = size;
this->bytesPerPixel = bytesPerPixel;
rootNode = std::make_unique<::TextureNode>(0, size);
buffer.resize(static_cast<uint>(size.w) * size.h * bytesPerPixel);
}
TextureNode* Pack(TextureNode* node, const Vector2<uint32>& size)
{
if (!node->empty) {
// The node is filled, not gonna fit anything else here
assert(!node->left && !node->right);
return nullptr;
} else if (node->left && node->right) {
// Non-leaf, try inserting to the left and then to the right
TextureNode* retval = Pack(node->left.get(), size);
if (retval != nullptr) {
return retval;
}
return Pack(node->right.get(), size);
} else {
// This is an unfilled leaf - let's see if we can fill it
Vector2<uint32> realSize = node->size;
// If we're along a boundary, calculate the actual size
if (node->origin.x + node->size.x == INT_MAX) {
realSize.x = textureSize.x - node->origin.x;
}
if (node->origin.y + node->size.y == INT_MAX) {
realSize.y = textureSize.y - node->origin.y;
}
if (node->size.x == size.x && node->size.y == size.y) {
// Perfect size - just pack into this node
node->empty = false;
return node;
} else if (realSize.x < size.x || realSize.y < size.y) {
// Not big enough
return nullptr;
} else {
// Large enough - split until we get a perfect fit
TextureNode* left = nullptr;
TextureNode* right = nullptr;
// Determine how much space we'll have left if we split each way
int remainX = realSize.x - size.x;
int remainY = realSize.y - size.y;
// Split the way that will leave the most room
bool verticalSplit = remainX < remainY;
if (remainX == 0 && remainY == 0) {
// Edge case - we are are going to hit the border of
// the texture atlas perfectly, split at the border instead
if (node->size.x > node->size.y) {
verticalSplit = false;
} else {
verticalSplit = true;
}
}
if (verticalSplit) {
// Split vertically (left is top)
left = new TextureNode(node->origin, Vector2<uint32>(node->size.x, size.y));
right = new TextureNode(Vector2<uint32>(node->origin.x, node->origin.y + size.y), Vector2<uint32>(node->size.x, node->size.y - size.y));
} else {
// Split horizontally
left = new TextureNode(node->origin, Vector2<uint32>(size.x, node->size.y));
right = new TextureNode(Vector2<uint32>(node->origin.x + size.x, node->origin.y), Vector2<uint32>(node->size.x - size.x, node->size.y));
}
node->left = std::unique_ptr<TextureNode>(left);
node->right = std::unique_ptr<TextureNode>(right);
return Pack(node->left.get(), size);
}
}
return nullptr;
}
Vector2<uint32> PackTexture(unsigned char* textureBuffer, const Vector2<uint32>& bufferSize)
{
TextureNode* node = Pack(rootNode.get(), bufferSize);
if (node == nullptr) {
ResizeBuffer(textureSize * 2U);
node = Pack(rootNode.get(), bufferSize);
// Note: this assert will be hit if we try to pack a texture larger
// than the current size of the texture
assert(node != nullptr);
}
assert(bufferSize.x == node->size.x);
assert(bufferSize.y == node->size.y);
assert(bytesPerPixel == node->bytesPerPixel);
// Copy the texture to the texture atlas' buffer
for (uint32 ly = 0; ly < bufferSize.y; ly++) {
for (uint32 lx = 0; lx < bufferSize.x; lx++) {
int y = node->origin.y + ly;
int x = node->origin.x + lx;
for (uint32 lz = 0; lz < bytesPerPixel; ++lz) {
buffer[(static_cast<uint>(y) * textureSize.x + x) * bytesPerPixel + lz] = textureBuffer[(ly * bufferSize.x + lx) * bytesPerPixel + lz];
}
}
}
return node->origin;
}
void ResizeBuffer(const Vector2<uint32>& newSize)
{
vector<uchar> newBuffer;
newBuffer.resize(static_cast<uint>(newSize.y) * newSize.x * bytesPerPixel);
for (uint32 y = 0; y < textureSize.y; y++) {
for (uint32 x = 0; x < textureSize.x; x++) {
for (uint32 z = 0; z < bytesPerPixel; ++z) {
newBuffer[(static_cast<uint>(y) * newSize.x + x) * bytesPerPixel + z] = buffer[(static_cast<uint>(y) * textureSize.x + x) * bytesPerPixel + z];
}
}
}
textureSize = newSize;
buffer = std::move(newBuffer);
}
vector<uchar> buffer;
Vector2<uint32> textureSize{ 512, 512 };
/*
1 - RED
3 - RGB
4 - RGBA
*/
uint32 bytesPerPixel = 4;
std::unique_ptr<TextureNode> rootNode = nullptr;
};
main wygląda tak
TextureAtlas atlas;
uchar* data[7];
for (int i = 0; i < 7; ++i) {
data[i] = new uchar[256 * 256 * 4];
memset(data[i], 0, 256 * 256 * 4 /* * sizeof(uchar)*/);
GenerateTexture(data[i], 256, 256);
}
atlas.Create({ 512, 512 }, 4);
for (int i = 0; i < 7; ++i) {
atlas.PackTexture(data[i], { 256, 256 });
}
próbowałem debugować kod i wyszło na to, że bufor zmienia wielkość w prawidłowy sposób, ale algorytm nie wie gdzie wsadzić tą teksturę.
Dlaczego tak się dzieje?
Jak to naprawić?