Dlaczego texture atlas nie może znaleźć miejsca na teksturę po zmianie wielkości bufora?

0

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ć?

0

Czyli wiesz w czym problem, ale liczysz na jakąś magię, że atlas 512x512 naglę będzie prawie 2 razy bardziej pojemny?

No jak nagle będzie 2 razy bardziej pojemny?

Metoda ResizeBuffer powiększa go 2 razy i jest używana w PackTexture na samym początku, jeśli if zostanie spełniony. Więc działa to tak, że najpierw pakuje 4 tekstury 256x256 i potem jak próbuje spakować piątą teksturę, widzi, że nie ma miejsca, powiększa bufor i próbuje pakować piątą teksturę już do większego, wystarczająco dużego bufora, ale zwraca błąd.

Powtórzę końcówkę postu:

..., ale algorytm nie wie gdzie wsadzić tą teksturę.

wsadzić w atlas, nie w bufor, tzn. czy na lewo, na prawo, na dół czy na górę. Powinien wybrać dwie wersje, albo dwa razy na prawo (bo są 2 tekstury na osi X, więc je omijamy), albo dwa razy w dół.

Wydaje mi się, że to jest problemem. Ale nie mam bladego pojęcia dlaczego.

1 użytkowników online, w tym zalogowanych: 0, gości: 1