We start on how to setup a basic scene in Unity to work with. The initial examples are going to use a plane and a camera pointing at the plane to show the different functionality of the fragment shaders. The setup of the plane and the camera is explained, moreover the shaders can work with any geometry without major modifications.
After starting Unity, you will probably be viewing an empty project. If not, you should create a new project by choosing File > New Project... from the menu. If you are not familiar with Unity's Scene View, Hierarchy View, Project View and Inspector View, now would be a good time to read the first two (or three) sections (Unity Basics and Building Scenes) of the Unity User Guide.
The first step in this scene setup is to create a plane. Click on Create > Plane in the menu Hierarchy View, or try out one of the other methods to do this in Unity. Then position the camera so it can show the plane. Double-click the Main Camera Object from the Hierarchy view and get a Scene View of the plane. Then select the Align with View option from the menu GameObject. Make sure the plane does not cover the entire screen so you can see the difference between rendering with a shader and without it. you should now see a gray sphere in the Preview section of the Inspector View for the material.
Create a new Material by clicking Create in the Project View and choosing Material. A new material called New Material will appear in the Project View. Change the name of the Material to something more descriptive like Material-ShaderLab.
Create a Shader in the same way as a Material, but selecting Shader in the menu. Change the name of the Shader to something more descriptive like Shader-ShaderLab.
Shader "Custom/NewShader" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
The default surface shader is generated after the creation of a new shader. The text can be replaced by a new version of a vertex-fragment shader.
Link the Shader with the Material in the inspector windows of the material. You can drag and drop the shader in the Project View over the material, or you can select the material in the Project View and then choose the shader from the drop-down list labeled Material-ShaderLab in the Inspector View. In either case, you should now see a gray sphere in the Preview section of the Inspector View for the material. If it doesn't show and an error message is displayed at the bottom of the Unity window, reopen the shader and check in the editor that the text is the same as that given in this lesson.
Link the new material to a triangle mesh. To do this attach the material to the new plane by dragging and dropping the material from the Project View to the plane in the Hierarchy View. Alternatively you can drag and drop the material from the Project View to the plane in the Scene View. Another way would be to select the plane in the Hierarchy View, locate the Mesh Renderer component in the Inspector View (open it by clicking the title if it isn't open) and open the Materials setting of the Mesh Renderer by clicking it. Use whichever of these methods that works best for you. Change the Default-Diffuse material to the new material by clicking the dotted circle icon to the right of the material name and choosing the new material from the pop-up window. The plane in the Scene View should now have the same color as the preview in the Inspector View of the material. Changing the shader should (after saving and switching to Unity) change the appearance of the plane in the Scene View.
Now you have the scene setup completed.
This will explain the four main parts of a Shader in ShaderLab that you can see in the code.
Shader "Custom/Empty" { SubShader { Pass { CGPROGRAM ENDCG } } }
The difference between vertex and fragment shaders is the process developed in the render pipeline. Vertex shader could be define as the shader programs that modifies the geometry of the scene and made the 3D projection. Fragment shaders are related to the render window and defines the color for each pixel. Fragment and Vertex shader can present more functionality in new graphics cards like moving the vertex position or storing more data for each pixel.
Shader "Custom/SolidColor" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag float4 vert(float4 v:POSITION) : SV_POSITION { return mul (UNITY_MATRIX_MVP, v); } fixed4 frag() : COLOR { return fixed4(1.0,0.0,0.0,1.0); } ENDCG } } }
This Shader defines a static color depending on the pixel coordinate of the window. This allows the definition of colors in the screen independent of the geometry rendered. If you move the plane you can see the colors don't change on the screen, only the visibility depending on the position of the geometry.
Shader "Custom/WindowCoordinates/Base" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" float4 vert(appdata_base v) : POSITION { return mul (UNITY_MATRIX_MVP, v.vertex); } fixed4 frag(float4 sp:WPOS) : COLOR { return fixed4(sp.xy/_ScreenParams.xy,0.0,1.0); } ENDCG } } }
Window coordinates are not commonly used in shaders, because normally the shaders work with the position of the texture as you will see in the next lessons. However some interesting effects like Render with a Mask or some other image effects use this property.
Shader "Custom/WindowCoordinates/Bars" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct vertOut { float4 pos:SV_POSITION; float4 scrPos; }; vertOut vert(appdata_base v) { vertOut o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.scrPos = ComputeScreenPos(o.pos); return o; } fixed4 frag(vertOut i) : COLOR0 { float2 wcoord = (i.scrPos.xy/i.scrPos.w); fixed4 color; if (fmod(20.0*wcoord.x,2.0)<1.0) { color = fixed4(wcoord.xy,0.0,1.0); } else { color = fixed4(0.3,0.3,0.3,1.0); } return color; } ENDCG } } }
Shader "Custom/WindowCoordinates/Vignetting" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" float4 vert(appdata_base v) : POSITION { return mul (UNITY_MATRIX_MVP, v.vertex); } float4 frag(float4 sp:WPOS) : COLOR { float2 wcoord = sp.xy/_ScreenParams.xy; float vig = clamp(3.0*length(wcoord-0.5),0.0,1.0); return lerp (float4(wcoord,0.0,1.0),float4(0.3,0.3,0.3,1.0),vig); } ENDCG } } }
Some shaders need external information given in types like Variables, Arrays, Textures. These cases are cover by the use of uniforms and properties in ShaderLab. Properties define a set of type which can be passed to the shader in every run. The variables can modify the behavior of the shader, the following example show how to change the values of a shader mask full of circles, The number of rows and columns can be change also the size of the circles.
Shader "Custom/WindowCoordinates/CirclesMask" { Properties { _CirclesX ("Circles in X", Float) = 20 _CirclesY ("Circles in Y", Float) = 10 _Fade ("Fade", Range (0.1,1.0)) = 0.5 } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" uniform float _CirclesX; uniform float _CirclesY; uniform float _Fade; float4 vert(appdata_base v) : POSITION { return mul (UNITY_MATRIX_MVP, v.vertex); } float4 frag(float4 sp:WPOS) : COLOR { float2 wcoord = sp.xy/_ScreenParams.xy; float4 color; if (length(fmod(float2(_CirclesX*wcoord.x,_CirclesY*wcoord.y),2.0)-1.0)<_Fade) { color = float4(sp.xy/_ScreenParams.xy,0.0,1.0); } else { color = float4(0.3,0.3,0.3,1.0); } return color; } ENDCG } } }
This lesson presents a set of shaders which use texture coordinates to draw the faces of a mesh with different effects.
Texture position in a mesh is given by the variable texcoord0, which is set for each vertex. The vertex and fragment shader can modify this variable. You should select in which shader you modify texcoord0, depending of the variables needed for the operation, the number of vertex in the scene and the screen resolution. You should select the modification of texcoord0 values looking for the smallest number of executions.
Shader "Custom/TextureCoordinates/Base" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" struct vertexInput { float4 vertex : POSITION; float4 texcoord0 : TEXCOORD0; }; struct fragmentInput{ float4 position : SV_POSITION; float4 texcoord0 : TEXCOORD0; }; fragmentInput vert(vertexInput i){ fragmentInput o; o.position = mul (UNITY_MATRIX_MVP, i.vertex); o.texcoord0 = i.texcoord0; return o; } float4 frag(fragmentInput i) : COLOR { return float4(i.texcoord0.xy,0.0,1.0); } ENDCG } } }
The range of the values of texcoord0 is from 0 to 1 and are mapped to a specific texture. Textures are images store in the memory of the GPU representing a square or rectangular image, the textures in the fragment shader can be also used as procedural textures which uses mathematical functions to define the color of each pixel in the screen. In the code above you the texcoord0 values where used to set the colors of the Red and Green channels, given a gradient effect for each color.
Shader "Custom/TextureCoordinates/Chess" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct vertexInput { float4 vertex : POSITION; float4 texcoord0 : TEXCOORD0; }; struct fragmentInput{ float4 position : SV_POSITION; float4 texcoord0 : TEXCOORD0; }; fragmentInput vert(vertexInput i){ fragmentInput o; o.position = mul (UNITY_MATRIX_MVP, i.vertex); o.texcoord0 = i.texcoord0; return o; } float4 frag(fragmentInput i) : COLOR { float4 color; if ( fmod(i.texcoord0.x*8.0,2.0) < 1.0 ){ if ( fmod(i.texcoord0.y*8.0,2.0) < 1.0 ) { color = float4(1.0,1.0,1.0,1.0); } else { color = float4(0.0,0.0,0.0,1.0); } } else { if ( fmod(i.texcoord0.y*8.0,2.0) > 1.0 ) { color = float4(1.0,1.0,1.0,1.0); } else { color = float4(0.0,0.0,0.0,1.0);} } return color; } ENDCG } } } | Shader "Custom/TextureCoordinates/ChessOpt" { SubShader { Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" float4 frag(v2f_img i) : COLOR { bool p = fmod(i.uv.x*8.0,2.0) < 1.0; bool q = fmod(i.uv.y*8.0,2.0) > 1.0; return float4(float3((p && q) || !(p || q)),1.0); } ENDCG } } } |
Shader "Custom/TextureCoordinates/Mandelbrot" { SubShader { Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" float4 frag(v2f_img i) : COLOR { float2 mcoord; float2 coord = float2(0.0,0.0); mcoord.x = ((1.0-i.uv.x)*3.5)-2.5; mcoord.y = (i.uv.y*2.0)-1.0; float iteration = 0.0; const float _MaxIter = 29.0; const float PI = 3.14159; float xtemp; for ( iteration = 0.0; iteration < _MaxIter; iteration += 1.0) { if ( coord.x*coord.x + coord.y*coord.y > 2.0*(cos(fmod(_Time.y,2.0*PI))+1.0) ) break; xtemp = coord.x*coord.x - coord.y*coord.y + mcoord.x; coord.y = 2.0*coord.x*coord.y + mcoord.y; coord.x = xtemp; } float val = fmod((iteration/_MaxIter)+_Time.x,1.0); float4 color; color.r = clamp((3.0*abs(fmod(2.0*val,1.0)-0.5)),0.0,1.0); color.g = clamp((3.0*abs(fmod(2.0*val+(1.0/3.0),1.0)-0.5)),0.0,1.0); color.b = clamp((3.0*abs(fmod(2.0*val-(1.0/3.0),1.0)-0.5)),0.0,1.0); color.a = 1.0; return color; } ENDCG } } }
Shader "Custom/TextureCoordinates/Lenna" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Pass { CGPROGRAM #pragma vertex vert_img #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex; float4 frag(v2f_img i) : COLOR { return tex2D(_MainTex, i.uv); } ENDCG } } }
Page last updated: 2013-07-19