Search Unity

Moire effect in LED panel shader

Discussion in 'Shaders' started by AnthonyAckerman, Jul 31, 2015.

  1. AnthonyAckerman

    AnthonyAckerman

    Joined:
    Feb 20, 2015
    Posts:
    16
    Hi everyone,

    I translated a GLSL shader if found on the web in CG. It's a shader that converts a texture/movie into a LED panel. See the image "LED close"
    The problem is that if I look at the panel from a distance, the little dots from the LED panel become so small that Unity apparantly doesn't know how to display it correctly. It starts with a moire pattern and if you go even further behind it becomes a total mess. See image "LED far"

    The problem is a lot less if I use an image as the opacity mask for the dots, but I need to adjust the size of the dots on the fly, so an image is not enough for me. I think the textured version is mipmapped and therefore has less of the moire effect. The shader version, which I need, is not mipmapped. If anyone can give some advice how to fix this, I would greatly appreciate.
     

    Attached Files:

  2. mholub

    mholub

    Joined:
    Oct 3, 2012
    Posts:
    123
  3. AnthonyAckerman

    AnthonyAckerman

    Joined:
    Feb 20, 2015
    Posts:
    16
    Hi again,

    After some desperate attempts to do some derivative supersampling, I'm turning to the community again and hope someone can help me.
    The LED shader I use is the following:
    Code (CSharp):
    1. Shader "Custom/pixelate" {
    2. Properties{
    3. _ResolutionX ("ResolutionX", int) = 100
    4. _ResolutionY ("ResolutionY", int) = 100
    5. _Samples ("Samples", int) = 5
    6. _Radius ("LED Radius", float) = 1.0
    7. _MainTex ("Main Texture", 2D) = "white" {}
    8. _ChromaColor ("Chroma color", color) = (1.0, 1.0, 1.0)
    9. _Feather ("Feather", Range(0, 1)) = 0.0
    10. _Size ("Size", float) = 1.0
    11. }
    12.     SubShader {
    13.         Pass {
    14.         Tags { "RenderType"="Transparent" }
    15.         Blend SrcAlpha OneMinusSrcAlpha
    16.             CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag
    19.             #include "UnityCG.cginc"
    20.             #pragma target 3.0
    21.            
    22.             float _ResolutionX;
    23.             float _ResolutionY;
    24.             float _Samples;
    25.             sampler2D _MainTex;
    26.             float _Radius;
    27.             fixed4 _ChromaColor;
    28.             float _Feather;
    29.             float _Size;
    30.             float2 texCoords[9];
    31.            
    32.             struct appdata {
    33.                 float4 vertex : POSITION;
    34.                 float2 texcoord0 : TEXCOORD;
    35.             };
    36.            
    37.             struct v2f
    38.             {
    39.                 float4 pos : SV_POSITION;
    40.                 float2 uv0 : TEXCOORD;
    41.             };
    42.  
    43.             v2f vert(appdata IN) {
    44.                 v2f OUT;
    45.                 OUT.pos = mul (UNITY_MATRIX_MVP, IN.vertex);
    46.                 OUT.uv0 = IN.texcoord0;
    47.                 return OUT;
    48.             }
    49.  
    50.      fixed4 frag(v2f IN) : COLOR {
    51.            
    52.         float4 avgColor; //will hold our averaged color from our sample points
    53.         float2 texCoordsStep = 1.0/(float2(float(_ResolutionX),float(_ResolutionX))/float(_Size)); //width of "pixel region" in texture coords
    54.         float2 pixelRegionCoords = frac(IN.uv0.xy/texCoordsStep); //x and y coordinates within "pixel region"
    55.         float2 pixelBin = floor(IN.uv0.xy/texCoordsStep); //"pixel region" number counting away from base case
    56.         float2 inPixelStep = texCoordsStep/3.0; //width of "pixel region" divided by 3 (for KERNEL_SIZE = 9, 3x3 square)
    57.         float2 inPixelHalfStep = inPixelStep/2.0;
    58.        
    59.         //use offset (pixelBin * texCoordsStep) from base case (the lower left corner of billboard) to compute texCoords
    60.      texCoords[0] = float2(inPixelHalfStep.x, inPixelStep.y*2.0 + inPixelHalfStep.y) + pixelBin * texCoordsStep;
    61.      texCoords[1] = float2(inPixelStep.x + inPixelHalfStep.x, inPixelStep.y*2.0 + inPixelHalfStep.y) + pixelBin * texCoordsStep;
    62.      texCoords[2] = float2(inPixelStep.x*2.0 + inPixelHalfStep.x, inPixelStep.y*2.0 + inPixelHalfStep.y) + pixelBin * texCoordsStep;
    63.      texCoords[3] = float2(inPixelHalfStep.x, inPixelStep.y + inPixelHalfStep.y) + pixelBin * texCoordsStep;
    64.      texCoords[4] = float2(inPixelStep.x + inPixelHalfStep.x, inPixelStep.y + inPixelHalfStep.y) + pixelBin * texCoordsStep;
    65.      texCoords[5] = float2(inPixelStep.x*2.0 + inPixelHalfStep.x, inPixelStep.y + inPixelHalfStep.y) + pixelBin * texCoordsStep;
    66.      texCoords[6] = float2(inPixelHalfStep.x, inPixelHalfStep.y) + pixelBin * texCoordsStep;
    67.      texCoords[7] = float2(inPixelStep.x + inPixelHalfStep.x, inPixelHalfStep.y) + pixelBin * texCoordsStep;
    68.      texCoords[8] = float2(inPixelStep.x*2.0 + inPixelHalfStep.x, inPixelHalfStep.y) + pixelBin * texCoordsStep;
    69.    
    70.      //take average of 9 pixel samples
    71.      avgColor = tex2D(_MainTex, texCoords[0]) +
    72.                          tex2D(_MainTex, texCoords[1]) +
    73.                          tex2D(_MainTex, texCoords[2]) +
    74.                          tex2D(_MainTex, texCoords[3]) +
    75.                          tex2D(_MainTex, texCoords[4]) +
    76.                          tex2D(_MainTex, texCoords[5]) +
    77.                          tex2D(_MainTex, texCoords[6]) +
    78.                          tex2D(_MainTex, texCoords[7]) +
    79.                          tex2D(_MainTex, texCoords[8]);
    80.                        
    81.       avgColor /= float(9.0);
    82.      
    83.       //blend between fragments in the circle and out of the circle defining our "pixel region"
    84.      //Equation of a circle: (x - h)^2 + (y - k)^2 = r^2
    85.      float2 powers = pow(abs(pixelRegionCoords - 0.5),float2(2.0, 2.0));
    86.      float radiusSqrd = pow(_Radius,2.0);
    87.      float gradient = smoothstep(radiusSqrd-_Feather, radiusSqrd+_Feather, powers.x+powers.y);
    88.      
    89.       fixed4 fragcolor = lerp(avgColor, float4(0.1,0.1,0.1,0.0), gradient);
    90.       return fragcolor;
    91.             }
    92.  
    93.             ENDCG
    94.         }
    95.     }
    96. }
    97.  
    If you try it you'll see that you can very easily create an LED display. That is until you watch it from a distance. See the first post to see what I mean.
    Now I tried to apply supersampling and shader antialising. I even tried the techniques from this GLSL shader.
    https://www.shadertoy.com/view/lsjSWW
    I just can't seem to make this work in the LED shader. Can someone change the code to implement some supersampling technique to make sure the LED display still looks OK from a distance?
    Any help would be greatly appreciated.
    Thanks you.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    You could go back to using a texture, but have the texture be a circular gradient that you repeat per pixel and use for the LED radius calculation. You'll get some of the beneficial aspects of mip mapping, but it might not look right. Use an A8 texture and make sure it's authored in linear color space and not in gamma space.

    Another way to handle this is analytically. Figure out how far away the pixel on screen is and compare that with the intended size of an LED. If the LED radius is smaller than a pixel make the radius larger and dim it.
     
  5. AnthonyAckerman

    AnthonyAckerman

    Joined:
    Feb 20, 2015
    Posts:
    16
    Thank you for the tip. I can not use a texture because my client needs to change the radius of each LED light.
    I create the circle procedurally and feather the edges based on the fwidth value of each UV region. Each LED light has it's own UV region.
    The moiré is less pronounced now, but still present. Moiré makes my LED display worthless. I'm sure a little bit of supersampling would help, but I have no idea how to implement it into the shader. I don't know how to implement supersampling. If anyone has a golden tip for me, I would greatly appreciate it because my client is getting impatient.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    I don't think you read my post fully.

    Use a circular gardient texture instead of calculating the circle from the UVs, basically:
    float2 powers = pow(abs(pixelRegionCoords-0.5),float2(2.0, 2.0));
    can be replaced with:
    float2 powers = pow(tex2D(_CircleGradient, IN.uv0.xy/texCoordsStep), 2.0); // You don't want the frac UV here
    Then you get the benefit of texture filtering and can still do dynamic LED sizes.

    The analytical approach is a bit more involved, but you would use something like:
    float coverage = _Radius / max(length(ddx(IN.uv0.xy/texCoordsStep)), length(ddy(IN.uv0.xy/texCoordsStep)); // probably wrong
    Something like that can be used to see how big the LED is in on screen pixels. If you're starting to get too small start making the LEDs bigger and fuzzier to simulate antialiasing.
     
    Last edited: Dec 1, 2015
  7. alberto2000

    alberto2000

    Joined:
    Jul 26, 2017
    Posts:
    9
    Sorry to necrobump this almost 2yr old thread but @AKiSeY did you get this fixed eventually? I'm working on the exact same problem. The shorter the LED-Pixel-Pitch (the closer the LEDs) and the smaller the LED-footprint, the more the moirée effect is visible. I'd appreciate very much if you could share how you went ahead with this back then. Thanks
     
  8. alberto2000

    alberto2000

    Joined:
    Jul 26, 2017
    Posts:
    9
    I found this but can't seem to find any contact information to get in touch with the guy who made it:
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    That video has a lot of moire in it as well, partially masked by the video compression, but it's there. Alan Zucconi has an article on how to avoid the moire by fading the pixelated effect in and out based on distance.

    http://www.alanzucconi.com/2016/05/04/lcd-shader/

    Personally I would use derivatives instead of using distance, but distance does work and is easier for most people to get their head around even if it requires more manually tweaking.
     
    alberto2000 likes this.
  10. alberto2000

    alberto2000

    Joined:
    Jul 26, 2017
    Posts:
    9
    @bgolus thanks, but I think there's more than just the fade between the real pixelated and a "fake" long distance view, I'm interested in more details about that shader