Что это за техника, если не билинейная фильтрация?

Я пытаюсь воспроизвести алгоритм автоматической билинейной фильтрации Unity3D, используя следующий код:

fixed4 GetBilinearFilteredColor(float2 texcoord)
{
    fixed4 s1 = SampleSpriteTexture(texcoord + float2(0.0, _MainTex_TexelSize.y));
    fixed4 s2 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, 0.0));
    fixed4 s3 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y));
    fixed4 s4 = SampleSpriteTexture(texcoord);

    float2 TexturePosition = float2(texcoord)* _MainTex_TexelSize.z;

    float fu = frac(TexturePosition.x);
    float fv = frac(TexturePosition.y);

    float4 tmp1 = lerp(s4, s2, fu);
    float4 tmp2 = lerp(s1, s3, fu);

    return lerp(tmp1, tmp2, fv);
}

fixed4 frag(v2f IN) : SV_Target
{
    fixed4 c = GetBilinearFilteredColor(IN.texcoord) * IN.color;
    c.rgb *= c.a;
    return c;
}

Я думал, что использую правильный алгоритм, потому что это единственный, который я видел там для билинейного. Но я попробовал использовать unity с той же текстурой, дублированной:

  • 1º текстура: находится в точечной фильтрации и использует пользовательский билинейный шейдер (сделанный из шейдера спрайта по умолчанию).
  • текстура 2º: в Билинейном фильтре с шейдер спрайта по умолчанию

и вот результат:

enter image description here

вы можете видеть, что они разные, а также есть некоторое смещение в моем пользовательском шейдере, которое делает спрайт вне центра при вращении по оси Z.

есть идеи о том, что я делаю неправильно? Любая идея о том, что делает Unity3D отличается? Есть ли другой алгоритм, который подходит для Unity3D по умолчанию фильтрация?

решение

обновлено с полным решением кода с кодом Нико для других людей, которые ищут его здесь:

fixed4 GetBilinearFilteredColor(float2 texcoord)
{
    fixed4 s1 = SampleSpriteTexture(texcoord + float2(0.0, _MainTex_TexelSize.y));
    fixed4 s2 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, 0.0));
    fixed4 s3 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y));
    fixed4 s4 = SampleSpriteTexture(texcoord);

    float2 TexturePosition = float2(texcoord)* _MainTex_TexelSize.z;

    float fu = frac(TexturePosition.x);
    float fv = frac(TexturePosition.y);

    float4 tmp1 = lerp(s4, s2, fu);
    float4 tmp2 = lerp(s1, s3, fu);

    return lerp(tmp1, tmp2, fv);
}

fixed4 frag(v2f IN) : SV_Target
{
    fixed4 c = GetBilinearFilteredColor(IN.texcoord - 0.498 * _MainTex_TexelSize.xy) * IN.color;
    c.rgb *= c.a;
    return c;
}

и тест изображения с результатом:

enter image description here

почему не вычесть 0.5 точно?

Если вы протестируете его, вы увидите некоторые крайние случаи, когда он переходит к (пиксель-1).

1 ответов


давайте более подробно рассмотрим, что вы на самом деле делаете. Я буду придерживаться случая 1D, потому что его легче визуализировать.

у вас есть массив точек и положение текстуры. Я полагаю,_MainTex_TexelSize.z устанавливается таким образом, что он дает пиксельные координаты. Это то, что вы получаете (поля представляют пиксели, числа в полях число пикселей и числа ниже координат пиксельного пространства):

Pixels

С вашей выборкой (предполагая ближайшая точка выборки), вы получите пиксели 2 и 3. Однако вы видите, что координата интерполяции для lerp на самом деле неправильно. Вы передадите дробную часть позиции текстуры (т. е. 0.8), но он должен быть!--4--> (= 0.8 - 0.5). Причина этого довольно проста: если вы приземляетесь в центре пикселя, вы хотите использовать значение пикселя. Если вы приземляетесь прямо посередине между двумя пикселями, вы хотите использовать среднее значение обоих пикселов (т. е. значение интерполяции 0.5). Прямо сейчас, у вас есть в основном смещение на половину пикселя влево.

когда вы решаете первую проблему, есть вторая:

Pixels

в этом случае вы действительно хотите смешаться между пикселями 1 и 2. Но поскольку вы всегда идете вправо в своей выборке, вы будете смешиваться между 2 и 3. Опять же, с неправильным значением интерполяции.

решение должно быть довольно простым: вычесть половину ширины пикселя из координаты текстуры, прежде чем делать что-либо с ней, что, вероятно, просто следующее (предполагая, что ваши переменные содержат вещи, которые я думаю):

fixed4 c = GetBilinearFilteredColor(IN.texcoord - 0.5 * _MainTex_TexelSize.xy) * IN.color;

еще одна причина, почему результаты разные, может быть, что Unity фактически использует другой фильтр, например бикубический (но я не знаю). Кроме того, на результат может повлиять использование mipmaps.