// // Intro to Shaders stream source code! // Annotated like heck! // Originally streamed at http://www.twitch.tv/acegikmo // Given as a thanks to Patreon supporters 💖 // Your support is as always *very* appreciated, thank you so much! // I hope this code will prove helpful one way or another! // // Cheers! // Freya Holmér // //////////////////////////////////////////////////////////// // This marks the beginning of the shader, // and this is where you can specify a name/path for it in the shader selection menu Shader "Unlit/Annotated Simple Shader" { Properties { // This is where we define properties we can tweak per-material // These show up in the inspector! _Color ( "Color", Color ) = (1,1,1,1) _Gloss ( "Gloss", Range(0,1) ) = 1 // Experimental properties _WaterShallow ( "_WaterShallow", Color ) = (1,1,1,1) _WaterDeep ( "_WaterDeep", Color ) = (1,1,1,1) _WaveColor ( "_WaveColor", Color ) = (1,1,1,1) _ShorelineTex ("Shoreline", 2D) = "black" {} } // Required in every shader. Some shaders can have multiple subshaders, but we'll only use one SubShader { // This is used by Unity's rendering pipeline to determine render order and such Tags { "RenderType"="Opaque" } // Some shaders render multiple passes, but in our case, we only need one Pass { // This marks the start of our CG/HLSL/Shader code CGPROGRAM // These specify the names of our fragment and vertex shaders #pragma vertex vert #pragma fragment frag // Various Unity utilities required to access their helper functions and variables #include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" // Mesh data: vertex position, vertex normal, UVs, tangents, vertex colors // This is data from the mesh being passed into the vertex shader. // Note that all of these are in mesh space, or, local space struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv0 : TEXCOORD0; // float4 colors : COLOR; // float4 tangent : TANGENT; // float2 uv1 : TEXCOORD1; }; // These are our interpolators, the output of the vertex shader, the data of which // is going to be interpolated across all visible triangles being rendered struct VertexOutput { float4 clipSpacePos : SV_POSITION; float2 uv0 : TEXCOORD0; float3 normal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; // These are required to make our properties at the top // be able to pass this data into the fragment and vertex shader float4 _Color; float _Gloss; // Experimental properties sampler2D _ShorelineTex; uniform float3 _MousePos; float3 _WaterShallow; float3 _WaterDeep; float3 _WaveColor; // This is the vertex shader, this is run for every vertex in the mesh VertexOutput vert( VertexInput v ) { VertexOutput o; // Assigning to o in here means we're passing data // through interpolators, to the fragment shader o.uv0 = v.uv0; o.normal = UnityObjectToWorldNormal( v.normal ); o.worldPos = mul( unity_ObjectToWorld, v.vertex ); // Clip space pos is required to make it positioned correctly o.clipSpacePos = UnityObjectToClipPos( v.vertex ); return o; } // Inverse Lerp answers the question: // Given a range from A to B, at what percentage between these two // values, does Value lie? // For instance, if a = 10, b = 20, value = 15, // this function returns 0.5, because 15 is halfway from 10 to 20 float InvLerp( float a, float b, float value ){ return ( value - a )/( b - a ); } // Stairsteps a color range, with a given number of steps per unit. // Similar to rounding with a given precision float Posterize( float steps, float value ){ return floor(value * steps) / steps; } // This is the fragment shader, which runs for every "pixel" it renders too float4 frag( VertexOutput o ) : SV_Target { // Waves and Texture experiments /* float shoreline = tex2D( _ShorelineTex, o.uv0 ).x; float waveSize = 0.04; //float shape = o.uv0.y; float shape = shoreline; float waveAmp = (sin( shape / waveSize + _Time.y * 4 ) + 1) * 0.5; waveAmp *= shoreline; float3 waterColor = lerp( _WaterDeep, _WaterShallow, shoreline ); float3 waterWithWaves = lerp( waterColor, _WaveColor, waveAmp ); return float4(waterWithWaves, 0); return frac(_Time.y); return shoreline; */ // Mouse glow experiments /* float dist = distance( _MousePos, o.worldPos ); float glow = saturate(1-dist); */ // Range remapping & Lerp experiments /* float2 uv = o.uv0; float3 colorA = float3( 0.1, 0.8, 1 ); float3 colorB = float3( 1, 0.1, 0.8 ); float t = uv.y; t = Posterize(16,t); return t; float3 blend = MyLerp( colorA, colorB, t ); return float4(blend,0); */ // We have to normalize this per-pixel, // since interpolating normalized vectors // does not yield normalized results float3 normal = normalize(o.normal); // Lighting - We now set up our light source, in this case, // Unity's primary light source, which is the directional light // Note that _WorldSpaceLightPos0 is not a position for // directional lights, it is the light direction float3 lightDir = _WorldSpaceLightPos0.xyz; float3 lightColor = _LightColor0.rgb; // Direct diffuse light - this simple dot-product based one // is also known as Lambert or Lambertian shading. // The max(0,x) is required to make all negative values 0, // otherwise we would get incorrect lighting when adding it all together float lightFalloff = max( 0, dot( lightDir, normal) ); float3 directDiffuseLight = lightColor * lightFalloff; // Ambient Light - to make the backfaces not go pitch black float3 ambientLight = float3( 0.1, 0.1, 0.1 ); // In order to get specular lighting using the Phong reflection model, // we need the view vector - the direction from the pixel/fragment to the camera. // To calculate this, we use the camera position and the pixel/fragment world position float3 camPos = _WorldSpaceCameraPos; float3 fragToCam = camPos - o.worldPos; float3 viewDir = normalize( fragToCam ); // We then bounce the view vector by the surface normal, // which gives us the view reflection vector float3 viewReflect = reflect( -viewDir, normal ); // Direct specular light - we can get the reflection falloff by // doing the dot product between the light direction and the view reflection vector. // This gives us a soft gradient. float specularFalloff = max( 0, dot( viewReflect, lightDir ) ); // We now need to raise it to the power of some value, // to sharpen it based on our gloss value. // In this case, we remap it exponentially from [0 to 1} to {1 to 2048} float specularExponent = exp2( _Gloss * 11 ); // Now we alter the falloff based on this exponent specularFalloff = pow( specularFalloff, specularExponent); // Multiply by gloss to fake energy conservation. // The more rough a surface is, the more spread out its reflection // Conversely - the sharper a reflection, the more focused and intense the light gets specularFalloff *= _Gloss; // Color it by the light color float3 directSpecular = specularFalloff * lightColor; // Composite by calculating our final diffuse light. // The diffuse light is unique in that it's // affected by the color of the surface, so we need it // separated from the specular light float3 diffuseLight = ambientLight + directDiffuseLight; // Color the diffuse light by the surface color, and add specular light on top float3 finalSurfaceColor = diffuseLight * _Color.rgb + directSpecular; // Output the color. The alpha channel is unused, though set to 1 return float4( finalSurfaceColor, 1 ); } ENDCG } } }