CG Programming, Points Lights attenuation and light range

The image below is showing what is happening. I’m simply trying to write a shader that does diffuse and specular lighting. I’m following this tutorial.

However I noticed that for diffuse, it doesn’t take range into account, and doesn’t behave exactly like the default shader in unity. And we have the edges shown below. I don’t know how to actually access the range information so i’m stuck. I’ve searched, but I have not found any good documentation that tells me how to avoid this. Basically what should I do to fix this.

Herese my messy shader code :

Shader "Custom/LightMap Blend Diffuse Specular Transparency" {
	Properties {
		_Color ("MainColor", Color) = (1, 1, 1, 1)
		_SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
		_Shininess ("Shininess", Float) = 10
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_LightMap01 ( "LightMap 1", 2D ) = "white" {}
		_LightMap02 ( "LightMap 2", 2D ) = "white" {}
		_Trans ("Transparency", Range(0, 1)) = 0
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		Pass
		{
			Tags { "LightMode" = "ForwardBase" } 
		
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			uniform float4 _LightColor0;

			sampler2D _MainTex;
			
			float4 _Color;
			float4 _SpecColor;
			float _Shininess;
			
			struct vertexIn
			{
				float4 pos : POSITION;
				float3 norm : normal;
			};
		
			struct vertexOut
			{
				float4 pos : SV_POSITION;
				float4 col : COLOR;
				float3 posWorld : TEXCOORD0;
				float3 norm : TEXCOORD1;
			};
				
			vertexOut vert(vertexIn i)
			{
				vertexOut o;
				
				    				
				float4x4 modelMatrix = _Object2World;
				float4x4 modelMatrixInverse = _World2Object;
				
				o.posWorld = mul(modelMatrix, i.pos); 
				o.norm = normalize( mul(float4(i.norm, 0), modelMatrixInverse).xyz );
				o.col = float4 (0, 0, 0, 0);
				o.pos = mul( UNITY_MATRIX_MVP, i.pos);
				
				return o;
			}
			
			float4 frag(vertexOut o) : COLOR
			{
				float3 normalDirection = normalize(o.norm);
				float3 viewDirection = normalize(_WorldSpaceCameraPos - o.posWorld.xyz);
				float3 lightDirection = float3(0, 0, 0);
				float attenuation;

				lightDirection =  normalize(_WorldSpaceLightPos0.xyz) * (1 - _WorldSpaceLightPos0.w);
						
				float3 PointLightDirection = _WorldSpaceLightPos0.xyz - o.posWorld.xyz;
				float PointLightLength = length(PointLightDirection);
				
				lightDirection += normalize(PointLightDirection) * _WorldSpaceLightPos0.w;
				
				attenuation = 1.0 / PointLightLength;

				float3 diffuseColor = lerp(_LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection)), 
										   _LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection) * attenuation), _WorldSpaceLightPos0.w);

				float3 specularColor = (_LightColor0.rgb * _Color.rgb * max(0, dot(reflect(-lightDirection, normalDirection), viewDirection)) ) 
										* max(0, dot (normalDirection,  lightDirection));
				
				
				return float4((UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb) + diffuseColor, 1);
			}
			
			ENDCG
		}
		
		Pass
		{
			Tags { "LightMode" = "ForwardAdd" } 
		
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			uniform float4 _LightColor0;

			sampler2D _MainTex;
			
			float4 _Color;
			float4 _SpecColor;
			float _Shininess;
			
			struct vertexIn
			{
				float4 pos : POSITION;
				float3 norm : normal;
			};
		
			struct vertexOut
			{
				float4 pos : SV_POSITION;
				float4 col : COLOR;
				float3 posWorld : TEXCOORD0;
				float3 norm : TEXCOORD1;
			};
				
			vertexOut vert(vertexIn i)
			{
				vertexOut o;
				
			    				
				
				float4x4 modelMatrix = _Object2World;
				float4x4 modelMatrixInverse = _World2Object;
				
				o.posWorld = mul(modelMatrix, i.pos); 
				o.norm = normalize( mul(float4(i.norm, 0), modelMatrixInverse).xyz );
				o.col = float4 (0, 0, 0, 0);
				o.pos = mul( UNITY_MATRIX_MVP, i.pos);
				
				return o;
			}
			
			float4 frag(vertexOut o) : COLOR
			{
				float3 normalDirection = normalize(o.norm);
				float3 viewDirection = normalize(_WorldSpaceCameraPos - o.posWorld.xyz);
				float3 lightDirection = float3(0, 0, 0);
				float attenuation;

				lightDirection =  normalize(_WorldSpaceLightPos0.xyz) * (1 - _WorldSpaceLightPos0.w);
						
				float3 PointLightDirection = _WorldSpaceLightPos0.xyz - o.posWorld.xyz;
				float PointLightLength = length(PointLightDirection);
				
				lightDirection += normalize(PointLightDirection) * _WorldSpaceLightPos0.w;
				
				attenuation = 1.0 / PointLightLength;

				float3 diffuseColor = lerp(_LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection)), 
										   _LightColor0.rgb * _Color.rgb * max(0, dot(normalDirection, lightDirection) * attenuation), _WorldSpaceLightPos0.w);


				float3 specularColor = (_LightColor0.rgb * _Color.rgb * max(0, dot(reflect(-lightDirection, normalDirection), viewDirection)) ) 
										* max(0, dot (normalDirection,  lightDirection));
				
				
				return float4( (UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb) + diffuseColor, 1);
			}
			
			ENDCG
		}
		
	} 
	FallBack "Diffuse"
}   

by the way. I’m also calculating specular, but I’m not using the values in the output, because I wanted to isolate diffuse, which is why my code is a mess.

You’ve almost got your own question answered in the topic. :slight_smile:

It’s all about light range and implementing attenuation.

Attenuation is the highlight’s size as a function of distance between light and pixel. So, to calculate it, you obviously need access to the light’s position. There are three ways to go about that:

1:

You can implement your shaders as surface shaders instead of native vert/frag pairs. Surface shaders are compiled by Unity into native vert/frag pairs in ways that utilize Unity’s built-in complex lighting engine, so you don’t have to worry about it. This comes at the cost of some coding flexibility, though surface shaders still do allow quite a bit of customization, including your own vertex shader.

2:

You can stick to a native vert/frag pair as in the above, and then try to tap into the built-in variables Unity’s internal engine passes to the shader system each frame, so your custom vert/frag pair accesses the same variables Unity’s own vert/frag pairs would do after its compiler makes vert/frag pairs out of surface shaders. These variables are mostly defined in UnityCG.cginc, Lighting.cginc and UnityShaderVariables.cginc, which can be found at this path:

C:/"Program Files (x86)/Unity/Editor/Data/CGIncludes

For example in UnityShaderVariables.cginc, you’ll find these declarations for what I believe to be the real-time positions of the 8 nearest light sources to each object:

float4 unity_LightColor[8];
float4 unity_LightPosition[8];
// x = -1
// y = 1
// z = quadratic attenuation
// w = range^2
float4 unity_LightAtten[8];
float4 unity_SpotDirection[8];

3:

You can ignore Unity’s built-in lighting engine altogether, and transfer the needed variables yourself. This means you have to identify the light sources you want to calculate lighting for in C# yourself, and then transfer all of their data manually, including its position, light color, intensity, range, etc. You must declare your own variables to hold the data in your shader, and then you must set the value of these variables on the CPU side using Material.SetFloat, Material.SetColor and Material.SetVector respectively. (Or the static versions found in the Shader class, if you want them to be globally available to all shaders).

Having done that, the real world, physically correct equation for standard light attenuation is 1/d^2, where d is the distance to the light source. But that doesn’t always suit 3D graphics well. There is a more elaborate formula for this in the Cg tutorial here, if you’re interested (Chapter 5.5.1):

http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter05.html

UnityShaderVariables.cginc has the built-in variable float4 _LightPositionRange, where xyz = pos and w = (1/range). From this you get

float attenuation = 1-(distanceToLight / (1 / _LightPositionRange.w)) ;

Which gives a smooth transition and also works for ForwardAdd passes.
You can find the distance by

_WorldSpaceLightPos0.xyz-posWorld.xyz;

You only need to include “AutoLight.cginc” for both _LightPositionRange and _WorldSpaceLightPos0.
Found little info on this elsewhere so hope this helps someone.

I have the same problem. Writing my own cg shaders for lighting. Want to get the light range so that I can calculate attenuation on my own. Or get attenuation. Any of two.

I debug in the shader by painting my pixels with some of the values.
I realized this. unity_LightAtten does not update the range. The X = -1, Y = 1, Z = 0 and W = 1
No matter if I change the range to 0.5, 0.25 or 2 or 4 or whatever.

When I try to update other values like position or color, they don’t seem to change when I move lightsource or change it’s color. It seems like these are the original initialized values and nothing else is happening. Like Unity is not passing them unless I would write surface shaders which I don’t want to. The third option would be to attach to my objects a C# code that simple reads the range and sends it as a uniform. I want to avoid this. Also what if I have different lights with different ranges?

I will continue to investigate this, but somehow some solutions to this are so hard to find (I searched for tutorials for hours).