Shaders for making the picture like an old LCD.

Let'Âs talk about the technology this time. In this article I'Âm going to show you how to create a shader for achieving an old LCD-style graphics. This kind of shader will work for a pixel-art and turning a picture into an old-technology style. You better not abuse this shader yet you may use it sometimes for a particular purpose. (Let me make it clear - I'm not suggesting to use this effect all the time. But for instance for loading screens it may work well).

For your information I don’t have deep understanding of shaders and I expect even less from you. That’s why I’m going to write considering that you know  hardly anything or almost nothing about shaders. Furthermore, I will try to explain you the ABCs of shaders so if you’re totally new here – welcome!

What are shaders and how do they work?

That’s what you need to know: shader – is a small program that is executed on a GPU (graphics processing unit) for each vertex (vertex shaders) and for each pixel (pixel or “fragmental” shaders).
As a matter of fact – even a simple setting a texture on a triangle – is a shader. In that shader its vertex part calculates triangle’s vertices and pixel part is directly rendering the texture’s pixels. The shader is called in order to draw every pixel respectively. Shaders can work simultaneously.

An important note. Each shader takes the input parameters and casts the output ones. What is more it can work in any order. Modern GPUs can execute big amounts of shader threads at the time. So when one shader is processing a pixel with its coordinates (0, 0), another one can be processing a pixel with its coordinates (10, 10) at the same moment.

In this way a shader which is processing a pixel at (0, 1) doesn’t know (and has no access) to the result of processing the pixel (0, 0). It can only apply to the initial value. That’s why when you need to use several interdependent effects sequentially – you will probably have to create few shaders.
Unity allows you to use various shading languages but I advise GG because it has no problems compiling in both OpenGL and DirectX. Therefore we don’t need to create two different shaders.
Another thing to know – for implementation a picture post-processing (That’s the thing we are doing here) we don’t need shaders for particular sprites but a general shader for the whole screen. To be more precise, render will be directed to a texture at first and the texture will use the shader to be rendered on the screen. And yes, we will need a Pro version of Unity.
So, here we go!
The first thing we will need – is to learn how to create a shader for the whole camera which doesn’t change anything.
Fot this reason let’s create a new shader (a file with .shader extension) and copy this code in there:

Shader "Custom/CRTShader" 
{
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader {
        Pass {
            ZTest Always Cull Off ZWrite Off Fog { Mode off }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            #include "UnityCG.cginc"
            #pragma target 3.0

            struct v2f 
            {
                float4 pos      : POSITION;
                float2 uv       : TEXCOORD0;
            };

            uniform sampler2D _MainTex;

            v2f vert(appdata_img v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord);
                return o;
            }

            half4 frag(v2f i): COLOR
            {
                half4 color = tex2D(_MainTex, i.uv);
                return color;
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

Main items:
•    Properties — describes the input parameters of the shader (parameters set from outside). It’s only a texture at the moment.
•    vert — vertex shader, frag – fragmental
•    struct v2f — describes the data structure that is passed from a vertex to a fragmental shader.
•    uniform — creates a reference from the shading language to the parameter(es) from item 1.
•    In the example above the vertex shader makes an operation with a matrix for calculating the vertex coordinates and coordinates for the texture. Let’s just take it as a magic that works ;).
•    In the same example a fragmental shader is reading a texture by its coordinates which it got from a vertex shader (tex2D command) and returns a resulting color which actually will be rendered.
•    In a shader language multicomponent structures are often required. For example we have 3 coordinates or 4 color components. In order to describe them the one uses types like float2 (a structure of 2 floats) or, for instance int4. The components can be referred to via a dot .x .y .z .w or .r .g .b .a

We’re almost there. Now we need to apply the shader to the camera.
Let’s create a controlling script on C# for that.

Shader "Custom/CRTShader" 
{
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader {
        Pass {
            ZTest Always Cull Off ZWrite Off Fog { Mode off }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            #include "UnityCG.cginc"
            #pragma target 3.0

            struct v2f 
            {
                float4 pos      : POSITION;
                float2 uv       : TEXCOORD0;
            };

            uniform sampler2D _MainTex;

            v2f vert(appdata_img v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord);
                return o;
            }

            half4 frag(v2f i): COLOR
            {
                half4 color = tex2D(_MainTex, i.uv);
                return color;
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

We only have to put it on camera and we should set our shader at the “shader” field.

How can we know that it works? Try to write something like color.r = 0; in the shader before “return color” and if everything goes well you will get a picture without the red color.

Here we are. We’re done with the shaders.


Let’s proceed to the effect implementation.

What do we want to accomplish? At first let’s try to implement an effect when the picture is made of colorful pixels. This way:


How to deal with that? It’s pretty obvious. You need to loop with the 3 pixels step and leave only R, G, B components for every pixel on the screen.
Task #1 is to get screen coordinates of the current vertex in the pixel shader.
For this we will need to calculate something in a vertex shader and pass the results to a pixel shader. As I mentioned before in order to exchange data between the vertex and pixel shaders the structure v2f is used and it currently has 2 fields – pos and uv. Let’s add three float4 scr_pos : TEXCOORD1.
Let’s add a string to the vertex shader:

o.scr_pos = ComputeScreenPos(o.pos);

Now we will get a screen coordinate in range (0..1) in a pixel shader. We need the pixels. It’s easy to be done too:

float2 ps = i.scr_pos.xy *_ScreenParams.xy / i.scr_pos.w;

Wooo! Now ps contains the pixel coordinates on the screen. After that you should write something like that:

int pp = (int)ps.x % 3; // a remainder of the division.
float4 outcolor = float4(0, 0, 0, 1);
if (pp == 1) outcolor.r = color.r; else if (pp == 2) outcolor.g = color.g; else outcolor.b = color.b;
return outcolor;

You'll get something like that:

You can observe 2 things: the first one is – the effect ended up being really strong, and the second – the picture became darker. Thanks god, fixing the first one will do the second.

I suggest not to divide strictly R/G/B, better leave all the components but just in a different proportion.That is to leave 100% R in a “red” column, and nearly 50% G and B. And it would be even better if we could customize that later.
 
Per se we can make our conversion by multiplying the color by a multiplier. In order to leave just R, we need to multiply color by float4(1, 0, 0, 1) (4th component – alpha, we’re not gonna change that). We want to customize coefficients. That is multiply a red column by (1, k1, k2, 1), green – by (k2, 1, k2, 1) and blue by (k1, k2, 1, 1).

First let’s incorporate the descriptions of two parameters at the very beginning of the shader.

_VertsColor("Verts fill color", Float) = 0
_VertsColor2("Verts fill color 2", Float) = 0

then set references:
uniform float _VertsColor;
uniform float _VertsColor2;

Now go to the code of the pixel shader and manipulate the color.

if (pp == 1) { muls.r = 1; muls.g = _VertsColor2; }
else
    if (pp == 2) { muls.g = 1; muls.b = _VertsColor2; }
    else
        { muls.b = 1; muls.r = _VertsColor2; }

color = color * muls;

One thing left to do –  is to learn how to manage those parameters in Unity.

[Range(0, 1)]
public float verts_force = 0.0f;
[Range(0, 1)]
public float verts_force_2 = 0.0f;

Into the method OnRenderImage before Graphics_Blit let’s add:

mat.SetFloat("_VertsColor", 1-verts_force);
mat.SetFloat("_VertsColor2", 1-verts_force_2);

I subtract from 1 to show it more graphically. The bigger parameter - the darker a column becomes.

If you’ve done everything correctly, you are gonna see the scrolls in Unity inspector choosing the Camera.

Let’s check the effect out:

It has become better. Yet we need a bit more brightness. Let’s add the pull-bars for the brightness and the contrast control to our shader. 

_Contrast("Contrast", Float) = 0
_Br("Brightness", Float) = 0
....
uniform float _Contrast;
uniform float _Br;
....
color += (_Br / 255);
color = color - _Contrast * (color - 1.0) * color *(color - 0.5); 

C# script.
    [Range(-3, 20)]
    public float contrast = 0.0f;
    [Range(-200, 200)] 
    public float brightness = 0.0f;
...
    mat.SetFloat("_Contrast", contrast);
    mat.SetFloat("_Br", brightness);

Result:

(contrast = 2.1, brightness = 27)
Now let’s implement scan lines. It’s easy. We need to darken every third row.

if ((int)ps.y % 3 == 0) muls *= float4(_ScansColor, _ScansColor, _ScansColor, 1);

And the finishing touch can be Bloom-effect. You can download this kind of shader here.  Well done! We have got the picture from the top of the article.

And of course – this shader will look the best on a triple pixel like at the examples.
Task can be easily solved by just multiplying a texture by another texture. That is having the parameters of lines intensity we are rendering the sample-texture and then multiply it. That would be better for the efficiency but the purpose of the article was not to write the most optimal shader but to show the general idea.

 

 

All data posted on the site represents accessible information that can be browsed and downloaded for free from the web.

 

http://habrahabr.ru/post/211140/

 

User replies

No replies yet