Czy sześcienny tensor interpolacji Lagrange'a jest taki sam jak interpolacja dwuububowa?

11

Właśnie zaimplementowałem niektóre interpolowane próbkowanie tekstur, próbkując najbliższe piksele 4x4, a następnie wykonując interpolację Lagrange'a na osi x, aby uzyskać cztery wartości do użycia interpolacji Lagrange'a na osi y.

Czy to jest to samo, co interpolacja dwuububowa, czy też jest inne? A może istnieją różne rodzaje interpolacji dwuububowych, a może to tylko jeden z nich?

Implementacja Webgl Shadertoy tutaj i odpowiedni kod GLSL (WebGL) poniżej: https://www.shadertoy.com/view/MllSzX

Dzięki!

float c_textureSize = 64.0;

float c_onePixel = 1.0 / c_textureSize;
float c_twoPixels = 2.0 / c_textureSize;

float c_x0 = -1.0;
float c_x1 =  0.0;
float c_x2 =  1.0;
float c_x3 =  2.0;

//=======================================================================================
vec3 CubicLagrange (vec3 A, vec3 B, vec3 C, vec3 D, float t)
{
    return
        A * 
        (
            (t - c_x1) / (c_x0 - c_x1) * 
            (t - c_x2) / (c_x0 - c_x2) *
            (t - c_x3) / (c_x0 - c_x3)
        ) +
        B * 
        (
            (t - c_x0) / (c_x1 - c_x0) * 
            (t - c_x2) / (c_x1 - c_x2) *
            (t - c_x3) / (c_x1 - c_x3)
        ) +
        C * 
        (
            (t - c_x0) / (c_x2 - c_x0) * 
            (t - c_x1) / (c_x2 - c_x1) *
            (t - c_x3) / (c_x2 - c_x3)
        ) +       
        D * 
        (
            (t - c_x0) / (c_x3 - c_x0) * 
            (t - c_x1) / (c_x3 - c_x1) *
            (t - c_x2) / (c_x3 - c_x2)
        );
}

//=======================================================================================
vec3 BicubicTextureSample (vec2 P)
{
    vec2 pixel = P * c_textureSize + 0.5;

    vec2 frac = fract(pixel);
    pixel = floor(pixel) / c_textureSize - vec2(c_onePixel/2.0);

    vec3 C00 = texture2D(iChannel0, pixel + vec2(-c_onePixel ,-c_onePixel)).rgb;
    vec3 C10 = texture2D(iChannel0, pixel + vec2( 0.0        ,-c_onePixel)).rgb;
    vec3 C20 = texture2D(iChannel0, pixel + vec2( c_onePixel ,-c_onePixel)).rgb;
    vec3 C30 = texture2D(iChannel0, pixel + vec2( c_twoPixels,-c_onePixel)).rgb;

    vec3 C01 = texture2D(iChannel0, pixel + vec2(-c_onePixel , 0.0)).rgb;
    vec3 C11 = texture2D(iChannel0, pixel + vec2( 0.0        , 0.0)).rgb;
    vec3 C21 = texture2D(iChannel0, pixel + vec2( c_onePixel , 0.0)).rgb;
    vec3 C31 = texture2D(iChannel0, pixel + vec2( c_twoPixels, 0.0)).rgb;    

    vec3 C02 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_onePixel)).rgb;
    vec3 C12 = texture2D(iChannel0, pixel + vec2( 0.0        , c_onePixel)).rgb;
    vec3 C22 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_onePixel)).rgb;
    vec3 C32 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_onePixel)).rgb;    

    vec3 C03 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_twoPixels)).rgb;
    vec3 C13 = texture2D(iChannel0, pixel + vec2( 0.0        , c_twoPixels)).rgb;
    vec3 C23 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_twoPixels)).rgb;
    vec3 C33 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_twoPixels)).rgb;    

    vec3 CP0X = CubicLagrange(C00, C10, C20, C30, frac.x);
    vec3 CP1X = CubicLagrange(C01, C11, C21, C31, frac.x);
    vec3 CP2X = CubicLagrange(C02, C12, C22, C32, frac.x);
    vec3 CP3X = CubicLagrange(C03, C13, C23, C33, frac.x);

    return CubicLagrange(CP0X, CP1X, CP2X, CP3X, frac.y);
}
Alan Wolfe
źródło
2
Możesz zamieścić tutaj odpowiedni kod modułu cieniującego w przypadku bitrotu, nie?
joojaa,
1
powinniśmy mieć ładniejszy kod do kodu modułu cieniującego, opublikuję na meta, jeśli ktoś mnie nie pobił!
Alan Wolfe
Czy to konkretny język modułu cieniującego nie jest dostępny na liście języków objętych naszym wyróżnianiem składni?
trichoplax,
Nie jestem pewny. To tylko GLSL (a dokładniej webgl!). Zrobiłem tylko 4 spacje przed każdą linią kodu, nie jestem pewien, czy jest lepszy sposób na oznaczenie go ...
Alan Wolfe

Odpowiedzi:

8

Okazuje się, że nie, chociaż można użyć dwuububowej interpolacji Lagrange'a do dwububowego próbkowania tekstur, nie jest to opcja najwyższej jakości i prawdopodobnie raczej nie będzie używana.

Sześcienne splajny pustelnicze są lepszym narzędziem do pracy.

Interpolacja Lagrange'a stworzy krzywą, która przechodzi przez punkty danych, zachowując w ten sposób ciągłość C0, ale splajny pustelnika zachowują pochodne na krawędziach, jednocześnie przechodząc przez punkty danych, zachowując w ten sposób ciągłość C1 i wyglądając znacznie lepiej.

To pytanie ma kilka świetnych informacji na temat splajnów sześciennych pustelników: /signals/18265/bicubic-interpolation

Oto sześcienna wersja pustelnika kodu, który opublikowałem w pytaniu:

//=======================================================================================
vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t)
{
    float t2 = t*t;
    float t3 = t*t*t;
    vec3 a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0;
    vec3 b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0;
    vec3 c = -A/2.0 + C/2.0;
    vec3 d = B;

    return a*t3 + b*t2 + c*t + d;
}

//=======================================================================================
vec3 BicubicHermiteTextureSample (vec2 P)
{
    vec2 pixel = P * c_textureSize + 0.5;

    vec2 frac = fract(pixel);
    pixel = floor(pixel) / c_textureSize - vec2(c_onePixel/2.0);

    vec3 C00 = texture2D(iChannel0, pixel + vec2(-c_onePixel ,-c_onePixel)).rgb;
    vec3 C10 = texture2D(iChannel0, pixel + vec2( 0.0        ,-c_onePixel)).rgb;
    vec3 C20 = texture2D(iChannel0, pixel + vec2( c_onePixel ,-c_onePixel)).rgb;
    vec3 C30 = texture2D(iChannel0, pixel + vec2( c_twoPixels,-c_onePixel)).rgb;

    vec3 C01 = texture2D(iChannel0, pixel + vec2(-c_onePixel , 0.0)).rgb;
    vec3 C11 = texture2D(iChannel0, pixel + vec2( 0.0        , 0.0)).rgb;
    vec3 C21 = texture2D(iChannel0, pixel + vec2( c_onePixel , 0.0)).rgb;
    vec3 C31 = texture2D(iChannel0, pixel + vec2( c_twoPixels, 0.0)).rgb;    

    vec3 C02 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_onePixel)).rgb;
    vec3 C12 = texture2D(iChannel0, pixel + vec2( 0.0        , c_onePixel)).rgb;
    vec3 C22 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_onePixel)).rgb;
    vec3 C32 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_onePixel)).rgb;    

    vec3 C03 = texture2D(iChannel0, pixel + vec2(-c_onePixel , c_twoPixels)).rgb;
    vec3 C13 = texture2D(iChannel0, pixel + vec2( 0.0        , c_twoPixels)).rgb;
    vec3 C23 = texture2D(iChannel0, pixel + vec2( c_onePixel , c_twoPixels)).rgb;
    vec3 C33 = texture2D(iChannel0, pixel + vec2( c_twoPixels, c_twoPixels)).rgb;    

    vec3 CP0X = CubicHermite(C00, C10, C20, C30, frac.x);
    vec3 CP1X = CubicHermite(C01, C11, C21, C31, frac.x);
    vec3 CP2X = CubicHermite(C02, C12, C22, C32, frac.x);
    vec3 CP3X = CubicHermite(C03, C13, C23, C33, frac.x);

    return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y);
}

Oto zdjęcie pokazujące różnicę między metodami próbkowania. Od lewej do prawej: najbliższy sąsiad, dwuliniowy, Lagrange Bicubic, Hermite Bicubic

wprowadź opis zdjęcia tutaj

Alan Wolfe
źródło
Chociaż wszystkie splajny sześcienne są w pewnym sensie równoważne, prawdopodobnie jest to koncepcyjnie łatwiejsze użycie splajnów Catmull-Rom. np. cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf
Simon F
Czy uważasz, że parametr tau pomaga lub przeszkadza w tym przypadku? Mogę się mylić, ale wydaje mi się, że catmull rom jest zbyt „zdefiniowany przez użytkownika” (i musi zostać dostrojony), podczas gdy splajn pustelniczy próbuje po prostu wykorzystać informacje z dostępnych danych. Wydaje się, że pustelnik sześcienny jest bliższy „gruntowej prawdzie”, która, jak sądzę, byłaby czymś w rodzaju idealnego filtra cynkowego. Co myślisz?
Alan Wolfe,
Nie rozumiem, w jaki sposób Catmull-Rom jest „zdefiniowany przez użytkownika”. Po utworzeniu sekwencji 4 ciągłych punktów, P [i-1], P [i], P [i + 1], P [i + 2] (4x4 w przypadku 2D) segment krzywej jest definiowany między P [i ] i P [i + 1] i jest ciągła C1 z sąsiednimi segmentami. Filtr cynkowy nadaje się do audio, ale nie do wideo. Zobacz Mitchell i Netravali: cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/... IIRC Catmull-Rom to szczególny przypadek rodziny proponowanych filtrów, ale myślę, że filtr jest krzywą aproksymacyjną, więc, w przeciwieństwie do CR, może nie przejść przez pierwotne punkty.
Simon F
Tak działa splajn pustelniczy, z tym że splajn rom catmull ma dodatkowy parametr tau (napięcie) zdefiniowany przez użytkownika. Również sinc ma zastosowanie do wideo, DSP to DSP: P
Alan Wolfe
Muszę przyznać, że nigdy wcześniej nie widziałem parametru napięcia związanego z splajnami Catmull Rom, ale tak naprawdę dowiedziałem się o nich tylko za pośrednictwem Foley & van Dam (i in.) Lub, powiedzmy, Watt & Watt, które, AFAICR, tworzą brak wzmianki o takich. Właściwie to powiedziawszy, biorąc pod uwagę, że istnieją cztery ograniczenia - tj. Krzywa musi przejść przez 2 punkty i mieć dwie zdefiniowane styczne ** w tych punktach i jest sześcienna - nie mam pewności, czy są jakieś więcej stopni swobody do obsługi parametru napięcia .... ** Chyba że masz na myśli, że styczne można skalować?
Simon F