Search Unity

Impossible to emulate photoshop bicubic resampling?

Discussion in 'Shaders' started by chai, Dec 26, 2017.

  1. chai

    chai

    Joined:
    Jun 3, 2009
    Posts:
    84
    Is it possible to emulate bicubic filter in a custom UI shader?

    Trying to get sharp crisp sprites for a game
    Source sprites are for 1440p, and testing in 1080p build. (e.g. 75% scale)

    Compared it to photoshop pre-scaled sprites, which are loaded pixel perfect ingame.
    Point & BiLinear filters matched nicely, and also emulated bilinear shader side perfectly, which leads me to believe it's possible and is purely the algorithm.


    zoom the image above to see it clearly
     
  2. chai

    chai

    Joined:
    Jun 3, 2009
    Posts:
    84
    And here's the code (and also here https://pastebin.com/dvMGyd19)
    Obviously used point filter (nearest neighbour) for the sprite import setting, for this shader

    It is based on this https://www.codeproject.com/Articles/236394/Bi-Cubic-and-Bi-Linear-Interpolation-with-GLSL
    Which afaik is based on this https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch24.html
    The Bicubic B/C values are nicely shown here http://entropymine.com/imageworsener/bicubic/

    Code (csharp):
    1. // Bicubic filtering shader test
    2. //
    3. // * based on https://www.codeproject.com/Articles/236394/Bi-Cubic-and-Bi-Linear-Interpolation-with-GLSL
    4. // * and the interpolations from http://entropymine.com/imageworsener/bicubic/
    5.  
    6. Shader "Hidden/UiBicubicTest"
    7. {
    8.     Properties
    9.     {
    10.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    11.         _Color ("Tint", Color) = (1,1,1,1)
    12.        
    13.         _StencilComp ("Stencil Comparison", Float) = 8
    14.         _Stencil ("Stencil ID", Float) = 0
    15.         _StencilOp ("Stencil Operation", Float) = 0
    16.         _StencilWriteMask ("Stencil Write Mask", Float) = 255
    17.         _StencilReadMask ("Stencil Read Mask", Float) = 255
    18.  
    19.         _ColorMask ("Color Mask", Float) = 15
    20.  
    21.         [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    22.     }
    23.  
    24.     SubShader
    25.     {
    26.         Tags
    27.         {
    28.             "Queue"="Transparent"
    29.             "IgnoreProjector"="True"
    30.             "RenderType"="Transparent"
    31.             "PreviewType"="Plane"
    32.             "CanUseSpriteAtlas"="True"
    33.         }
    34.        
    35.         Stencil
    36.         {
    37.             Ref [_Stencil]
    38.             Comp [_StencilComp]
    39.             Pass [_StencilOp]
    40.             ReadMask [_StencilReadMask]
    41.             WriteMask [_StencilWriteMask]
    42.         }
    43.  
    44.         Cull Off
    45.         Lighting Off
    46.         ZWrite Off
    47.         ZTest [unity_GUIZTestMode]
    48.         Blend SrcAlpha OneMinusSrcAlpha
    49.         ColorMask [_ColorMask]
    50.  
    51.         Pass
    52.         {
    53.             Name "Default"
    54.         CGPROGRAM
    55.             #pragma vertex vert
    56.             #pragma fragment frag
    57.             #pragma target 3.0
    58.  
    59.             #include "UnityCG.cginc"
    60.             #include "UnityUI.cginc"
    61.  
    62.             #pragma multi_compile __ UNITY_UI_ALPHACLIP
    63.            
    64.             struct appdata_t
    65.             {
    66.                 float4 vertex   : POSITION;
    67.                 float4 color    : COLOR;
    68.                 float2 texcoord : TEXCOORD0;
    69.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    70.             };
    71.  
    72.             struct v2f
    73.             {
    74.                 float4 vertex   : SV_POSITION;
    75.                 fixed4 color    : COLOR;
    76.                 float2 texcoord  : TEXCOORD0;
    77.                 float4 worldPosition : TEXCOORD1;
    78.                 UNITY_VERTEX_OUTPUT_STEREO
    79.             };
    80.            
    81.             fixed4 _Color;
    82.             fixed4 _TextureSampleAdd;
    83.             float4 _ClipRect;
    84.  
    85.             v2f vert(appdata_t IN)
    86.             {
    87.                 v2f OUT;
    88.                 UNITY_SETUP_INSTANCE_ID(IN);
    89.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
    90.                 OUT.worldPosition = IN.vertex;
    91.                 OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
    92.  
    93.                 OUT.texcoord = IN.texcoord;
    94.                
    95.                 OUT.color = IN.color * _Color;
    96.                 return OUT;
    97.             }
    98.  
    99.             //sampler2D _MainTex;
    100.             sampler2D _MainTex;
    101.             float4 _MainTex_TexelSize;          
    102. //            uniform sampler2D _KernelBicubic;
    103.                        
    104. //            float4 texFilter (uniform sampler2D tex, float2 uvs) {
    105.              
    106. //              float4 c;
    107.              
    108. //              c =
    109.              
    110. //              return c;
    111. //            }
    112.  
    113.             float Triangular( float f )
    114.             {
    115.                 f = f / 2.0;
    116.                 if( f < 0.0 )
    117.                 {
    118.                     return ( f + 1.0 );
    119.                 }
    120.                 else
    121.                 {
    122.                     return ( 1.0 - f );
    123.                 }
    124.                 return 0.0;
    125.             }
    126.            
    127.             float BellFunc( float x )
    128.             {
    129.                 float f = ( x / 2.0 ) * 1.5; // Converting -2 to +2 to -1.5 to +1.5
    130.                 if( f > -1.5 && f < -0.5 )
    131.                 {
    132.                     return( 0.5 * pow(f + 1.5, 2.0));
    133.                 }
    134.                 else if( f > -0.5 && f < 0.5 )
    135.                 {
    136.                     return 3.0 / 4.0 - ( f * f );
    137.                 }
    138.                 else if( ( f > 0.5 && f < 1.5 ) )
    139.                 {
    140.                     return( 0.5 * pow(f - 1.5, 2.0));
    141.                 }
    142.                 return 0.0;
    143.             }
    144.            
    145.             float BSpline( float x )
    146.             {
    147.                 float f = x;
    148.                 if( f < 0.0 )
    149.                 {
    150.                     f = -f;
    151.                 }
    152.  
    153.                 if( f >= 0.0 && f <= 1.0 )
    154.                 {
    155.                     return ( 2.0 / 3.0 ) + ( 0.5 ) * ( f* f * f ) - (f*f);
    156.                 }
    157.                 else if( f > 1.0 && f <= 2.0 )
    158.                 {
    159.                     return 1.0 / 6.0 * pow( ( 2.0 - f  ), 3.0 );
    160.                 }
    161.                 return 1.0;
    162.             }  
    163.            
    164.             float Cubic ( float x, float B, float C)
    165.             {
    166.                 //const float B = 0.0;        // original bspline
    167.                 //const float C = 0.5;
    168.                 float f = x;
    169.                 if( f < 0.0 )
    170.                 {
    171.                     f = -f;
    172.                 }
    173.                 if( f < 1.0 )
    174.                 {
    175.                     return ( ( 12 - 9 * B - 6 * C ) * ( pow(f,3) ) +
    176.                         ( -18 + 12 * B + 6 * C ) * ( pow(f,2) ) +
    177.                         ( 6 - 2 * B ) ) / 6.0;
    178.                 }
    179.                 else if( f >= 1.0 && f < 2.0 )
    180.                 {
    181.                     return ( ( -B - 6 * C ) * ( pow(f,3) )
    182.                         + ( 6 * B + 30 * C ) * ( pow(f,2) ) +
    183.                         ( - ( 12 * B ) - 48 * C  ) * f +
    184.                         8 * B + 24 * C)/ 6.0;
    185.                 }
    186.                 else
    187.                 {
    188.                     return 0.0;
    189.                 }
    190.             }
    191.            
    192.             float CatMullRom( float x ) {
    193.                 const float B = 0.0;
    194.                 const float C = 0.5;
    195.                 return (Cubic (x,B,C));                      
    196.             }
    197.            
    198.             float Interpolation (float x) {
    199.                 //return Triangular(x);
    200.                 //return BellFunc(x);
    201.                 //return BSpline(x);
    202.                 //return CatMullRom(x);
    203.                
    204.                 //return (Cubic(x,1,0));        // b-spline (blurry)
    205. //                return (Cubic(x,1/3,1.3));        // mitchel
    206.                 //return (Cubic(x,0,0.5));        // CatMullRom
    207.                 //return (Cubic(x,0,1.4));        // CatMullRom
    208.                 return (Cubic(x,0,0.75));        // photoshop    (sharper, but they mix with blur)
    209.                 //return (Cubic(x,0,0.5));        // test
    210.                 //return (Cubic(x,1,0));        // gimp
    211.                 //return Cubic(x,0, (1.125-(0.5*0.75)) );        // photoshop reduce
    212.             }
    213.  
    214.             float4 BiCubic( sampler2D textureSampler, float2 TexCoord, bool blr )
    215.             {              
    216.                 float fWidth = 1/_MainTex_TexelSize.x;
    217.                 float fHeight = 1/_MainTex_TexelSize.y;
    218.                 float texelSizeX = _MainTex_TexelSize.x;
    219.                 float texelSizeY = _MainTex_TexelSize.y;
    220.                
    221.                 float4 nSum = float4( 0.0, 0.0, 0.0, 0.0 );
    222.                 float4 nDenom = float4( 0.0, 0.0, 0.0, 0.0 );
    223.                 float a = frac( TexCoord.x * fWidth ); // get the decimal part
    224.                 float b = frac( TexCoord.y * fHeight ); // get the decimal part
    225.                
    226.                 for( int m = -1; m <=2; m++ )
    227.                 {
    228.                     for( int n =-1; n<= 2; n++)
    229.                     {
    230.                         float4 vecData = tex2D(textureSampler,
    231.                                 TexCoord + float2(texelSizeX * float( m ),
    232.                                 texelSizeY * float( n )));
    233.                         float f  = Interpolation( float( m ) - a , blr);
    234.                         float4 vecCooef1 = float4( f,f,f,f );
    235.                         float f1 = Interpolation ( -( float( n ) - b ), blr );
    236.                         float4 vecCoeef2 = float4( f1, f1, f1, f1 );
    237.                         nSum = nSum + ( vecData * vecCoeef2 * vecCooef1 );
    238.                         nDenom = nDenom + (( vecCoeef2 * vecCooef1 ));
    239.                     }
    240.                 }
    241.                                                
    242.                 return nSum / nDenom;
    243.             }
    244.            
    245.             // Bilinear filtering (works on image set to nearest)
    246.             float4 tex2DBiLinear( sampler2D textureSampler_i, float2 texCoord_i )
    247.             {
    248.                 float fWidth = 1/_MainTex_TexelSize.x;
    249.                 float fHeight = 1/_MainTex_TexelSize.y;
    250.                 float texelSizeX = _MainTex_TexelSize.x;
    251.                 float texelSizeY = _MainTex_TexelSize.y;
    252.            
    253.                 float4 p0q0 = tex2D(textureSampler_i, texCoord_i);
    254.                 float4 p1q0 = tex2D(textureSampler_i, texCoord_i + float2(texelSizeX, 0));
    255.  
    256.                 float4 p0q1 = tex2D(textureSampler_i, texCoord_i + float2(0, texelSizeY));
    257.                 float4 p1q1 = tex2D(textureSampler_i, texCoord_i + float2(texelSizeX , texelSizeY));
    258.  
    259.                 float a = frac( texCoord_i.x * fWidth ); // Get Interpolation factor for X direction.
    260.                                 // Fraction near to valid data.
    261.  
    262.                 float4 pInterp_q0 = lerp( p0q0, p1q0, a ); // Interpolates top row in X direction.
    263.                 float4 pInterp_q1 = lerp( p0q1, p1q1, a ); // Interpolates bottom row in X direction.
    264.  
    265.                 float b = frac( texCoord_i.y * fHeight );// Get Interpolation factor for Y direction.
    266.                 return lerp( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction.
    267.             }
    268.  
    269.             fixed4 frag(v2f IN) : SV_Target
    270.             {
    271.                 half4 colorSrc = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;              
    272.                 half4 color = colorSrc;
    273.  
    274.                 // custom bicubic (photoshop)
    275.                 half4 color2 = (BiCubic(_MainTex, IN.texcoord, false) + _TextureSampleAdd) * IN.color;
    276.                 color = color2;
    277.                
    278.                 color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
    279.                
    280.                 #ifdef UNITY_UI_ALPHACLIP
    281.                 clip (color.a - 0.001);
    282.                 #endif
    283.  
    284.                 return color;
    285.             }
    286.         ENDCG
    287.         }
    288.     }
    289. }
    290.  
     
    JuDelCo, Quatum1000 and RomBinDaHouse like this.
  3. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Hi, It's long time ago. But did you had success finding the issue?
     
  4. chai

    chai

    Joined:
    Jun 3, 2009
    Posts:
    84
    Nah, since even AAA games have the same issue, I figured it's a waste of time to pursue it further ;)
     
  5. Quatum1000

    Quatum1000

    Joined:
    Oct 5, 2014
    Posts:
    889
    Thanks, it's corious anyway because there are only one or two examples in the net. Photoshop seems to use a slightly different algorithm. So the original gsl code seems lite worse without any doubt.

    Another great way to resize is to enlarge an image to the double size of the desired result with bilinear, then resize to desired size with neatest.
     
  6. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    524