# For the shader wizards out there....

polycounter lvl 14
Offline / Send Message
polycounter lvl 14
Hey guys, I'm sure I am not the only person completely enamored with the game Kentucky Route Zero:
[ame=" 40 - Kentucky Route Zero (Gameplay) - YouTube[/ame]

I would like to ask you how you think the did the dynamic light?
I already know that they have done the environments with vertex colors, and I am asuming it is something along the same lines with the light!
It is just so sexy and I would love to find out how they did it.

Best Regards,

## Replies

• Offline / Send Message
Polycount Sponsor
I imagine it might simply be a light?

atten = step( atten, _LightCutoff );

Or something like that?

Although it's "forced" in some places (it quickly gets reeled right in when you get to where the dice is) so I'm thinking it's altered based on some other values too.

Could really easily just be some simple 2D planes and a bit of trickery to mask/unmask them. It's a very carefully crafted game, could be one of a whole bunch of methods.
• Offline / Send Message
polycounter lvl 14
Hey, I am not entirely sure what:
atten = step( atten, _LightCutoff );
means, I am very new with Unity shadering
So I'm assuming you mean that it chanes the way the light behaves on the surface...?
atten = attenuation?
What does step do/mean? and the _LightCutoff would that just be a float var?

Thanks again
• Offline / Send Message
polycounter lvl 10
http://msdn.microsoft.com/en-gb/library/windows/desktop/bb509665%28v=vs.85%29.aspx
Basically it will make a linear curve into a stepped one; a steep drop instead of a gradual fade.
• Offline / Send Message
Polycount Sponsor
Ah, sorry.

atten is the float value supplied to the lighting function within Unity's Surface Shader system. It contains the attenuation value of the current light (multiplied by the shadow value if the light casts shadows). It ranges from 1 (fully lit) to 0 (unlit).

step (x, y) returns a float that's either 1 or 0 depending on whether value x is bigger or smaller than y.

_LightCutoff would be a value you give to the shader via a property, so yeah, just a float. Combined with the step() function, anything less than this value would force the lighting to 0 (unlit) and anything above it would force the lighting to 1 (fully lit).

On closer inspection, I'm not convinced that's how they're doing it - the light's not quite binary on/off - it has a gradient to it. Could well just be a texture plane that gets masked somehow.
• Offline / Send Message
polycounter lvl 14
Thanks for the elaboration, much appreciated
I actually asked the developers and just got an answer to what they have done;
"to elaborate on the custom shaders, there is no normal-based
shading. It only uses the light's attenuation to illuminate the
geometry and that's how all the faces get that flat, even lighting."

Does that make sense to you? I must say it makes a bit more sense after your explanation, but as you said they do have this gradient to them...
Thanks again!
• Offline / Send Message
Polycount Sponsor
Perhaps it's closer to something like

atten = step(atten, _LightCutoff) * atten;

That would give the gradient in the centre (because it acts like regular light attenuation) but then the step gives a hard cutoff after the _LightCutoff threshold is reached.
• Offline / Send Message
polycounter lvl 14
Okay after a bit of haggling around I managed to do it! Yay for you!
I ended up having to change it around so it was atten = step(_LightCutoff, atten) * atten; for it not to be... inverted, but it works
Edit: SO it seems it does not display light on blacks, which is very unfortunate for the kind of game I'm making... Is it even possible to do?
This is my shader code, if it makes for any help...
```Shader "Custom/No Normals 2" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_LightCutoff("Maximum distance", Float) = 2.0
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf WrapLambert
uniform float _LightCutoff;
half4 LightingWrapLambert (SurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot (s.Normal, lightDir);

atten = step(_LightCutoff, atten) * atten;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (atten);
c.a = s.Alpha;
return c;
}

struct Input {
float2 uv_MainTex;

};
sampler2D _MainTex;

void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
```
• Offline / Send Message
polycounter lvl 12
it seems it does not display light on blacks
The portion you'll want to look at is:
```c.rgb = s.Albedo * _LightColor0.rgb * (atten);
```

You can ignore the Albedo portion in the lighting calculations. However, this will result in less saturated final colors than you might be used to (in effect you're taking the Albedo and just adding the lighting). The colorAdjust bit in the following code attempts to counter this:
```half vMax = (max(max(s.Albedo.r, s.Albedo.g), s.Albedo.b));
half3 colorAdjust = vMax > 0 ? s.Albedo / vMax : 1;
half4 c;
//take the albedo out of the equation entirely
c.rgb = _LightColor0.rgb * atten * vMax;
```

As for the banding, you can do an alternate way (this will get you banding throughout the range of your lighting, instead of a smooth gradient and a hard cutoff). Depends on which effect you are looking for:

Under "Properties":
```	// exposed param to configure how banded the gradient is
_Steps ("Gradient steps", Float) = 10.0
```

modified atten:
```_Steps = int(_Steps);
atten = int(atten * _Steps) / float(_Steps);
atten = 1 - atten > _LightCutoff ? 0 : atten;
```

The whole thing:
```Shader "Custom/No Normals 2" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_LightCutoff("Maximum distance", Float) = 2.0
_Steps ("Gradient steps", Float) = 10.0
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf WrapLambert

uniform float _LightCutoff;
uniform float _Steps;

half4 LightingWrapLambert (SurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot (s.Normal, lightDir);
//atten = step(_LightCutoff, atten) * atten;
_Steps = int(_Steps);
atten = int(atten * _Steps) / float(_Steps);
atten = atten < 1 - _LightCutoff ? 0 : atten;
half vMax = (max(max(s.Albedo.r, s.Albedo.g), s.Albedo.b));
half3 colorAdjust = vMax > 0 ? s.Albedo / vMax : 1;
half4 c;
c.rgb = _LightColor0.rgb * atten * colorAdjust;
c.a = s.Alpha;
return c;
}

struct Input {
float2 uv_MainTex;
};

sampler2D _MainTex;
half4 _Color;

void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * _Color;
}
ENDCG
}
Fallback "Diffuse"
}
```
• Offline / Send Message
polycounter lvl 14
Hey LoTekK, Thanks soooo much!
Really thanks for taking time to explain the changes
• Offline / Send Message
Hey there!
I've just stumbled on this thread, turns out it's really useful for what I'm trying to do right now.

So I tried the shader in Unity, but I cannot seem to make it work in the right way. Right now the shader only outputs a single color, with no shadow or gradient effect.

Is this how it's meant to be? Right now for me looks like just a regular unlit shader, so I'm clearly missing something.

Unfortunately I've just started learning shaders, so I'm a bit lost here. Could you help me?

Thanks!
• Offline / Send Message
Polycount Sponsor
Was messing with this the other day... give this a try?
```Shader "KRZ" {
Properties {
_Color ("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
_MainTex ("Color (RGBA)", 2D) = "white" {}
_LightCutoff ("Light Cutoff", Range(0.0, 1.0)) = 0.2
}

SubShader {
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM
#pragma surface surf KRZ fullforwardshadows

sampler2D _MainTex;
float4 _Color;
float _LightCutoff;

struct SurfaceOutputKRZ {
fixed3 Albedo;
fixed Alpha;
fixed3 Emission;
fixed3 Normal;
fixed Specular;
};

struct Input {
float2 uv_MainTex;
};

void surf (Input IN, inout SurfaceOutputKRZ o) {
fixed4 tex = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = tex.rgb;
o.Alpha = tex.a;
o.Emission = fixed3(0.0,0.0,0.0); // Stop DX11 complaining.
}

inline fixed4 LightingKRZ (SurfaceOutputKRZ s, fixed3 lightDir, fixed3 viewDir, fixed atten)
{
atten = step(_LightCutoff, atten) * atten;

float4 c;
c.rgb = (_LightColor0.rgb * s.Albedo) * (atten * 2);
c.a = s.Alpha;
return c;
}

ENDCG
}
FallBack "VertexLit"
}
```
• Offline / Send Message
Thank you!
I've tried it, but it still looks like it's an unlit shader to me. Here's what I get on two test meshes:

Shouldn't I get at least some shadows when the light cutoff is below a threshold?
Thanks!
• Offline / Send Message
Polycount Sponsor
Well, if you're using a direct light, everything will be fully lit, always (unless it's in a cast shadow).

This works best with point and spot lights.
• Offline / Send Message
Oh, now I understand it better. I was using a direct light, thanks for the tip!
Sign In or Register to comment.