Home Unity

For the shader wizards out there....

polycounter lvl 18
Offline / Send Message
Nostradamus polycounter lvl 18
Hey guys, I'm sure I am not the only person completely enamored with the game Kentucky Route Zero:
[ame="http://www.youtube.com/watch?v=l5zwtIExdIM"]First 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

  • Farfarer
    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.
  • Nostradamus
    Offline / Send Message
    Nostradamus polycounter lvl 18
    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 :)
  • Xoliul
    Offline / Send Message
    Xoliul polycounter lvl 14
    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.
  • Farfarer
    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.
  • Nostradamus
    Offline / Send Message
    Nostradamus polycounter lvl 18
    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!
  • Farfarer
    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.
  • Nostradamus
    Offline / Send Message
    Nostradamus polycounter lvl 18
    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"
      }
    
  • LoTekK
    Offline / Send Message
    LoTekK polycounter lvl 17
    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"
    }
    
  • Nostradamus
    Offline / Send Message
    Nostradamus polycounter lvl 18
    Hey LoTekK, Thanks soooo much!
    Really thanks for taking time to explain the changes :)
  • Kurainisei
    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!
  • Farfarer
    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"
    }
    
  • Kurainisei
    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:
    223qQRf.png

    Shouldn't I get at least some shadows when the light cutoff is below a threshold?
    Thanks!
  • Farfarer
    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.
  • Kurainisei
    Oh, now I understand it better. I was using a direct light, thanks for the tip!
Sign In or Register to comment.