Unity Tutorial — Rim Light shader

Hey! In this tutorial, I’ll show you how to implement a Rim Light shader. This technique consists in calculating the dot product of object’s normals and the view direction to put light around the edges of an object. It can be used to create damage effects, for example.

Let’s get started with our shader! To implement this effect, we’ll need:

• 3D model’s normals
• Camera’s view direction

First, we need to add a parameter to control the Rim Light’s color. It’ll allow us to have more control in our material to choose a good color.

`Properties{    _MainTex ("Texture", 2D) = "white" {}    _RimColor ("Rim Color", Color) = (1, 1, 1, 1)}....float4 _RimColor;`

In the vertex shader, we’ll calculate some information to pass to the fragment shader. In our appdata struct, add the normal information and in the v2f add variables for the worldSpaceNormal and the View Direction.

`struct appdata{    float4 vertex : POSITION;    float2 uv : TEXCOORD0;    float3 normal : NORMAL;};...struct v2f{    float2 uv : TEXCOORD0;    float4 vertex : SV_POSITION;    float3 worldNormal : TEXCOORD1;    float3 viewDirection : TEXCOORD2;};`

With our structs ready, we can start to code our vertex/fragment shaders.

In the vertex shader, calculate the World Space Normal from our model and the View Direction vector based on each vertex. To do it, Unity provides us the function UnityObjectToWorldNormal(vec3) to calculate the World Space Normals and WorldSpaceViewDir(vec3) for the View Direction.

Let’s use this functions and pass this information to the fragment shader, throw the variable output.

`v2f vert (appdata v){    v2f output;    output.vertex = UnityObjectToClipPos(v.vertex);    output.uv = TRANSFORM_TEX(v.uv, _MainTex);    output.worldNormal = UnityObjectToWorldNormal(v.normal);    output.viewDirection = WorldSpaceViewDir(v.vertex);    return output;}`

Now, create a function to calculate the dot product of the normal and the view direction.

`float4 rimLight(float4 color, float3 normal, float3 viewDirection){   float NdotV = dot(normal, viewDirection);      return float4(NdotV.rrr, 1);}`

Here’s the result of this operation. The border gets darker and the center lighter.

In our case, we need to invert it by doing a OneMinus operation to get a bright border.

`float NdotV = 1 - dot(normal, viewDirection);`

We can have more control of our shader by adding a variable to control the whole effect intensity (_RimIntensity) and another variable to control the dot product propagation (_RimPower).

`Properties{    _MainTex ("Texture", 2D) = "white" {}    _RimColor ("Rim Color", Color) = (1, 1, 1, 1)    _RimIntensity ("Rim Intensity", Range (0, 1)) = 0    _RimPower ("Rim Power", Range (0, 5)) = 1}...float4 _RimColor;float _RimIntensity;float _RimPower;`

In the rimLight function, apply this operation in the NdotV variable.

`float4 rimLight(float4 color, float3 normal, float3 viewDirection){    float NdotV = 1 - dot(normal, viewDirection);    NdotV = pow(NdotV, _RimPower);    NdotV *= _RimIntensity;    float4 finalColor = float4(NdotV.rrr, 1) * _RimColor;    return finalColor;}`

Now to get the final color, lerp between the current pixel color and the _RimColor based on the NdotV variable.

`float4 rimLight(float4 color, float3 normal, float3 viewDirection){    float NdotV = 1 - dot(normal, viewDirection);    NdotV = pow(NdotV, _RimPower);    NdotV *= _RimIntensity;    float4 finalColor = lerp(color, _RimColor, NdotV);    return finalColor;}`

To apply our effect, in the fragment shader call our rimLight function passing the normalized WorldSpaceNormal and ViewDirection.

`fixed4 frag (v2f i) : SV_Target{    fixed4 col = tex2D(_MainTex, i.uv);    i.worldNormal = normalize(i.worldNormal);    i.viewDirection = normalize(i.viewDirection);        col = rimLight(col, i.worldNormal, i.viewDirection);    return col;}`

Here’s the final result!

Showcase

Here’s an example. I’ve created a scene to use this effect as a damage feedback using animations and collision triggers.

--

--

More from Fe Game Art

Game developer and Technical Art enthusiast

Love podcasts or audiobooks? Learn on the go with our new app.