|
 | Multiple lights | |  |
| Poster | : BerserkerSwe | | Posts | : 2 | | Country | : Sweden | | City | : Göteborg |
| | | | Posted by BerserkerSwe on 04/02/2010 at 07:07:00
| | <span><h1>--- Intro</h1></span>
Hi, im a longtime reader, but this is my first post. Excuse me if my english is bad sometimes, im from Sweden.
I have been playing around with most of the 3D-tutorials and more specifically with the Terrain. I did a big Exam-job about procedurelly generate heightmaps, but wreally, all i wanted was to have an opportunity to learn XNA. So i have managed to make a pretty cool project wich generates (seemingly) random terrain.
And this project i've been working on from time to time during my spare time, making it nicer and nicer. My latest addition was the moving water.
Anyhow...
I wanted to add support for multiple lights. Following a tutorial wich can be found <a href="http://creators.xna.com/en-us/sample/shader_series5">here</a>.
I wanted to have this functionality in my XNA Terrain and this is how i solved it:
<span><h1>--- Problem</h1></span>
The problem was that i didnt want my tarrain, player, water, trees, building... etc, to have:
| DrawLightMap(Vector3 camPos, Matrix worldMatrix, Matrix ViewMatrix, Matrix ProjectionMatrix); |
The code given by the tutorial has shared shader-variables so if Shader B includes Shader A, and shader A defines shared variables then you can manipulate them via the B shader, in the normal fashion | B.Parameters["SomeSharedVariable"].SetValue(SomeValue); |
I wanted a class called LightManager to update all lights, if they move or change color or whatever and then change data in the included shader (A). Now, shader A is not wreally a shader it's a .inc-file. The content pipeline does not recognize it as a shader either.
I thought about making a shader for my lightmanager, the shader would only define the shared variables. But this is problematic/ugly to do for a number of reasons:
1. The LightManager class would have to be a DrawableGameComponent so that it can load the shader via LoadContent.
2. In order for the shader to be recognized as one it has to define a vertexshader (rasterizer), pixelshader and a technique to utilize them.
<span><h1>--- Solution</h1></span>
One file Light.inc defines the following NOT SHARED variables and functions (you will have to write them yourselfs because their not mine i may not be allowed to redistribute them):
struct Light
{
float4 diffuseColor;
float diffuseIntensity;
float specularIntensity;
float4 specularColor;
float4 position;
float falloff;
float range;
};
uniform Light lights[12];
//They have default-values so you don't HAVE to change them
uniform extern float4 xLight_Ambient_Color = float4(0.9,0.9,0.9,0.9);
uniform extern int xLight_NumLightsPerPass = 1;
uniform extern float xLight_Ambient_Power = 0.9;
uniform extern float xSelfShadowPower = 0.82345;
uniform extern float xGlobalSpecularPower = 1.0;
float4 CalculateSingleLight(Light light, float3 worldPosition, float3 worldNormal, float3 inCamPos){/*code*/}
float4 CalcMultipleLight(float3 inWorldPosition, float3 inWorldNormal, float3 inCamPos){/*code*/}
|
Now since that file (Lights.inc) is included in every other shader wich needs support of multiple lights they are atleast defined in one place, but still implemented in many places. This still doesen't solve the problem though. Sure all shaders use functions in the Lights.inc and will therefore rely on variables (lights in an array) to be set somewhere (LightManager), but rather than setting an array containing lights for all shaders i would like to only set (a shared array) once.
Also, i dont want to restrict it to a certain number of lights, it would be slow to calculate 500lights but its not up to the shader to deside, in my oppinion.
Anyways, heres my working solution.
<strong>LightManager</strong>
/// <summary>
/// Manages lights.
/// Updates them and sets appropiate variables on all shaders who has
/// given interest in knowing.
///
/// This is a singelton, meaning there can only be one instance of /// this class.
///
/// Usage:
/// Be sure your object has an Effect and that it includes Light.inc, thats where the necessary light-variables and
/// functions are. Hook it up to the LightManager once via LightManager.GetInstance().HookUpShader( XXX );
/// In the pixelshader of youre effect add something similar:
/// float4 Light = CalcMultipleLight(wPOSITION, wNORMAL,
/// CAMERAPOSITION);
/// </summary>
public class LightManager
{
private static LightManager mInstance = null;
/// <summary>
///List containing all lights.
/// </summary>
private List<PointLight> mAllLights;
/// <summary>
/// List containing all interested effects.
/// </summary>
private List<Effect> mHookedEffects;
/// <summary>
/// The ambient light.
/// </summary>
private Color mAmbientLightColor;
private float mAmbientLightPower;
/// <summary>
/// Use this function to hook up a shader to the LightManager so that it's lightcalculation
/// can take multiple lights into consideration.
/// TODO: Needs to throw exception if effectparameters can't be found.
/// </summary>
/// <param name="inEffect">
/// A effect that wants to use multiple lights.
/// The effect needs to include Light.inc
/// </param>
public void HookUpShader(ref Effect inEffect)
{
inEffect.Parameters["xLight_Ambient_Color"].SetValue(mAmbientLightColor.ToVector4());
inEffect.Parameters["xLight_Ambient_Power"].SetValue(mAmbientLightPower);
inEffect.Parameters["xLight_NumLightsPerPass"].SetValue(mAllLights == null ? 0 : mAllLights.Count);
inEffect.CommitChanges();
mHookedEffects.Add(inEffect);
}
/// <summary>
/// Private ctor.
/// </summary>
private LightManager()
{
mAllLights = new List<PointLight>();
mHookedEffects = new List<Effect>();
mAmbientLightColor = Color.Yellow;
mAmbientLightPower = 0.9f;
}
/// <summary>
/// Since this class is of the Singelton-type only one instance may exist.
/// This function returns that one instance. If no instance has been created that will be done.
/// </summary>
/// <returns>
/// The one and only instance of this class.
/// </returns>
public static LightManager GetInstance()
{
if (mInstance == null)
mInstance = new LightManager();
return mInstance;
}
/// <summary>
/// Updates all the registered lights by calling their Update()-function.
/// Then it iterates through all registered effects and change their variables accordingly.
/// TODO: the shader has a limit of 12 lights, this has to be dynamic and some sort
/// solution needs to be done for when theres more than then limited number of lights,
/// maybe not all lights need to be taken in to consideration (Octree ?).
/// </summary>
/// <param name="inTime">
/// Gametime passed since last call.
/// </param>
public void Update(GameTime inTime)
{
if (mHookedEffects == null || mHookedEffects.Count == 0)
return;
if (mAllLights == null || mAllLights.Count == 0)
return;
int i = 0;
foreach (PointLight PL in mAllLights)
{
PL.Update(inTime);
foreach (Effect HE in mHookedEffects)
{
if (PL.NeedToUpdateShader())
{
//EffectParameter EP = HE.Parameters[string.Format("lights[{0}]", i)];
EffectParameter EP = HE.Parameters["lights"];
EffectParameter TargetLight = EP.Elements[i];
PL.UpdateLight(TargetLight);
}
}
i++;
if (i > 12)//This is hardcoded and ugly
break;
}
}
#region Add/Remove
/// <summary>
/// Adds a light to the list of lights.
/// </summary>
/// <param name="inLight">
/// Light to add.
/// </param>
public void Add(PointLight inLight)
{
mAllLights.Add(inLight);
}
/// <summary>
/// Removes a specific light from the list of lights.
/// </summary>
/// <param name="inLight">
/// Light to be removed (given a reference).
/// </param>
public void Remove(PointLight inLight)
{
mAllLights.Remove(inLight);
}
/// <summary>
/// Removes a specific light from the list of lights.
/// </summary>
/// <param name="inLight">
/// Light to be removed (given an index).
/// </param>
public void Remove(int inAtIndex)
{
bool IllegalIndex = ((inAtIndex < 0) || (inAtIndex > mAllLights.Count));
string ErrorMsg = string.Format("Error! Index {0} is illegal!", inAtIndex);
System.Diagnostics.Trace.Assert(!IllegalIndex, ErrorMsg);
this.Remove(mAllLights[inAtIndex]);
}
/// <summary>
/// Removes all registered lights from the list of lights.
/// </summary>
public void RemoveAll()
{
mAllLights.Clear();
}
#endregion
}
|
<strong>PointLight (example, abstract)</strong>
public class PointLight
{
#region Variables
protected float rangeValue;
protected float falloffValue;
private float diffuseIntensity = 0.56f;
private float specularIntensity = 0.361f;
protected bool hasMoved = false;
protected Color diffuseColor;
protected Color specularColor;
protected Vector4 positionValue;
protected Vector4 lastPositionValue;
protected bool needToUpdateShader = true;
#endregion
#region CTORS
public PointLight(Vector4 initialPosition)
: this(initialPosition, Color.White, Color.Yellow){}
public PointLight(Vector4 initialPosition, Color inSpecularColor, Color inDiffuseColor)
:this(initialPosition, inSpecularColor, inDiffuseColor, 30.0f){}
public PointLight(Vector4 initialPosition, Color inSpecularColor, Color inDiffuseColor, float inRange)
: this(initialPosition, inSpecularColor, inDiffuseColor, inRange, 0.2f){}
public PointLight(Vector4 initialPosition, Color inSpecularColor, Color inDiffuseColor, float inRange, float inFalloff)
{
positionValue = initialPosition;
specularColor = inSpecularColor;
diffuseColor = inDiffuseColor;
rangeValue = inRange;
falloffValue = inFalloff;
///NOTE: This makes an unnecessary dependency, might want to add the light
/// manually after instanciation.
LightManager.GetInstance().Add(this);
}
#endregion
/// <summary>
/// Updates the light.
/// In it's basic form this does nothing.
/// It's up to derived classes to implement updatelogic.
/// </summary>
/// <param name="inTime"></param>
public virtual void Update(GameTime inTime)
{
if (positionValue != lastPositionValue)
hasMoved = true;
lastPositionValue = positionValue;
return;
}
#region Properties
public Vector4 Position
{
get { return positionValue; }
set { positionValue = value; needToUpdateShader = true; }
}
public Color SpecularColor
{
get { return specularColor; }
set { specularColor = value; needToUpdateShader = true; }
}
public Color DiffuseColor
{
set { diffuseColor = value; needToUpdateShader = true; }
get { return diffuseColor; }
}
public float DiffuseIntensity
{
get { return diffuseIntensity; }
set
{
diffuseIntensity = MathHelper.Clamp(value, 0.0f, 1.0f);
needToUpdateShader = true;
}
}
public float SpecularIntensity
{
get { return specularIntensity; }
set
{
specularIntensity = MathHelper.Clamp(value, 0.0f, 1.0f);
needToUpdateShader = true;
}
}
public float Range
{
get { return rangeValue; }
set { rangeValue = Math.Max(10.0f, value); needToUpdateShader = true; }
}
public float Falloff
{
get { return falloffValue; }
set { falloffValue = value; needToUpdateShader = true; }
}
#endregion
/// <summary>
/// Wheter changes has happend to the light wich is crucial for depending shaders.
/// Needs to be overridden if some other type of light moves or similar.
/// </summary>
/// <returns>
/// bool, true if shaders need to be updated, false otherwise.
/// </returns>
public virtual bool NeedToUpdateShader()
{
return needToUpdateShader || hasMoved;
}
/// <summary>
/// Any given effect with the righ variables defined can call this function
/// to get their values corrected after a light has updated itself.
/// This is intended to be called from the LightManager.
/// </summary>
/// <param name="lightParameter">
/// Once instance of the struct "Light" defined in Light.inc.
/// </param>
public void UpdateLight(EffectParameter lightParameter)
{
lightParameter.StructureMembers["position"].SetValue(positionValue);
lightParameter.StructureMembers["falloff"].SetValue(falloffValue);
lightParameter.StructureMembers["range"].SetValue(rangeValue);
lightParameter.StructureMembers["diffuseColor"].SetValue(diffuseColor.ToVector4());
lightParameter.StructureMembers["specularColor"].SetValue(specularColor.ToVector4());
lightParameter.StructureMembers["diffuseIntensity"].SetValue(DiffuseIntensity);
lightParameter.StructureMembers["specularIntensity"].SetValue(SpecularIntensity);
needToUpdateShader = false;
}
}
|
| |
|
| | | | | | Poster | : BerserkerSwe | | Posts | : 2 | | Country | : Sweden | | City | : Göteborg |
| | | | Posted by BerserkerSwe on 04/02/2010 at 07:10:20
| | So, i see the html and intendition of the text is not very good, i apalogise for that.
Anyways, as stated, that code works, but i dont like it. To make my question short: "What is the proper way of managing shared shadervariables such as lightsources ?".
If you don't have an answer please give feedback on my code anyways. | |
|
|
 | | |  |
|
Useronline Insert Failed >
|
|
If you appreciate the amount of time I spend creating and updating these pages, feel free to donate -- any amount is welcome !
|
- Website design & DirectX code : Riemer Grootjans - ©2006 Riemer Grootjans
|
|