Skip to main content

History: White Phong part 3 - JaJDoo Shader Guide - Basics

Source of version: 11

Copy to clipboard
            !:::I’M BLINDED!:::
!:::Or:::
!:::Other types of light:::

{maketoc}

!!“Point me to the right direction…”
get it? point like point light, but direction like directional light? ....
.. ah forget it.

Other than point lights, we have two other types of lights in OGRE:
*	__Directional lights__: have no position, only a direction.
*	__Spotlights__: these lights have a ‘corridor cone’ in a direction. They have two cones:
**	__Inner cone__: maximum illumination.
**	__Outer cone__: gradually fading illumination.

!!Directional lights
These lights are probably the easiest thing in the world to calculate. All they have is a direction from which they are coming from (as if originating from infinity and traveling parallel).

All you have to do is ignore the position. That means we have one less calculation. There isn’t much more to tell, just show you the code (even that is redundant) 
{CODE(wrap="1", colors="c++")}
vertexOut mainVS(vertexIn input)
{
	vertexOut output = (vertexOut)0;
	
	float4 worldPos 	= mul(input.pos, world_m);
	//fill output
	//------------------------------
	output.pos = mul(input.pos, worldViewProj_m);
	output.normal 		= mul(input.normal, world_m);
	output.viewDir		= viewPos - worldPos;
	//------------------------------
	return output;
}

float4 mainPS(vertexOut input) : COLOR 
{
	//normalize per pixel
	//------------------------------
	float3 normal 		= normalize(input.normal);
	float3 viewDir 		= normalize(input.viewDir);
	//------------------------------
	//diff and specular
	//------------------------------
	float 	dotNL 	= dot(lightPos.xyz, normal);
	float4	diff = saturate(dotNL);
	
	float3 	halfAng = normalize(viewDir + lightPos.xyz);
	float 	dotNH	= dot(normal, halfAng);
	float 	spec	= pow(saturate(dotNH),50);
	//------------------------------
	
	return  (diff * diffColor + spec*specColor + ambientColor);
}{CODE}

!!Spotlights
Spot lights are a bit more complicated, since they originate from a specific point, project to a specific direction, and have a cone which only in it the object is affected (like a flash light). These points make it somewhat trickier.

Let’s take a look at the spotlight:
{IMG(src="display1867")}{IMG}

To avoid confusion, we will rename __lightDir __to __pixelToLight__ since the spotlight has a facing direction.

We will use two dot products:
* __dotNL __: dot between __pixelToLight__ and the __surface normal__
* __dotPLd__: dot between the inverse __pixelToLight__ and __spotlight direction__

We have a few more variables however; when you ask for spotlight_params from OGRE material script, OGRE will send a float4 organized as such:
* cos(innerConeAngle/2)
* cos(outerConeAngle/2)
*falloff value 
* 1

The forth value (w component) is used to help differentiate spotlights from point lights.

The cosine uses the half of the angle because the angle defines the cone, and we need only the angle between the side of the cone and the axis (which is the spotlight facing vector).

!!!Calculating
There are two parts for determining the effect of the cone of light on the object depending on its direction to it:
# The __dotNL__ must be positive: because otherwise we are either behind the light or on the other side of the object.
# The __dotPLd__ value:
* The value of the __dotPLd__ equals in value to the orthogonal to the __spotlight facing vector__ that extends to the __pixelToLight__; thus, its value will determine the where is the pixel compared to the cones.
* __Diffuse__:
** Larger than cos(innerConeAngle/2): the pixel is in the inner cone.
*** Value: the diffuse will equal the dotNL.
** Larger than cos(innerConeAngle/2): the pixel is in the outer cone.
*** Value: the diffuse will equal the dotNL, multiplied by the relative location of the pixel between the outer and inner cone (will be explained later)
** Otherwise: not in the spotlight’s AOE.
* __Specular__:
**As long as the dotPLd is positive, the specular is present; this is because the specular reflects the source of the light, which means that as long as pixel is not behind the source of the light, the specular will appear.

I ignore the falloff value, because I don’t really know what to do with it.

!!!Code
The vertex program remained almost unchanged, but take a look at it anyhow:
{CODE(wrap="1", colors="c++")}
vertexOut mainVS(vertexIn input)
{
	vertexOut output = (vertexOut)0;
	
	float4 worldPos 	= mul(input.pos, world_m);
	//fill output
	//------------------------------
	output.pos 			= mul(input.pos, worldViewProj_m);
	output.normal 		= mul(input.normal, world_m);
	output.pixelToLight	= lightPos - worldPos;
	output.viewDir		= viewPos - worldPos;
	//------------------------------
	return output;
}
{CODE}

The main change is in the pixel program:
We begin the program by normalizing and preparing the dot products:
{CODE(wrap="1", colors="c++")}
float4 mainPS(vertexOut input) : COLOR 
{
	//prep
	//------------------------------
	float3 normal 		= normalize(input.normal);
	float3 pixelToLight	= normalize(input.pixelToLight);
	float3 viewDir 		= normalize(input.viewDir);

	float 	dotPLd	= dot(-pixelToLight, spotLightDir);
	float 	dotNL 	= dot(pixelToLight, normal);
	//------------------------------{CODE}

Now, let’s add the diffuse and specular as we described earlier and return the value:
{CODE(wrap="1", colors="c++")}	
	// lights
	//------
	float4	diff = 0, spec = 0;
	if(dotNL > 0)
	{	
		//diffuse
		//------------------------------
		if	( dotPLd > spotLightParams.x )
			diff 	= dotNL;
		else 
		if	( dotPLd > spotLightParams.y )
			diff 	= dotNL * (1-(spotLightParams.x - dotPLd)/(spotLightParams.x - spotLightParams.y));
		//------------------------------
		// specular
		//------------------------------
		if (dotPLd > 0) 
		{
			float3 	halfAng = normalize(viewDir + pixelToLight);
			float 	dotNH	= dot(normal, halfAng);
			spec 	= pow(saturate(dotNH),100);
		}
		//------------------------------
	}
	//------
	return  (diff * diffColor + spec*specColor);// * luminosity;
}{CODE}

!!!Outer cone diffuse explained
Why 
{CODE(wrap="1", colors="c++")}dotNL * (1-(spotLightParams.x - dotPLd)/(spotLightParams.x - spotLightParams.y));{CODE}
You ask?
We need to know where the pixel is between the inner and outer cones, but in a relative value (range of 0 to 1).
Therefore, we must first do this:
{CODE(wrap="1", colors="c++")}spotLightParams.x – dotPLd{CODE}

This will give us the absolute difference between the dotPLd (the orthogonal to pixelToLight) and the inner cone limit.

In order to receive a relative value, we divide it by the absolute difference between the inner cone limit and the outer cone limit:
{CODE(wrap="1", colors="c++")}(spotLightParams.x - dotPLd)/(spotLightParams.x - spotLightParams.y){CODE}

The problem is that the value is inversed (closest will equal 0) so we need to one minus it:
{CODE(wrap="1", colors="c++")}(1-(spotLightParams.x - dotPLd)/(spotLightParams.x - spotLightParams.y)){CODE}

And finally, we need to address the angle towards the source of the light (dotNL):
{CODE(wrap="1", colors="c++")}dotNL * (1-(spotLightParams.x - dotPLd)/(spotLightParams.x - spotLightParams.y)){CODE}

!!Differentiating light type
When you ask for light data from OGRE material script, you will receive all lights depending on distance, therefore, you don’t know if it’s a point light, a directional light or a spotlight.

In order to address all three in one shader, we need to know how to differ them; we need three things from the material script:
* Light_position_array
* light_direction_array
* spotlight_params

Each type of light has a different signature (a different combination of values):
__Point light:__
* light_position 	: (pos.x, pos.y, pos.z, 1)
* light_direction	: not relevant
* spotlight_params	: (1, 0, 0, 1)

__Directional light:__
* light_position 	: (-dir.x, -dir.y,-dir.z, 0)
* light_direction	: not relevant
* spotlight_params	: (1, 0, 0, 1)

__Spotlight:__
* light_position 	: (pos.x, pos.y, pos.z, 1)
* light_direction	: (dir.x, dir.y, dir.z )
* spotlight_params	: (cos(innerAngle/2) , cos(outerAngle/2) , falloff, 1)

!!!“Why is light_direction not relevant in directional light?”
Because it is deprecated for greater flexibility. Instead, its inverse direction is sent in light_position (because we usually use the pixel to light)

The only reason it’s not deprecated in spotlights is that they have both direction and position.

!!Completionist
As a closing note, lets write a shader that an handle multiple lights of any type!
Try and write it on your own, and don’t give up – it will take a while.
Tips: 
* __efficiency is key__ : try and avoid unnecessary work
* __Delegate__: write sub programs to do the main bulk of the code separately, and leave the work flow in the main program.
* __Shader model 3__: if the program gets really branched and the work flow isn’t constant, you might have to use ps_3_0 and vs_3_0. 
* __Work slowly__: do each task at a time; expand on need – make sure what you already wrote works.

!!!My solution
I wrote my own version for this; it’s a bit more mature than what we wrote up to this point, but all of its elements should be familiar to you. Just follow the work flow line by line (starting with the main programs) and you’ll get it.

A few words about it:
* all positions and directions in object space, saving time (spent on transforming before calculating directional vector) and also helps result accuracy.
* Use of shader model 3.
* Use of inout for the sub programs to make them as seamless with the main program as possible.
* use of __light_distance_object_space__ instead of manual calculation (trading attenuation accuracy for speed)
* its currently set to receive 3 light and use 3 lights. you can change the number of received lights and set a different the value for the lightCount #define without any slowdown - the program sets the size of the array according to 'lightCount'  - all additional data will be discarded; as long as the 'lightCount' is lower than the number of lights you are requesting there will no error ("array index out of bounds").
* it is also set to skip all computations when receiving an empty light (if the object is affected by less than 'lightCount' amount of lights OGRE sends (0,0,0,0) in most parameters for additional data)
* It’s elegant, and I really like staring and the code.

__material:__
{CODE(wrap="1", colors="c++")}vertex_program lightMasterVS hlsl
{
	source lightmaster.hlsl
	target vs_3_0
	entry_point multiTypeLightVS
	preprocessor_defines lightCount=3
	default_params
	{
		param_named_auto 	worldViewProj_m 	worldviewproj_matrix
		param_named_auto 	world_m 		world_matrix
		 param_named_auto	cameraPos		camera_position_object_space
		param_named_auto	lightPoses		light_position_object_space_array	3
	}
}

fragment_program lightMasterPS hlsl
{
	source lightmaster.hlsl
	target ps_3_0
	entry_point multiTypeLightPS
	preprocessor_defines lightCount=3
	default_params
	{
		param_named_auto	lightPoses		light_position_object_space_array	3
		param_named_auto	lightDirs		light_direction_object_space_array 	3
		param_named_auto	SLParamsArray		spotlight_params_array			3
		param_named_auto	diffColors		light_diffuse_colour_array		3
		param_named_auto	specColors		light_specular_colour_array		3
		param_named_auto	lightAttens		light_attenuation_array			3
		param_named_auto	lightDists		light_distance_object_space_array	3
		param_named_auto	ambientColor		ambient_light_colour
		param_named specShine float 50
	}
}

material lightMasterA
{
	technique
	{
		pass
		{
			vertex_program_ref lightMasterVS {}
			fragment_program_ref lightMasterPS {}
		}
	}
}
{CODE}

__the HLSL file:__
{CODE(wrap="1", colors="c++")}
struct vertexIn
{
	float4 position : POSITION;
	float3 normal	: NORMAL0;
};

struct vertexOut
{
	float4 position  		: POSITION;
	float3 normal	 		: TEXCOORD0;
	float3 viewDir   		: TEXCOORD1;
	float3 pixelToLight[lightCount]	: TEXCOORD2;
};

float attenuation(float lightDist, float4 lightAtten)
{
	float luminosity = 0;
	luminosity= 1 / ( lightAtten.y + lightAtten.z*lightDist + lightAtten.w*pow(lightDist,2));
	return luminosity;
}

//point light sub pixel program
//--------------------------------------------------------------------------------
void pointLightPS(	float3 iNormal, 
			float3 iViewDir, 
			float3 iPixelToLight,
			float4 diffColor,
			float4 specColor,
			float4 lightAtten,
			float  lightDist,
			float  specShine,
		inout	float4 diff,
		inout	float4 spec
		)
{
	if( lightAtten.x > lightDist)
	{
		float 	luminosity	= attenuation(lightDist, lightAtten);
		float 	dotNL 		= dot ( iPixelToLight, iNormal );
		float3 	halfAng 	= normalize(iViewDir + iPixelToLight);
		float 	dotNH		= dot(iNormal, halfAng);
		
		spec	+= pow(saturate(dotNH),specShine) * specColor * luminosity;
		diff 	+= (saturate(dotNL)) * diffColor * luminosity;
	}
}
//--------------------------------------------------------------------------------

//directional light  sub pixel program
//--------------------------------------------------------------------------------
void directionalPS(	float3 iNormal, 
			float3 iViewDir,
			float3 iPixelToLight,
			float4 diffColor,
			float4 specColor,
			float  specShine,
		inout	float4 diff,
		inout	float4 spec
		)
{
	float 	dotNL 	= dot(iPixelToLight, iNormal);
	float3 	halfAng = normalize(iViewDir + iPixelToLight);
	float 	dotNH	= dot(iNormal, halfAng);
	
	diff 	+= saturate(dotNL) * diffColor;
	spec	+= pow(saturate(dotNH),specShine) * specColor;
}
//--------------------------------------------------------------------------------

//spotlight sub pixel program
//--------------------------------------------------------------------------------
void spotlightPS(	float3 iNormal, 
			float3 iViewDir, 
			float3 iPixelToLight,
			float3 iSpotLightDir,
			float4 spotLightParams,
			float4 diffColor,
			float4 specColor,
			float4 lightAtten,
			float  lightDist,
			float  specShine,
		inout	float4 diff,
		inout	float4 spec
		)
{
	if( lightAtten.x > lightDist)
	{
		float 	dotPLd		= dot(-iPixelToLight, iSpotLightDir);
		float 	dotNL	 	= dot(iPixelToLight, iNormal);
		float 	luminosity 	= attenuation(lightDist, lightAtten);
		
		if(dotNL > 0)
		{	
			//diffuse
			//------------------------------
			if	( dotPLd > spotLightParams.y )
				diff += dotNL * (1-(spotLightParams.x - dotPLd)/(spotLightParams.x - spotLightParams.y)) * diffColor * luminosity;
			else
			if	( dotPLd > spotLightParams.x )
				diff += dotNL * diffColor * luminosity;
				
			//------------------------------
			// specular
			//------------------------------
			if (dotPLd > 0) 
			{
				float3 	halfAng = normalize(iViewDir + iPixelToLight);
				float 	dotNH	= dot(iNormal, halfAng);
				spec 	+= pow(saturate(dotNH),specShine) * specColor * luminosity;
			}
			//------------------------------
		}
	}
}
//--------------------------------------------------------------------------------

// main vertex program
//--------------------------------------------------------------------------------
vertexOut multiTypeLightVS(		vertexIn 	input,
				uniform	float4x4 	worldViewProj_m,
				uniform	float4x4 	world_m,
				uniform	float4 		cameraPos,
				uniform	float4 		lightPoses[lightCount]
			)
{
	vertexOut output= (vertexOut)0;
	
	output.position = mul(worldViewProj_m, input.position);
	output.normal 	= mul(world_m, input.normal);
	output.viewDir	= cameraPos - input.position;
	
	for(int i = 0; i < lightCount; i++)
	{	
		// if the light is not empty
		if( !(lightPoses[i].x==0 && lightPoses[i].y==0 && lightPoses[i].z==0 && lightPoses[i].w==0) )
			output.pixelToLight[i] = lightPoses[i] - input.position;
	}
	return output;
}
//--------------------------------------------------------------------------------

//main pixel program
//--------------------------------------------------------------------------------
float4 multiTypeLightPS(	vertexOut 	input,
			uniform float4		lightPoses[lightCount],
			uniform float4		SLParamsArray[lightCount],
			uniform float3		lightDirs[lightCount],
			uniform	float4		diffColors[lightCount],
			uniform	float4 		specColors[lightCount],
			uniform float4		lightAttens[lightCount],
			uniform float4		lightDists[lightCount],
			uniform	float4		ambientColor,
			uniform float		specShine
			) : COLOR
{
	input.viewDir	= normalize( input.viewDir );
	input.normal 	= normalize( input.normal );
	float4 diff 	= float4(0, 0, 0, 0);
	float4 spec 	= float4(0, 0, 0, 0);

	for(int i = 0; i<lightCount ; i++)
	{
		input.pixelToLight[i]	= normalize( input.pixelToLight[i] );
		// if the light position data is (0,0,0,0),the light is empty
		if( !(lightPoses[i].x==0 && lightPoses[i].y==0 && lightPoses[i].z==0 && lightPoses[i].w==0) )
		{
			if(lightPoses[i].w==0)
			{
				// if the light is not empty and the w component is 0, we have a directional
				directionalPS(	input.normal, 
						input.viewDir, 
						lightPoses[i].xyz,
						diffColors[i],
						specColors[i],
						specShine,
						diff,
						spec);
			}
			else
			if ( SLParamsArray[i].x==1 && SLParamsArray[i].y==0 && SLParamsArray[i].z==0 && SLParamsArray[i].w==1 )
			{
				// if the light is not directional and the spot light params is (1,0,0,1), we have a point light
				pointLightPS(	input.normal, 
						input.viewDir, 
						input.pixelToLight[i],
						diffColors[i],
						specColors[i],
						lightAttens[i],
						lightDists[i],
						specShine,
						diff,
						spec);
			}
			else
			{
				// otherwise, we have a spotlight
				spotlightPS(	input.normal, 
						input.viewDir, 
						input.pixelToLight[i],
						normalize(lightDirs[i]),
						SLParamsArray[i],
						diffColors[i],
						specColors[i],
						lightAttens[i],
						lightDists[i],
						specShine,
						diff,
						spec);
			}
		}
	}
	return ambientColor + diff + spec ;
}
//--------------------------------------------------------------------------------
{CODE}

My shader in action; red directional, blue point light from below and white spotlight from the left.
{IMG(src="display1868", height="461", width="649" )}{IMG}
        

History

Information Version
Sat 10 of Jul, 2010 10:37 GMT-0000 jajdoo 45
Sat 10 of Jul, 2010 10:36 GMT-0000 jajdoo 44
Sat 10 of Jul, 2010 09:56 GMT-0000 jajdoo 43
Sun 04 of Jul, 2010 17:35 GMT-0000 jajdoo 42
Sun 04 of Jul, 2010 17:34 GMT-0000 jajdoo 41
Sun 04 of Jul, 2010 03:37 GMT-0000 jajdoo 40
Sun 04 of Jul, 2010 03:36 GMT-0000 jajdoo 39
Sun 04 of Jul, 2010 03:33 GMT-0000 jajdoo 38
Sat 03 of Jul, 2010 19:10 GMT-0000 jajdoo 37
Sat 03 of Jul, 2010 19:09 GMT-0000 jajdoo 36
Sat 03 of Jul, 2010 15:41 GMT-0000 jajdoo 35
Sat 03 of Jul, 2010 07:32 GMT-0000 jajdoo 34
Sat 03 of Jul, 2010 07:31 GMT-0000 jajdoo 33
Sat 03 of Jul, 2010 07:08 GMT-0000 jajdoo 32
Sat 03 of Jul, 2010 07:08 GMT-0000 jajdoo 31
Fri 02 of Jul, 2010 17:11 GMT-0000 jajdoo 30
Thu 01 of Jul, 2010 15:27 GMT-0000 jajdoo 29
Thu 01 of Jul, 2010 12:21 GMT-0000 jajdoo 28
Thu 01 of Jul, 2010 06:03 GMT-0000 jajdoo 27
Thu 01 of Jul, 2010 06:02 GMT-0000 jajdoo 26
Wed 30 of Jun, 2010 19:23 GMT-0000 jajdoo 25
Wed 30 of Jun, 2010 19:11 GMT-0000 jajdoo 24
Wed 30 of Jun, 2010 19:10 GMT-0000 jajdoo 23
Wed 30 of Jun, 2010 16:23 GMT-0000 jajdoo 22
Wed 30 of Jun, 2010 16:02 GMT-0000 jajdoo 21
  • «
  • 1 (current)
  • 2