Source available on GitHub.

Capsule Light Source

This is probably the silliest of the things I'm putting up here, but maybe someone will find it helpful. When learning about shaders for different kinds of light sources, all you really see are point lights, directional lights, and cone lights. I had need of a capsule-shaped light for my lasers, like the one on the floor here:



That is indeed a light that has a 3D capsule shape, not just a billboard on the ground. You can see it illuminating Zomborg's chest as it wobbles.



Achieving this effect was a pretty fun challenge. This is the kind of situation where you can actually learn new math for the purpose of applying it, which is a whole lot more fun than memorizing some theory you'll never see yourself using practically *cough* all of calculus 2 *cough*. The idea for this light is that I made a rectangle and rounded the edges off. To make a rectangle light, I used this reference to figure out a point's distance from a line. You pass in the origin and the direction of the laser to get the line, and get the world-space location of the pixel you are rendering to find the point. Then you find the point on the line closest to your pixel with this formula:



distance = -[(x1 - x0) dot (x2 - x1)] / length(x2 - x1)^2


That gives you the lightsource's position for this pixel. From here, just calculate the distance between your pixel and that point to find how strong your light is there. In case it isn't clicking for you, think about it this way: this strategy is very similar to how you would make a point light. You find your pixel's distance from the light source, and lessen the light the greater that distance (attenuation). In our case, the light source is the line that represents the laser, instead of a singular point.



And now we have a rectangle light that extends to infinity. To cap it off, we need to discard the pixel it if it's past the length of the line (the alongLine variable). Lastly, we round the edges by clamping the light source length by 10% off each edge. This keeps the rounded pixels from being discarded, and makes all the pixels at the end receive light from the same point at the end of the line, creating a spherical shape.



If that wall of text was a bit much for you, just try looking at the source code instead. It's not a very long file, you mainly need to understand what purpose the formula serves. Everything after the "Calculate Diffuse Light" comment is the relevant part.



CapsuleLight.fx

The HLSL code for the capsule light. The referenced .fxh files are just includes with common code, they can be downloaded in the GitHub repo.

 
//these files can be found in the full GitHub download
#include "CommonLight.fxh"
#include "Macros.fxh"
#include "ModelLight.fxh"
 
 
float LightWidth       		   _vs(c9)  _ps(c10) _cb(c9);
float Roundedness       	   _vs(c10)  _ps(c11) _cb(c10);
float3 LightOrigin             _vs(c12) _ps(c13) _cb(c12);
float3 LightEndPoint           _vs(c13) _ps(c15) _cb(c13);
 
 
float4 PSCapsuleLight(ModelLightVSOutput input) : COLOR0
{
	input.ScreenPos.xy /= input.ScreenPos.w;
	float2 texCoord = 0.5f * (float2(input.ScreenPos.x,-input.ScreenPos.y) + 1);
 
	float4 normalData = SAMPLE_TEXTURE(NormalMap, texCoord);
	float3 normal = normalData.xyz;//decodeNormals(normalData.xy);
	//float specularPower = normalData.a * 255;
	//float specularIntensity = normalData.z;
 
	float4 colorData = SAMPLE_TEXTURE(ColorMap, texCoord);
 
 
	float depthVal = SAMPLE_TEXTURE(DepthMap, texCoord).r;
	float4 position;
	position.xy = input.ScreenPos.xy;
	position.z = depthVal;
	position.w = 1.0f;
	position = mul(position, InverseViewProjection);
	position /= position.w;
 
 
	//Calculate Diffuse light
	float3 lineDiff = LightEndPoint - LightOrigin;
	//find the point on the light's axis that is closest to this pixel's 3d position
	//reference: http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
	//t = -[(x1 - x0) dot (x2 - x1)] / length(x2 - x1)^2
	float alongLine = -(dot ((LightOrigin - position), lineDiff)) / (lineDiff.x * lineDiff.x + lineDiff.y * lineDiff.y + lineDiff.z * lineDiff.z);
	//round off the edges, making it a capsule shape
	if(alongLine < .1f)
	{
		alongLine = .1f;
	}
	else if(alongLine > .9f)
	{
		alongLine = .9f;
	}
	//calculate light falloff (attenuation)
	float3 lightPos = LightOrigin + lineDiff * alongLine;
	float3 lightVector = lightPos - position;
	float attenuation = saturate(1.0f - length(lightVector)/LightWidth);
	lightVector = normalize(lightVector);
	float light = saturate(dot(normal,lightVector));
 
	light *= attenuation;
 
	//DirectX 9 couldn't handle another possible control flow, so both discard cases were combined here
	//instead of one after calculating the line position and one after calculating light attenuation.
	if(alongLine < 0 || alongLine > 1 || light <= 0)
	{
		discard;
	}
 
	//cel shading
	if (light> threshHigh)
	{
	    light = intensityHigh;
	}
	else if (light > threshMed)
	{
	    light = intensityMed;
	}
	else
	{
	    light = intensityLow;
	}
 
	float3 diffuseColor = light * colorData * Color;
 
	return float4(diffuseColor.rgb, 1);
}
 
TECHNIQUE(CapsuleLight, ModelVSLight, PSCapsuleLight);