# Colored Rim Lighting

Hello Community,

I’m currently trying to create a shader for a project I’m working on. It’s supposed to create rim lighting in a lighter color of a vertex close to it. It’s a bit difficult to explain. So basically I have rim lighting around my entity now, in white. Now If the object was blue the rim light should be light blue. If it was red on one side and blue on the other, it should be light red and light blue on their respective sides.

Is something like that even possible?
Also could someone explain how I’m able to show rim lighting only in lighted or shadowed areas?
It’s a bit complex…

I’ve a shader coded already waiting to implement these changes. It has ramp shadows, specular, rim and wrapped diffuse lighting.

The specular looks a bit weird and makes the object look sorta plastic-like. Any help there would be sweet, too!

I know it’s a lot, but maybe we can tackle this problem step by step.

Many thanks

Moritz

Welcome!

That sounds impossible because vertices don’t know about other vertices…

…however, if you’re just talking about using texture or vertex colors, certainly!

I’ve personally got no knowledge of the shadow system. Utilizing light probes is probably best, but I don’t know how to use those either. Gotta love that shader documentation.

Example code and a package would be sweeter.

Thanks for your great help so far. I’ll put in some research on light probes

Clarification:

Basically the only thing I’m trying to achieve is this…
I have a Character. He has a yellow head, a red body, green arms and blue legs.
Now the RIM LIGHTING around the head should be light yellow, around the body light red, around the arm light green and around the legs light blue. So basically I need to read out colors, make them a bit lighter and change the rim light accordingly.

Any help on that?

Moritz

Why? Vertex colors? Different meshes with uniform colors? Texture maps? The data’s got to come from somewhere. Use it!

Apparently you’ve got the rim calculation working already? Get the dot product of the vert-to-eye vector and normal, invert it, raise it to a power or use a greyscale LUT, then multiply by some color. Or, whatever else you need for “a bit lighter”. If you’ve got the rest of it working, why would this colorization trouble you? It seems like the easiest part of what you’re doing, to me. It sounds to me like you don’t know why part of the mesh is a certain color; that won’t work out. Again, it’s going to be impossible to give you good help without posting some of the work you already did.

It’s one single mesh, with UV data and then just a texture applied normally. So the color of the character is only based on the texture map (as far as I understand).

``````Shader "ToonRimShader"
{
Properties
{
_Color ("Main Color", Color) = (1, 1, 1, 1)
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
_RampTex ("Shading Ramp", 2D) = "gray" {}
_RimPower ("Rim Power", Float) = 3.0
_Cutoff ("Alphatest Cutoff", Range(0, 1)) = 3.0

_UseColor ("Use Color", Color) = (1, 1, 1, 1)
_LightColor ("Light Color", Color) = (1, 1, 1, 1)
}

{
Tags { "RenderType" = "Opaque" }

CGPROGRAM
#pragma target 3.0

struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float3 viewDir;
};

sampler2D _MainTex, _BumpMap, _RampTex;
samplerCUBE _Cube;

float4 _UseColor;
float4 _LightColor;
float _RimPower;

half4 LightingToonRimShader (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
half3 h = normalize (lightDir + viewDir);

half NdotL = dot (s.Normal, lightDir);
half diff = NdotL * 0.5 + 0.5;

half3 ramp = tex2D(_RampTex, float2(diff * atten)).rgb;

float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, 50.0);

half4 c;
c.rgb = (s.Albedo * (_LightColor0.rgb * diff * ramp) + (_LightColor0.rgb * spec)) * (atten * 2);
c.a = s.Alpha;
return c;
}

void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Albedo *= _UseColor.rgb;
o.Alpha = tex2D(_MainTex, IN.uv_MainTex).a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));

half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
o.Emission = pow(rim, _RimPower) * _LightColor.rgb;
}
ENDCG
}

Fallback "Transparent/Cutout/Diffuse"
}
``````

I wrote this with the help of the shaderlab documentation and a team fortress shader found somewhere on these forums. I think that I got a good sense of how it works and seem to understand the basics. As you might’ve noticed I’m fairly new to shader programming. I also added a _UseColor to change the Albedo color and that works well (it’s not needed, that was just for me). I AM also able to change the rim lighting color. Just not in the way I want. Maybe I’m doing it totally wrong.

Thanks for your help so far

You’re already incorporating the texture map as some kind of “light” by using the rim calculation as “emission”. Without defining exactly what this means, you can’t progress forward: [quote]
It’s supposed to create rim lighting in a lighter color of a vertex close to it.
[/quote]

The TF2 toon shader form the wiki is a bit weird, I wouldn’t use that as a base. I’d strongly recommend you start from scratch so you actually know what everything in that shader is doing because you seem very lost with that one.

As for colouring the rim highlight - you’re already sampling _MainTex to get color info (yellow for hair ect) just a few lines above your rim calculation…

Wow! That was genius I actually understand how to achieve the effect I want now. I got it working. Great tip about sampling the texture. Thanks! I will probably start over anyways. But now I actually know how to create the desired result.

Do you guys have any tips on making the character look less… plastic-like? It’s the specular lighting.

It probably looks like plastic because you’re not modulating the specular highlight, it’s being calculated as if the entire surface of the model is 100% smooth, like plastic.

Paint an alpha channel into your main texture, then read/save that as o.Gloss in your surface function and then multiply that by the spec in your lighting function. You’d paint the alpha channel so that only certain areas would pick up the specular highlight, rough surfaces (hair, skin ect) wouldn’t have any or very little specular highlights, flat surfaces (armour, eye balls ect) would have more - dirt would probably settle in creases, as well as general occlusion which would often prevent highlights from showing, hard surface edges would be worn flat and probably pick up highlights more - do a Google image search for “specular map” and you’ll get a good idea of how they’re usually authored/painted.

So my shader works now… for the most part. I have one last question - I need to implement the rim light only on places where the character is shadowed. Any tips on achieving this? Light probes don’t seem to be the problem solver for this one…

You might want to consider writing your own lighting model so that you can pick up the amount of light at each vertex or fragment.
Then you can do something like: rim = rim * pow((1 - light), 2);

``````Shader "Toon/ToonRimShader"
{
Properties
{
_Color ("Main Color", Color) = (1, 1, 1, 1)
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
_RampTex ("Shading Ramp", 2D) = "gray" {}
_SpecularTex ("Specular Level (R) Gloss (G)", 2D) = "gray" {}
_RimPower ("Rim Power", Float) = 3.0
_Cutoff ("Alphatest Cutoff", Range(0, 1)) = 3.0
}

{
Tags { "RenderType" = "Opaque" }

CGPROGRAM
#pragma target 3.0

struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float3 viewDir;
};

sampler2D _MainTex, _BumpMap, _RampTex, _SpecularTex;

float _RimPower;

half4 LightingToonRimShader (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
half3 h = normalize (lightDir + viewDir);

half NdotL = dot (s.Normal, lightDir);
half diff = NdotL * 0.5 + 0.5;

half3 ramp = tex2D(_RampTex, float2(diff * atten)).rgb;

float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, 50.0 * s.Gloss) * s.Specular;

half4 c;
c.rgb = (s.Albedo * (_LightColor0.rgb * diff * ramp) + (_LightColor0.rgb * spec)) * (atten * 2);
c.a = s.Alpha;
return c;
}

void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Alpha = tex2D(_MainTex, IN.uv_MainTex).a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));

float3 specMap = tex2D(_SpecularTex, IN.uv_MainTex).rgb;
o.Specular = specMap.r;
o.Gloss = specMap.g;

half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
o.Emission = pow (rim, _RimPower) * tex2D(_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}

Fallback "Transparent/Cutout/Diffuse"
}
``````

I also added outlines now. They work great except the line doesn’t scale. So from far away all I see is a big, black border. I could use the distance from camera to object in order to change it. How can I calculate that?

Any hints at all? I keep trying to figure this out but can’t seem to get it right…

``````Shader "Toon/ToonRimShader"
{
Properties
{
_Color ("Main Color", Color) = (1, 1, 1, 1)
_Outline ("Outline width", Float) = 0.0025
_OutlineColor ("Outline Color", Color) = (0,0,0,1)
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
_RampTex ("Shading Ramp", 2D) = "gray" {}
_SpecularTex ("Specular Level (R) Gloss (G)", 2D) = "gray" {}
_RimPower ("Rim Power", Float) = 3.0
_Cutoff ("Alphatest Cutoff", Float) = 0.5
}

CGINCLUDE
#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};

struct v2f
{
float4 pos : POSITION;
float4 color : COLOR;
};

uniform float _Outline;
uniform float4 _OutlineColor;

v2f vert(appdata v)
{
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

float3 norm = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 offset = TransformViewToProjection(norm.xy);

o.pos.xy += offset * o.pos.z * _Outline;
o.color = _OutlineColor;
return o;
}

ENDCG

{
Tags { "RenderType" = "Opaque" }

CGPROGRAM
#pragma target 3.0

struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float3 viewDir;
};

sampler2D _MainTex, _BumpMap, _RampTex, _SpecularTex;

float _RimPower;

half4 LightingToonRimShader (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
half3 h = normalize (lightDir + viewDir);

half NdotL = dot (s.Normal, lightDir);
half diff = NdotL * 0.5 + 0.5;

half3 ramp = tex2D(_RampTex, float2(diff * atten)).rgb;

float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, 50.0 * s.Gloss) * s.Specular;

half4 c;
c.rgb = (s.Albedo * (_LightColor0.rgb * diff * ramp) + (_LightColor0.rgb * spec)) * (atten * 2);
c.a = s.Alpha;
return c;
}

void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Alpha = tex2D(_MainTex, IN.uv_MainTex).a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));

float3 specMap = tex2D(_SpecularTex, IN.uv_MainTex).rgb;
o.Specular = specMap.r;
o.Gloss = specMap.g;

half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
o.Emission = pow (rim, _RimPower) * tex2D(_MainTex, IN.uv_MainTex).rgb;
}

ENDCG

Pass
{
Name "Outline"
Tags { "LightMode" = "Always" "Queue" = "Overlay" }

Cull Front
ZWrite On
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
Offset 15,15

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

half4 frag(v2f i) : COLOR { return i.color; }

ENDCG
}
}

Fallback "Transparent/Cutout/Diffuse"
}
``````

I still have the same 2 problems:

• Outlines not scaling when viewed from the distance
• Rim light needed only on the shadow side

Sorry for being so persistent with these. I’m still trying to learn and made good progress.
But I AM stuck at the moment…