Jak interpolować dwuliniowo obraz renderowany w czasie rzeczywistym?

1

Mam na zadanie na studia napisać program, który będzie renderował obraz oraz obsługiwał przesunięcie, obrót oraz skalowanie tego obrazu w czasie rzeczywistym.

Do obliczeń matematycznych używam glm. Do ładowania obrazu stb.

Struktura TransformComponent wygląda w taki sposób:

struct TransformComponent
{
    auto get_transform() const
    {
        glm::mat4 t = glm::mat4(1.0f);

        t *= glm::translate(glm::mat4(1.0f), { translation.x, translation.y, 1.0f });
        t *= glm::mat4(glm::shearX(glm::mat3(1.0f), shear.x));
        t *= glm::mat4(glm::shearY(glm::mat3(1.0f), shear.y));
        t *= glm::rotate(glm::mat4(1.0f), rotation, {0.0f, 0.0f, 1.0f});
        t *= glm::scale(glm::mat4(1.0f), {scale.x, scale.y, 1.0f});

        return t;
    }

    glm::vec2 translation = {0.0f, 0.0f};
    glm::vec2 shear = {0.0f, 0.0f};
    glm::vec2 scale = {1.0f, 1.0f};
    float rotation = 0.0f;
};

funkcja renderująca

void draw_image_transform(const glm::ivec2& pos, const glm::ivec2& size, const glm::mat4& transform, std::shared_ptr<STBTexture> texture)
{
    if (!texture) {
        return;
    }

    static const auto convert_range = [](int old_value, int old_min, int old_max, int new_min, int new_max) {
        const int old_range = (old_max - old_min);

        if (old_range == 0) {
            return new_min;
        } else {
            const int new_range = (new_max - new_min);
            return (((old_value - old_min) * new_range) / old_range) + new_min;
        }
    };

    const auto min = pos;
    const auto max = pos + size;

    const auto half = size / 2;
    const auto finalTransform = 
        glm::translate(glm::mat4(1.0f), { half.x, half.y, 1.0f }) * 
        transform * 
        glm::translate(glm::mat4(1.0f), { -half.x, -half.y, 1.0f });

    for (int y = min.y; y < max.y; ++y) {
        if (y < 0 || y >= viewport_h) {
            return;
        }

        for (int x = min.x; x < max.x; ++x) {
            if (x < 0 || x >= viewport_w) {
                continue;
            }
            int u = convert_range(x, min.x, max.x, 0, texture->w);
            int v = convert_range(y, min.y, max.y, 0, texture->h);

            const uint32_t texture_index = v * texture->w * texture->channels + u * texture->channels;
            const uint32_t color = texture->get_pixel_u32(u, v);

            Vector2 transformed = finalTransform * glm::vec4{ x, y, 0.0f, 1.0f };

            put_pixel(pixel_buffer, transformed.x, transformed.y, color);
        }
    }
}

pixel_buffer to tablica 2d, która jest wrzucana do finalnej tekstury, która jest renderowana na ekranie. put_pixel wrzuca color do tablicy pixel_buffer

no i gdy nie ma obrotu czy powiększenia, to wszystko jest gicik, ale gdy przeskaluje np. 2 razy obraz to robią się dziury.

Żeby się tego pozbyć, moim zadaniem jest zrobienie interpolacji dwuliniowej. Nie ma to być super szybki algorytm. Wiem, że interpolacja ta działa tak, że biorę 4 sąsiadujące pixele i obliczam średnią ważoną z nich, ale nie mam pojęcia jak to zaimplementować.

Ogólnie musze przejść przez wszystkie pixele w canvasie (wiem, że to będzie wolne, ale na razie chce ogarnąć prostą implementacje) i sprawdzić kolor pixela i potem wziąć sąsiadów i obliczyć średnią i ustawić ten kolor dla pixela.

for(uint32_t y = 0; y < viewport_h; ++y) {
  for(uint32_t x = 0; x < viewport_w; ++x) {
    //i co teraz???
  }
}

ale jakby nie wiem co zrobić w tej pętli.

W jaki sposób ogarnąć tą interpolacje? Przeszukałem cały internet i wiem na czym polega ta interpolacja ale nie wiem jak ją zaimplementować.

0
edziukowal napisał(a):

no i gdy nie ma obrotu czy powiększenia, to wszystko jest gicik, ale gdy przeskaluje np. 2 razy obraz to robią się dziury.

Objaw jakbys na podstawie wejscia liczyl wyjscie. Powinno byc na odwrot.

Żeby się tego pozbyć, moim zadaniem jest zrobienie interpolacji dwuliniowej. Nie ma to być super szybki algorytm. Wiem, że interpolacja ta działa tak, że biorę 4 sąsiadujące pixele i obliczam średnią ważoną z nich, ale nie mam pojęcia jak to zaimplementować.

Po transformacji wyjscia na wejscie wyjdzie ze wejscie nie jest calkowite (no w szegolnych przypadkach moze byc). Czyli mozna wziac 4 wspolrzedne calkowite w okolicach tego punktu. To mozna zobrazowac jako kwadrat (w rogach beda wartosci pikseli z wejscia) z punktem w srodku. Odleglosc tego punktu od rogow wyznacza wagi. Jak znalezc srednia wazona?
Jesli za trudne to zacznij od sredniej wazonej na odcinku. Pozniej wystarczy polaczyc wazenie poziomu i pionu.
To w najprostszej wersji, bo nie zawsze branie 4 pikseli da przyzwoite rezultaty. Np. zmniejszajac obrazek 10 razy mnostwo pikseli wejsciowych bys pominal i bedzie to wygladac slabo.

0

Podłub w glm dokładniej. Jeśli sam będziesz iterował pixel po pixelu to szybkość kodu nie będzie zadowalająca.

2
edziukowal napisał(a):

no i gdy nie ma obrotu czy powiększenia, to wszystko jest gicik, ale gdy przeskaluje np. 2 razy obraz to robią się dziury.

Prawdopodobnie (nie czytałem kodu) iterujesz po pikselach tekstury i wyliczasz im pozycję na ekranie. Trzeba robić odwrotnie - iterować po pikselach ekranu, wyliczyć współrzędne w teksturze i interpolować między sąsiednimi tekselami.

eleventeen napisał(a):

Żeby się tego pozbyć, moim zadaniem jest zrobienie interpolacji dwuliniowej. Nie ma to być super szybki algorytm. Wiem, że interpolacja ta działa tak, że biorę 4 sąsiadujące pixele i obliczam średnią ważoną z nich, ale nie mam pojęcia jak to zaimplementować.

std::lerp osobno dla pikseli po X i osobno po Y (wszystko jedno czy najpierw po X czy po Y) i masz interpolację biliniową.

0
Azarien napisał(a):
edziukowal napisał(a):

no i gdy nie ma obrotu czy powiększenia, to wszystko jest gicik, ale gdy przeskaluje np. 2 razy obraz to robią się dziury.

Prawdopodobnie (nie czytałem kodu) iterujesz po pikselach tekstury i wyliczasz im pozycję na ekranie. Trzeba robić odwrotnie - iterować po pikselach ekranu, wyliczyć współrzędne w teksturze i interpolować między sąsiednimi tekselami.

eleventeen napisał(a):

Żeby się tego pozbyć, moim zadaniem jest zrobienie interpolacji dwuliniowej. Nie ma to być super szybki algorytm. Wiem, że interpolacja ta działa tak, że biorę 4 sąsiadujące pixele i obliczam średnią ważoną z nich, ale nie mam pojęcia jak to zaimplementować.

std::lerp osobno dla pikseli po X i osobno po Y (wszystko jedno czy najpierw po X czy po Y) i masz interpolację biliniową.

a jak obliczyć współrzędne w teksturze jeśli np. ekran jest 500x500, a tekstura tylko 50x50? Po prostu zrobić ifa i jeśli x i y nie mieści się w teksturze to continue?
W sensie żeby ta tekstura nie była renderwowana na całym ekranie tylko na 50x50 pixelach.

2
edziukowal napisał(a):

a jak obliczyć współrzędne w teksturze jeśli np. ekran jest 500x500, a tekstura tylko 50x50? Po prostu zrobić ifa i jeśli x i y nie mieści się w teksturze to continue?
W sensie żeby ta tekstura nie była renderwowana na całym ekranie tylko na 50x50 pixelach.

No tak, jeśli wyjeżdżasz poza teksturę to nie rysować. Tylko problemem będzie marnowanie czasu na cały ekran kiedy rysowany prostokąt jest mały. Jeśli wykluczyć obroty, wystarczy wyznaczyć 4 ekranowe wierzchołki prostokąta i tylko w tym obszarze rysować. Ewentualny obrót trochę komplikuje, ale wystarczyłoby wyznaczyć prostokąt opisujący, czyli z tych 4 wierzchołków wziąć maxx, maxy, minx, miny i z tego zbudować prostokąt w którym podejmujesz się renderingu.

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