2013-01-16 16 views
9

Używam WebGL, aby bardzo szybko zmienić rozmiar obrazu strony klienta w aplikacji, nad którą pracuję. Napisałem moduł cieniujący GLSL, który wykonuje proste dwustanowe filtrowanie na obrazach, które zmniejszam.Jak mogę poprawić ten shader próbkowania obrazu WebGL/GLSL?

Działa to w większości przypadków, ale jest wiele okazji, w których rozmiar jest ogromny, np. od obrazu 2048x2048 do 110x110 w celu wygenerowania miniatury. W takich przypadkach jakość jest słaba i zbyt nieostra.

Mój obecny GLSL shader jest następujący:

uniform float textureSizeWidth;\ 
uniform float textureSizeHeight;\ 
uniform float texelSizeX;\ 
uniform float texelSizeY;\ 
varying mediump vec2 texCoord;\ 
uniform sampler2D texture;\ 
\ 
vec4 tex2DBiLinear(sampler2D textureSampler_i, vec2 texCoord_i)\ 
{\ 
    vec4 p0q0 = texture2D(textureSampler_i, texCoord_i);\ 
    vec4 p1q0 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX, 0));\ 
\ 
    vec4 p0q1 = texture2D(textureSampler_i, texCoord_i + vec2(0, texelSizeY));\ 
    vec4 p1q1 = texture2D(textureSampler_i, texCoord_i + vec2(texelSizeX , texelSizeY));\ 
\ 
    float a = fract(texCoord_i.x * textureSizeWidth);\ 
\ 
    vec4 pInterp_q0 = mix(p0q0, p1q0, a);\ 
    vec4 pInterp_q1 = mix(p0q1, p1q1, a);\ 
\ 
    float b = fract(texCoord_i.y * textureSizeHeight);\ 
    return mix(pInterp_q0, pInterp_q1, b);\ 
}\ 
void main() { \ 
\ 
    gl_FragColor = tex2DBiLinear(texture,texCoord);\ 
}'); 

TexelsizeX i TexelsizeY są po prostu (1,0/tekstury szerokość) wysokość i odpowiednio ...

chciałbym wdrożyć technikę wyższej jakości filtrowania , najlepiej filtr [Lancosz] [1], który powinien dawać znacznie lepsze wyniki, ale nie mogę się zorientować, jak zaimplementować algorytm za pomocą GLSL, ponieważ jestem bardzo nowym użytkownikiem WebGL i GLSL w ogóle.

Czy ktoś może wskazać mi właściwy kierunek?

Z góry dziękuję.

Odpowiedz

15

Jeśli szukasz Lanczos resampling, po to program shader używać w otwartej bibliotece źródło GPUImage:

Vertex shader:

attribute vec4 position; 
attribute vec2 inputTextureCoordinate; 

uniform float texelWidthOffset; 
uniform float texelHeightOffset; 

varying vec2 centerTextureCoordinate; 
varying vec2 oneStepLeftTextureCoordinate; 
varying vec2 twoStepsLeftTextureCoordinate; 
varying vec2 threeStepsLeftTextureCoordinate; 
varying vec2 fourStepsLeftTextureCoordinate; 
varying vec2 oneStepRightTextureCoordinate; 
varying vec2 twoStepsRightTextureCoordinate; 
varying vec2 threeStepsRightTextureCoordinate; 
varying vec2 fourStepsRightTextureCoordinate; 

void main() 
{ 
    gl_Position = position; 

    vec2 firstOffset = vec2(texelWidthOffset, texelHeightOffset); 
    vec2 secondOffset = vec2(2.0 * texelWidthOffset, 2.0 * texelHeightOffset); 
    vec2 thirdOffset = vec2(3.0 * texelWidthOffset, 3.0 * texelHeightOffset); 
    vec2 fourthOffset = vec2(4.0 * texelWidthOffset, 4.0 * texelHeightOffset); 

    centerTextureCoordinate = inputTextureCoordinate; 
    oneStepLeftTextureCoordinate = inputTextureCoordinate - firstOffset; 
    twoStepsLeftTextureCoordinate = inputTextureCoordinate - secondOffset; 
    threeStepsLeftTextureCoordinate = inputTextureCoordinate - thirdOffset; 
    fourStepsLeftTextureCoordinate = inputTextureCoordinate - fourthOffset; 
    oneStepRightTextureCoordinate = inputTextureCoordinate + firstOffset; 
    twoStepsRightTextureCoordinate = inputTextureCoordinate + secondOffset; 
    threeStepsRightTextureCoordinate = inputTextureCoordinate + thirdOffset; 
    fourStepsRightTextureCoordinate = inputTextureCoordinate + fourthOffset; 
} 

Fragment shader:

precision highp float; 

uniform sampler2D inputImageTexture; 

varying vec2 centerTextureCoordinate; 
varying vec2 oneStepLeftTextureCoordinate; 
varying vec2 twoStepsLeftTextureCoordinate; 
varying vec2 threeStepsLeftTextureCoordinate; 
varying vec2 fourStepsLeftTextureCoordinate; 
varying vec2 oneStepRightTextureCoordinate; 
varying vec2 twoStepsRightTextureCoordinate; 
varying vec2 threeStepsRightTextureCoordinate; 
varying vec2 fourStepsRightTextureCoordinate; 

// sinc(x) * sinc(x/a) = (a * sin(pi * x) * sin(pi * x/a))/(pi^2 * x^2) 
// Assuming a Lanczos constant of 2.0, and scaling values to max out at x = +/- 1.5 

void main() 
{ 
    lowp vec4 fragmentColor = texture2D(inputImageTexture, centerTextureCoordinate) * 0.38026; 

    fragmentColor += texture2D(inputImageTexture, oneStepLeftTextureCoordinate) * 0.27667; 
    fragmentColor += texture2D(inputImageTexture, oneStepRightTextureCoordinate) * 0.27667; 

    fragmentColor += texture2D(inputImageTexture, twoStepsLeftTextureCoordinate) * 0.08074; 
    fragmentColor += texture2D(inputImageTexture, twoStepsRightTextureCoordinate) * 0.08074; 

    fragmentColor += texture2D(inputImageTexture, threeStepsLeftTextureCoordinate) * -0.02612; 
    fragmentColor += texture2D(inputImageTexture, threeStepsRightTextureCoordinate) * -0.02612; 

    fragmentColor += texture2D(inputImageTexture, fourStepsLeftTextureCoordinate) * -0.02143; 
    fragmentColor += texture2D(inputImageTexture, fourStepsRightTextureCoordinate) * -0.02143; 

    gl_FragColor = fragmentColor; 
} 

Stosuje się to w dwóch przejściach, z pierwszym wykonaniem poziomego próbkowania w dół, a drugiego z pionowym próbkowaniem w dół. Stroje texelWidthOffset i texelHeightOffset są na przemian ustawione na 0,0, a frakcja szerokości lub wysokość pojedynczego piksela na obrazie.

Ciężko obliczam przesunięcia teksel w module cieniującym wierzchołków, ponieważ pozwala to uniknąć odczytywania zależnych tekstur na urządzeniach mobilnych, na które celuję, co prowadzi do znacznie lepszej wydajności. Jest to jednak trochę gadatliwe.

Wyniki tego Lanczos resampling:

Lanczos

Normal bilinear downsampling:

Bilinear

najbliższego sąsiedztwa próbkowania:

Nearest-neighbor

+0

A pięknie skonstruowana odpowiedź. Dziękuję Ci. Powinienem móc się tam dostać z opublikowanego przez ciebie kodu. Twoje zdrowie! – gordyr

+0

Tylko po to, aby Cię powiadomić Wiem, że działa to doskonale, a wyniki są piękne. Co dziwne, musiałem ustawić Texeloffsets na (1,0/(docelowa szerokość * 3)) i (1,0/(docelowa wysokość * 3)), aby uzyskać najlepsze wyniki. Nie jestem pewien, czy rozumiem dlaczego, ale użycie standardowej szerokości/wysokości spowodowało bardzo zamazany obraz. Niezależnie od tego jest teraz fantastyczny. Ogromne dzięki! – gordyr

+0

@gordyr - Dobrze słyszeć. Masz na myśli, że musisz użyć texelWidthOffset = 3.0/(szerokość obrazu w pikselach) lub texelWidthOffset = 1.0/(3.0 * (szerokość obrazu w pikselach))?Wygenerowałem powyższe obrazy za pomocą texelWidthOffset = 1.0/(szerokość obrazu w pikselach) i texelHeightOffset = 1.0/(wysokość obrazu w pikselach), ale jeśli współczynnik 3 działa dla ciebie, idź z nim. –

Powiązane problemy