第五集 3D中的太阳
3D虚拟世界是尽可能的模拟现实的, 所以在3D中没有光照是不行的. 前面的例子都是在没有光照的环境里直接由计算机给出物体表面不同点的光亮度和颜色的, 这是不现实的模拟.
光的数学模型非常多, 我们只能讲解比较实用的模型, 其他模型大家可参考相关的文献.
5.1 光照明模型
5.1.1 光照明模型的由来
现实世界中, 来自光源或环境的光照射到物体时, 一般会出现的情况为,
(1). 光从物体表面反射.
(2). 如果物体材质透明, 则光线穿过物体形成透射.
(3). 光能被物体吸收 .
容易发现我们能看到(1)和(2)的光.
现实中如果没有光, 我们不能看到任何物体; 而且在有光的照射下, 不同物体的颜色是不同的. 现实中的光是白光, 实际是由不同波长的光组成的(按不同比例组成的光的颜色是不一样的), 我们可以通过光谱分析来得到光的组成. 物体的颜色是由它吸收的光的波长决定的.
海水可以作为理想的物体来表示---它只吸收蓝光, 所以当白光(由不同波长的光组成)照射到海水时, 海水呈现蓝色; 反过来, 用红光或绿光照射海水, 海水是黑的.
由上面对现实的光的分析, 我们要建立光的数学模型, 引入到3D虚拟世界中. 我们从光照射到物体时的情况可以得到以下几点,
(1). 反射光和投射光的强弱决定被照物体的明暗程度.
(2). 被物体吸收后的反射光和投射光的光谱分布决定物体呈现的颜色.
(3). 反射光和投射光的强弱由入射光的强弱决定; 反射光和投射光的光谱分布由入射光的光谱分布和被照物体吸收不同波长的光的程度决定.
就由上面的结论建立的光数学模型计算量非常庞大, 如光谱的采样和光谱到颜色的转换就是大问题. 考虑到计算机中的颜色都由RED-红, GREEN-绿, BLUE-蓝三基础颜色组成, 所以把光的光谱组成简化为红-绿-蓝三色的组成, 这样整个模型就简化了.
简化的模型中, 只要知道光的方向, 红-绿-蓝三色的组成, 还有物体的对红-绿-蓝三色的吸收率和透射率---也就是知道光源的属性和物体表面的材质属性, 我们就可以模拟现实中的光照了.
5.1.2 光照明模型的分类
上面的分析只是在数学的基础上得到的, 实际的光照明模型还结合了光学物理中的定律, 结合了光源, 环境, 物体表面的几何-吸收-反射-辐射等特性.
光照明模型可分为局部光照明模型和整体光照明模型.
我们需要根据实际的情况使用不同的光照明模型.
5.1.2.1 局部光照明模型
局部光照明模型只考虑光源直接照射到物体表面产生的光照效果, 物体基本不透明且各表面的反射率是常数.
局部光照明模型中, 物体表面的反射光分为漫反射光和镜面反射光; 漫反射光可以认为是光穿过物体表面被吸收后, 余下的重新向外各各方向均匀发射的光, 所以在任何方向的漫反射光强度都相同. 镜面反射光由入射光在物体的表面的直接反射, 镜面反射光沿镜面反射主方向最强, 主方向周围逐渐衰减, 形成一定的可观察区域.
优点 : 能真实的模拟现实中光源直接照射在物体上的连续明暗变化, 镜面效果, 物体间的阴影.
缺点 : 无法实现相邻物体之间的色彩映射等光照效果.
5.1.2.2 整体光照明模型
整体光照明模型在局部光照明模型的基础上, 还考虑了物体周围环境对物体的影响, 如镜面上的映象等.
优点 : 比局部光照明模型更精确的模拟现实中的光照效果.
缺点 : 计算复杂度显著提高.
5.1.3 Lambert(朗伯漫)反射模型
朗伯漫余弦定律在高中的光学中有简单的学习, 是理想漫反射物体在点光源照射下的光反射定律. 根据定律, 一个理想漫反射物体表面上反射的漫反射光的强度同入射光和物体表面的法向量之间的余弦成正比,
I = k d * I l * (N . L) = k d * I l * cosa (I)
Light o
L / N /
_/| /|/ -/|
/ | /
/ | /
/ a| /
/-| /
________/|/________
P
I是物体表面被照点P处的漫反射光的光的强度, I l 是点光源的入射光的光强度, k d 是物体表面的漫发射率, a是入射光L同物体表面法向量N之间的夹角.
考虑到物体还可能受环境光的影响, 为简化, 假设环境光是均匀的直线漫反射光, 这样上面的公式(I)变成,
I = k a * I a + k d * I l * (N . L) (II)
I a 是入射的环境光的强度, k a 是物体表面对环境光的漫反射率(0<= k a <=1).
其实(II)公式没有表示出光随距离衰减的效果, 两个完全相同的物体和光源的方向一致, 但距离不同, 它们的反射光的强度相同, 这不符合实际. 一般光在传播中以距离的平方衰减的, 我们要把光的衰减加入公式(II)中,
I = k a * I a + k d * (I l / (D ^ s) ) * (N . L) (III)
D是光源到被照射点的距离, s是衰减指数(0 <= s <= 2), 用来模拟光的距离衰减效果.
当物体被N个点光源照射, 我们可看作N个漫反射光强度的加,
I = k a * I a + (连加号)k d * (I li / ( Di ^ s)) * (N . Li) (i = 1, 2, ... n)(IV)
I li 是第i个点光源的光强度, Di 是第i个点光源到被照射点的距离, Li第i个点光源的光线入射的单位向量.
优点 : 我们把入射光分解为红-绿-蓝三色光强度的组成, 把物体的漫反射率分解 为对红-绿-蓝三色的光强度的漫反射率, 再由(IV)进行求解, 可快速的得到物体表面某点的漫反射光强度.
缺点 : 对接近理想漫反射的物体模拟是可行的, 但对如金属物体的模拟不现实, 模拟的金属物体没有特有的金属光泽, 主要是缺乏对镜面反射的模拟.
5.1.4 Phong模型
金属物体表面的highlight区域其实是镜面反射的结果, 与漫反射不同, 镜面反射光的空间分布有一定的方向性, 只有在可观察区域能看到.
1973年, Phong提出一个用于计算镜面反射光的强度的经验模型, 模型将物体表面的镜面反射区域近似为一个圆锥, 光强度从圆锥中心开始以指数形式衰减,
I s = k s * I l * (V . R)^n = k s * I l * (cosa)^n (V)
k s 是物体表面的镜面反射率(0<= k s <= 1), 通常对同一物体k s 可取常数; I l 是入射光强度; n为镜面高光指数, 用来模拟镜面反射在空间中的会聚程度; a是视线向量V同镜面反射光线向量R之间的夹角; I s 是物体表面投向视线方向的镜面反射光强度.
图5.1
从(V)公式可得, (a). 投向视线方向的镜面反射光强度不仅由入射光的强度决定, 而且也由视线方向决定. (b). n值越大, 镜面反射光的会聚程度越高; n越小, 镜面反射光越趋于发散状态.
将镜面反射光的强度的经验模型和Lambert(朗伯漫)反射模型组合, 就得到Phong模型,
I = k a * I a + (连加号)(I li / ( Di ^ s)) * [k d * (N . Li) + k s * (V . R)^n] (VI)
优点 : 同样, 光强度和物体的漫反射率, 镜面反射率可分解为红-绿-蓝三色进行计算.
缺点 : Phong模型假设物体表面是平坦的平面, 虽然简化了计算, 但对于高度真实感图形绘制还是不够的.
5.1.5 Blinn模型和Cook-Torrance模型
在Phong模型基础上, 加入物体表面材质粗糙不平的特征, 得到了Blinn模型和Cook-Torrance模型. 因为这两个模型的现在在DirectX Graphics不支持, 且计算比较复杂, 我们不进行讲解了, 有兴趣的请参考相关文献.
5.1.6 Whitted模型和辐射度模型
前面讲的模型都是局部光照明模型, 这节简单介绍整体光照明模型.
5.1.6.1 Whitted模型
Whitted模型把物体某点的光强度由光源直接照射物体产生的光强度, 及环境光在理想镜面反射方向和理想透射方向对物体产生的光强度组成, 因为物体为理想镜面反射和理想透射, 现实中不存在, 所以这个模型不实用.
5.1.6.2 辐射度模型
辐射度模型用热辐射工程中的辐射度方法建立起来的模型, 辐射度方法基于热辐射工程中能量传递和守恒理论, 在一封闭环境中的能量经过多次反射后, 形成一种平衡状态, 这时环境中的各物体表面的光分布平衡不在变化, 再选取视点和视线方向就可绘制视线内的物体.
辐射度模型能真实的模拟面光源的光照效果, 其模型比较复杂, 不进行讲解了, 有兴趣的请参考相关文献.
5.2 光线跟踪算法
现代的个人电脑的处理能力显著提高, 在光照明模型中为计算简化做的假设而形成的无法模拟光的反射, 折射和物体颜色的相互映衬的局限性要进行突破, 为达到精致的光照明效果, 出现了光线跟踪算法, 我们简单了解一下其中几种.
5.2.1 层次包围盒技术
层次包围盒技术是加速光线跟踪算法中最基础的. 包围盒技术用一个简单的包围盒(如球面, 长方体)将复杂的物体包裹在内, 当光线经过这个环境时, 光线和包围盒进行求交测试, 若相交, 光线再与包围盒内的物体进行求交测试; 否则, 光线与此包围盒内物体无相交处.
由于包围盒的表面简单, 光线和包围盒进行求交测试比物体简单, 在剔除光线与此包围盒内物体无相交处时简化计算; 但在光线与此包围盒内物体有交点处时, 与包围盒的计算是一种额外的代价.
从上面得知, 包围盒技术没有降低光线跟踪的复杂度, 包围盒技术的优点是层次结构, 包围盒技术根据物体在3D虚拟世界的分布, 将相距近的物体用一包围盒组织在一起, 再将各包围盒组织在更大的包围盒中---形成树状的层次结构, 如图5.2.
图5.2
世界中, 被跟踪的光线首先与最大的包围盒A测试, 有相交处, 与A包围盒内的包围盒b, c, d, e测试, 与c, d, e有相交处, ..., 最后到物体为止.
5.2.2 三维DDA
三维DDA(Digital Differential Analyzer)是结合了二维图形光栅化的算法, 特别是二维直线. 二维图形光栅化的算法首先根据给定的直线的绘制方向确定主轴DA(Driving Axis)方向---取直线方向向量的最大分量的坐标轴为主轴, 对应于主轴方向的坐标增加(减去)1, 另一坐标增加(减去)直线的斜率, 这样, 后一像素的坐标可由前一像素的坐标简单的增量运算得到. 如图5.3.
图5.3
三维DDA将空间分解为互不重叠的均匀三维网格, 三维网格可简单的用一整数三元数组Array(i, j, k)来表示, 每一三维网格中存储有在其内的物体表面片的指针. 光线跟踪时, 光线只需依次与经过的三维网格中的物体表面片进行求交点计算. 如图5.4.
图5.4
图中, 光线1只需与物体7的表面进行测试, 光线2与4, 9, 0, 8, 6的表面测试, 得到与物体6有交点. 我们可以看出来, 测试的效率和三维网格的大小有关. 当三维网格数越多, 三维网格的大小就越小, 每个包含的物体表面也越少, 光线和每个三维网格内的物体表面片的测试时间也越少, 但光线跟踪的三维网格也越多了, 相应的内存占用越大.
三维DDA首先确定被跟踪光线的向量的主轴方向Vd, |Vd| = max(|Vx|, |Vy|, |Vz|),设另两个坐标向量为i和j, 将三维DDA分解为两个二维DDA过程来进行计算,如图5.5
图5.5
缺点 : 当世界中物体少且分布广时, 与许多的空三维网格的测试降低了效率.
现在的商业应用中使用的光线跟踪算法是三维DDA.
5.2.3 BSP技术
二叉空间划分(BSP : Binary Space Partition)是利用平面将整个空间划分为两部分(两部分 : 正侧, 负侧)的性质, 建立空间的二叉树结构.
当一平面L(a)将空间a划分为正空间a+, 负空间a-, 得到a+, L(a), a-三个集合. 当视点在a+时, a-中的物体不可能遮挡a+中的物体及L(a), 空间物体的排序优先级是a+, L(a), a-; 同理, 当视点在a-时空间物体的排序优先级是a-, L(a), a+; 当视点在L(a)时, 依据视线的方向确定排序优先级.
空间中, 以正, 负子空间要有在其内部的多边形为准则选取其中一平面将空间分为正, 负两子空间, 分别作为该空间(根结点)的两个子结点; 正, 负两子空间中再按准则分别选取一平面分为正, 负子空间, ..., 直到每个子空间只包含一多边形为止.空间的二叉树结构每一结点表示一个子空间或多边形. 如图5.6.
图5.6
图中,光线与区域有交点, 首先依据光线的起点或方向确定近区域和远区域(如图中a左边是近区域, a右边是远区域), 一般将交点延光线方向偏移一点, 这样得到的点就在区域内了, 我们设这个偏移点为P(x, y, z).
(1). 现在判断P和第一个划分平面a的关系, 显然P在a的右边(远区域);
(2). 确定P与a右边的第二个划分平面b的关系, P在b的近区域;
(3). 再确定P与第三个划分平面c的关系, P在c的远区域;
(4). 最后确定P第四个划分平面d的关系, P在d的近区域;
我们只要测试光线和5的有效交点, 没有则光线出该区域.
一般划分平面取与坐标轴两两形成的平面平行来大大加快测试. 我们可以发现BSP其实和三维DDA相似, 无非BSP的划分平面是根据物体的分布确定的, 从而避免了空的网格的出现.
BSP内存消耗小, 无无效的区域产生, 且BSP树是二叉树, 树的深度较浅, 从上面的理论分析, BSP是光线跟踪算法中最有潜力的.
5.3 局部光照的例子
5.3.1 代码更新
注意, 这集的例子是game5 project, 不是game4 project.
我们在这例子里加的不多, 在上个例子的基础上改变了顶点的属性, 我们去掉了顶点的漫反射属性, 加了顶点的法向量, 光对顶点的明暗作用是由顶点法向量决定的, 后面的纹理属性不要理它, 下集讲.
我们在例子里加入了一个点光源, 物体加入了表面材质的描述, 这些我们在说明中具体讲解, 下面看看更新的地方.
---------------------------------------------------------------
// gamedef.h 中改了顶点的属性
#define D3DFVF_MYVERTEXTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)
struct MYVERTEXTEX
{
FLOAT x, y, z;
FLOAT nx, ny, nz;
FLOAT tu, tv;
};
// direct9.cpp 中, 在Init3D环境后, 我们加入了光源,
// 同时设置了环境光的颜色
HRESULT CD9Game::InitLight()
{
D3DLIGHT9 d3dl;
FillMemory(&d3dl, sizeof(D3DLIGHT9), 0);
d3dl.Type = D3DLIGHT_POINT;
d3dl.Diffuse.r = 1.0f;
d3dl.Diffuse.g = 1.0f;
d3dl.Diffuse.b = 1.0f;
d3dl.Specular.r = 0.0f;
d3dl.Specular.g = 0.0f;
d3dl.Specular.b = 0.0f;
d3dl.Ambient.r = 0.0f;
d3dl.Ambient.g = 0.0f;
d3dl.Ambient.b = 0.0f;
d3dl.Position.x = 0.0f;
d3dl.Position.y = 0.0f;
d3dl.Position.z = -10.0f;
d3dl.Range = 100.0f;
d3dl.Attenuation0 = 1.0f;
d3dl.Attenuation1 = 0.0f;
d3dl.Attenuation2 = 0.0f;
if (FAILED(m_pD3DDev->SetLight(0, &d3dl)))
{
return E_FAIL;
}
if (FAILED(m_pD3DDev->LightEnable(0, TRUE)))
{
return E_FAIL;
}
m_pD3DDev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(32, 32, 32));
return S_OK;
}
// d9texobject.cpp 中, 在构造函数中设置物体表面的材质特性
CD9TexObject::CD9TexObject(LPDIRECT3DDEVICE9 pD3DDev)
{
m_pD3DDev = pD3DDev;
m_pD3DVBuffer = NULL;
m_pD3DTexture = NULL;
m_bInit = FALSE;
m_fx = 0.0;
m_fy = 0.0;
m_fz = 0.0;
m_fW = 0.0;
m_fH = 0.0;
m_fD = 0.0;
m_D3DMaterial.Diffuse.r = 1.0f;
m_D3DMaterial.Diffuse.g = 1.0f;
m_D3DMaterial.Diffuse.b = 1.0f;
m_D3DMaterial.Diffuse.a = 0.0f;
m_D3DMaterial.Ambient.r = 1.0f;
m_D3DMaterial.Ambient.g = 1.0f;
m_D3DMaterial.Ambient.b = 1.0f;
m_D3DMaterial.Ambient.a = 0.0f;
m_D3DMaterial.Specular.r = 0.0f;
m_D3DMaterial.Specular.g = 0.0f;
m_D3DMaterial.Specular.b = 0.0f;
m_D3DMaterial.Specular.a = 0.0f;
m_D3DMaterial.Emissive.r = 0.0f;
m_D3DMaterial.Emissive.g = 0.0f;
m_D3DMaterial.Emissive.b = 0.0f;
m_D3DMaterial.Emissive.a = 0.0f;
m_D3DMaterial.Power = 0.0f;
InitD3DVertex(pD3DDev);
}
// 在Render中告诉设备物体表面的材质特性,
// 请把设置纹理的代码注解掉
VOID CD9TexObject::Render()
{
if (m_bInit)
{
m_pD3DDev->SetStreamSource(0, m_pD3DVBuffer, 0, sizeof(MYVERTEXTEX));
m_pD3DDev->SetFVF(D3DFVF_MYVERTEXTEX);
//if (m_pD3DTexture != NULL)
//{
// m_pD3DDev->SetTexture(0, m_pD3DTexture);
//}
m_pD3DDev->SetMaterial(&m_D3DMaterial);
m_pD3DDev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 12);
}
}
---------------------------------------------------------------
5.3.2 说明
5.3.2.1 光源的属性
DirectX Graphics中的光源具有的属性, 并非所有的光源都有7种属性.
(1). Position(位置).
在3D空间中光源的位置, 如(0.0, 10.0, 0.0).
(2). Direction(方向).
光源发射出来的光的方向, 用向量表示, 如(0.0, 0.0, 1.0).
(3). Range(范围).
光从光源发出后所能到达的最大距离, 范围以外的物体将不会接受到此光源的光.
(4). Attenuation(衰减).
在光源到它的范围之间, 光会逐渐衰减, 定义衰减参数.
光源(除环境光)可产生以下三种光, 我们需要根据实际的情况决定是否都要使用.
(5). Diffuse(光源产生的漫反射光的颜色).
用D3DCOLORVALUE定义漫反射光颜色, 由红-绿-蓝- alpha 表示.
(6). Ambient(光源产生的环境光的颜色).
环境光的颜色.
(7). Specular(光源产生的镜面反射光的颜色).
镜面反射光的的颜色.
5.3.2.2 光源的类型
DirectX Graphics中的光源按简单到复杂分为以下4种,
(1). Ambient Light(环境光源).
DirectX Graphics中的环境光是不需用D3DLIGHT9定义的. 因为环境光是世界空间中到处存在的, 没有位置, 没有方向, 没有范围和衰减; 所以环境光是作为物体的背景颜色处理的, 我们是通过SetRenderState来设置环境光的颜色.
(2). Point Light(点光源).
点光源有位置, 它向所有方向发射光, 所以没有方向, 有范围和衰减.
(3). Directional Light(直线光源).
直线光源到处存在所以没有位置, 有方向, 没有范围和衰减. 直线光源和环境光源的唯一区别就是直线光源有方向.
(4). SpotLight(聚光灯光源).
聚光灯光源比较复杂, 它有光源的7种属性, 而且聚光灯光源在光照内径和光照外径的衰减是不同的.
5.3.2.3 物体表面的材质
光照射到物体表面后, 是根据物体表面的材质来确定光对物体的作用, 我们在D3DMATERIAL9结构中定义物体表面的材质,
(1). Diffuse
物体表面材质对漫反射光的吸收率, 用D3DCOLORVALUE定义, 分别设置对红-绿-蓝的吸收率, 由本集开始的知识中我们知道其实是定义物体表面的颜色.
(2). Ambient
物体表面材质对环境光的吸收率. 影响物体表面的颜色.
上面的(1)和(2)综合就是物体表面显示的颜色.
(3). Specular
物体表面材质对镜面反射光的的反射率.
(4). Power
从物体表面反射的镜面反射光的汇聚值, 从上面的知识中我们知道, Power越大, 镜面反射光趋于一点; Power越小, 镜面反射光趋于整个物体表面.
上面的(3)和(4)综合就是物体表面镜面反射的效果, 注意光源要设置Specular的值, 并且要让IDirect3DDevice9知道我们要产生镜面反射效果, 通过IDirect3DDevice9的函数SetRenderState(D3DRS_SPECULARENABLE, TRUE)来设置.
(5). Emissive
物体表面自发光,但不会影响到世界里的其它物体.
第五集 小结
这一集我们学习了要进行DirectX Graphics 3D编程中的光照模型, 现在使用的一般都是局部光照明模型, 光线跟踪算法在游戏编程中现在还不是很主流. 可以改变光源的RGB和物体材质中的RGB, 看看会有什么不同的效果.