20.光源Light类
首先参考一下DirectX中的光源结构:
typedef struct D3DLIGHT9 {
D3DLIGHTTYPE Type; //光源类型
D3DCOLORVALUE Diffuse; //漫反射颜色
D3DCOLORVALUE Specular; //镜面高光颜色
D3DCOLORVALUE Ambient; //环境光颜色
D3DVECTOR Position; //光源位置,对单向光无意义
D3DVECTOR Direction; //光线方向,对点光源无意义
float Range; //光源有效范围,对单向光无意义
float Falloff; //聚光灯内外光锥之间的光强衰减
float Attenuation0; //随距离衰减的常数因子
float Attenuation1; //随距离衰减的线性因子
float Attenuation2; //随距离衰减的二次因子
float Theta; //内光锥弧度
float Phi; //外光锥弧度
} D3DLIGHT9, *LPD3DLIGHT;
其中D3DLIGHTTYPE是一个枚举:
typedef enum D3DLIGHTTYPE
{
D3DLIGHT_POINT = 1, //点光源
D3DLIGHT_SPOT = 2, //聚光灯
D3DLIGHT_DIRECTIONAL = 3, //单向光
D3DLIGHT_FORCE_DWORD = 0x7fffffff, //强制将枚举编译为32bit,不会使用这个值
} D3DLIGHTTYPE, *LPD3DLIGHTTYPE;
我的Light类就是想自己构建类似于DirectX中的D3DLIGHT9结构,主要代码参考了Shader系列4:材质和光源中的PointLight类,只不过它只实现了点光源,我还需要在一个类中实现单向光、点光源和聚光灯。代码如下:
namespace StunEngine.Rendering
{
///
/// 光源对象。一个光源包含颜色,光源类型,光线方向,光照范围和位置的信息。
///
public class Light
{
///
/// 光源是否可用
///
private bool enabled;
///
/// 光线颜色
///
private Vector3 color;
///
/// 光线方向
///
private Vector3 direction;
///
/// 光源位置
///
private Vector3 position;
///
/// 聚光灯光源属性的数据集合,包括range, fallof, theta和phi
///
public Vector4 SpotData;
///
/// 光源类型枚举,分为单向光、点光源、聚光灯。
///
private LightType lightType;
// 为了减少查询字符串所用的时间,位置参数存储在一个参数实例中,其他参数更新频率小得多。
private EffectParameter positionParameter;
private EffectParameter instanceParameter;
///
/// 创建一个单向光源。
///
/// 引擎
/// 颜色
/// 光线方向
public Light(StunXnaGE engine,Vector3 color, Vector3 direction) : this(engine,LightType.Directional, color, -direction, direction, 0, 0, 0, 0)
{
if (direction == Vector3.Zero)
throw new System.ArgumentException("单向光必须要有一个方向");
}
///
/// 创建一个点光源。
///
/// 引擎
/// 颜色
/// 方向
/// 光照范围
/// 随距离变化的衰减因子,范围为0到无穷大,0表示不衰减,1表示线性减小
public Light(StunXnaGE engine, Vector3 color, Vector3 position, float range, float falloff) : this(engine,LightType.Point, color, position, Vector3 .Zero , range, falloff, 0, 0) { }
///
/// 创建一个聚光灯光源。
///
/// 引擎
/// 颜色
/// 位置
/// 方向
/// 光照范围
/// 在聚光灯光锥内部顶角和外部顶角之间的光照衰减因子,范围为0到无穷大,0表示不衰减,1表示线性减小。
/// 内光锥弧度,介于0值Phi之内。
/// 外光锥弧度,介于0至PI/2之内。
public Light(StunXnaGE engine,Vector3 color, Vector3 position, Vector3 direction, float range, float falloff, float theta, float phi):this(engine,LightType.Spot, color, position, direction, range, falloff, theta, phi){}
///
/// 创建一个新Light对象。
///
/// 引擎
/// 光源类型
/// 光源颜色
/// 光源位置
/// 光源方向
/// 光照范围
/// 聚光灯光锥内部顶角和外部顶角之间的光照衰减或点光源的距离衰减因子,范围为0到无穷大,0表示不衰减,1表示线性减小。
/// 聚光灯内光锥弧度,介于0值Phi之内。
/// 聚光灯外光锥弧度,介于0至PI/2之内。
public Light(StunXnaGE engine,LightType lightType, Vector3 color, Vector3 position, Vector3 direction, float range, float falloff, float theta, float phi)
{
//一个光源对象绑定到一个定义在BaseEffect中的gLights结构对象,参数的"StructureMembers"属性用来访问结构中的独立字段。
instanceParameter = engine.BaseEffect .Parameters ["gLights"].Elements [SceneManager.TotalLights];
positionParameter = instanceParameter.StructureMembers["position"];
this.lightType = lightType;
instanceParameter.StructureMembers["lightType"].SetValue((float)lightType);
this.Color = color;
this.Position = position;
this.Direction = direction;
this.Enabled = true;
this.SpotData.X = range;
this.SpotData.Y = falloff;
this.SpotData .Z= theta;
this.SpotData.W = phi;
instanceParameter.StructureMembers["spotData"].SetValue(SpotData);
}
///
/// 获取光源类型。
///
public LightType LightType { get { return lightType; } }
///
/// 获取或设置光源是否可用
///
public bool Enabled
{
get { return enabled; }
set
{
enabled = value;
if (enabled)
instanceParameter.StructureMembers["enabled"].SetValue(1.0f);
else
instanceParameter.StructureMembers["enabled"].SetValue(0.0f);
}
}
///
/// 获取或设置光源颜色。
///
public Vector3 Color
{
get
{
return color;
}
set
{
color = value;
instanceParameter.StructureMembers["color"].SetValue(color);
}
}
///
/// 获取或设置光源位置,单向光的方向等于位置的反方向:direction = -position。
///
public Vector3 Position
{
get
{
return position;
}
set
{
position = value;
this.positionParameter.SetValue(position);
if (lightType == LightType.Directional)
{
direction = -position;
this.instanceParameter.StructureMembers["direction"].SetValue(direction);
}
}
}
///
/// 获取或设置光线方向,单向光的位置等于光线的反方向:position = -direction。
///
public Vector3 Direction
{
get
{
return direction;
}
set
{
direction = value;
instanceParameter.StructureMembers["direction"].SetValue(direction);
if (lightType == LightType.Directional)
{
Position = -direction;
}
}
}
///
/// 获取或设置光源作用距离。这个参数对单向光不起作用。
///
public float Range
{
get
{
return SpotData.X;
}
set
{
SpotData.X= value;
instanceParameter.StructureMembers["spotData"].SetValue(SpotData);
}
}
///
/// 获取或设置聚光灯光锥内部顶角和外部顶角之间的光照衰减或点光源的距离衰减因子
///
public float FallOff
{
get
{
return SpotData.Y;
}
set
{
SpotData.Y = value;
instanceParameter.StructureMembers["spotData"].SetValue(SpotData);
}
}
///
/// 获取或设置聚光灯内光锥弧度,介于0值Phi之内。
///
public float Theta
{
get
{
return SpotData .Z ;
}
set
{
SpotData.Z = value;
instanceParameter.StructureMembers["spotData"].SetValue(SpotData);
}
}
///
/// 获取或设置聚光灯外光锥弧度,介于0至PI/2之内。
///
public float Phi
{
get
{
return SpotData.W;
}
set
{
SpotData.W = value;
instanceParameter.StructureMembers["spotData"].SetValue(SpotData);
}
}
}
///
/// 光源类型枚举。
///
public enum LightType
{
///
/// 最常用的光源。类似于太阳光,因为光源在无穷远处,所以场景中的所有物体都会被这个光源照亮。
///
Directional,
///
/// 向四周发射光线的点光源,Direction参数对这个光源无意义。
///
Point,
///
/// 聚光灯。
///
Spot,
}
}
说明1
Light类并没有包含DirectX的 D3DLIGHT9结构中的Diffuse,Specular,Ambient,我将它们作为一个color属性,对应Diffuse,而Specular,Ambient是在材质中定义的,这样可以减少计算量;Light类也没有包含Attenuation0,Attenuation1,Attenuation2,会在effect文件(将在下一个教程中讨论)中用简化的方法计算距离衰减因子,也能大大减少计算量。
说明2
Enabled属性是一个布尔类型,但是对应于Effect文件中的enabled参数却是浮点数类型。这是因为如果将enabled参数设置为bool类型,shader会报错:maximum boolean register index exceeded - Try reducing number of constant branches,即已经超过了布尔寄存器的数量,需要减少判断分支,这主要是因为shader(将在下一个教程中讨论)中遍历光源计算衰减值的代码的循环判断语句过多,所以将shader中enabled设置为float避免这个错误。
说明3
GPU是以四维向量为基本单位进行计算的,虽然HLSL的基本数据类型定义了float,float2,float3,int和bool等非float4向量的数据类型,但实际上最后通常都会被编译器转换成float4向量。因此我将range,falloff,theta和phi放在了一个Vector4类型的spotData变量中的四个分量中,这样可以减少数据量。
管理光源
就像使用SceneManager管理Scene对象、UIManager管理UISceneNode……那样,本来准备创建一个LightManager管理Light对象,现在发现并无必要,管理Light的工作交给Scene就可以了,所以在Scene类中添加光源集合:
////// 光源集合 /// private Listlights = new List (SceneManager.SCENE_MAX_LIGHTS);
并添加以下方法添加或移除场景中的光源:
////// 在场景中添加一个光源。 /// /// public void AddLight(Light light) { //更新光源数量 SceneManager.TotalLights += 1; if (SceneManager.TotalLights >= SceneManager.SCENE_MAX_LIGHTS) throw new IndexOutOfRangeException(string.Format("无法添加大于{0}个光源", SceneManager.SCENE_MAX_LIGHTS)); //在集合中添加光源 lights.Add(light); // 更新effect中的表示光源数量的gTotalLights参数 engine.BaseEffect.Parameters["gTotalLights"].SetValue(SceneManager.TotalLights); } ////// 从场景中移除最后一个光源。 /// public void RemoveLastLight() { RemoveLightAt(lights.Count - 1); } ////// 根据光源索引从场景中移除一个光源。 /// /// 光源索引 private void RemoveLightAt(int index) { if (index >= 0) { //更新光源数量 SceneManager.TotalLights -= 1; if (lights[index].Enabled) SceneManager.EnabledLights -= 1; //移除集合中的光源 lights.RemoveAt(index); // 更新effect中的表示光源数量的gTotalLights参数 engine.BaseEffect.Parameters["gTotalLights"].SetValue(SceneManager.TotalLights); } }
但是从以上的代码中可以看到,只能移除集合中的最后一个光源。比如场景中有三个光源,我想移除第一个光源无法实现,这是因为移除C#代码中lights集合的一个元素并不难,但你必须将相应的修改施加到effect中的gLights结构体中,这个我还没有想出合适的方法,不过移除前面的光源好像只有在特殊场合才会这样做。通常需要动态添加移除光源的一个例子是发射一枚火箭,我要实时添加一个点光源照亮周围,在火箭爆炸后移除这个光源,此时的光源是最后一个光源,上述代码还是可以实现的。
文件下载(已下载 1569 次)发布时间:2010/4/20 下午12:25:55 阅读次数:7349
