Chaptor 7 Lighting (Introduction to 3D Game Programing with DirextX 11)自读
7.1 LIGHT AND MATERIAL INTERACTION
材质描述了这个物体会如何与光照交互,并解释了全局光照和局部光照的区别。
7.2 NORMAL VECTORS
surface上的normal,开始只定义了vertex上的normal,面上的是在光栅化阶段进行顶点插值后得到的逐像素光照,与之相对应的,如果不进行插值,把计算光照的计算从ps移到vs,则为逐顶点光照,效率提高了,但效果也差一些。
The face normal is orthogonal to all points on the face.
The surface normal is the vector that is orthogonal to the tangent plane of a point on a surface.
7.2.1 Computing Normal Vectors
计算Δp0 p1 p2 的face normal的方法:
u = p1 – p0 ,v = p2 – p0
则face normal 计算n为
dx中可以使用void ComputeNormal(const D3DXVECTOR3& p0, const D3DXVECTOR3& p1, const D3DXVECTOR3& p2, D3DXVECTOR3& out) 接口计算
计算vertex normal的方法:在传入所谓顶点位置后,可以计算每个面的face normal,然后根据各个face normal 做normal average 求得顶点vertex上的normal。
也可以不这么纯平均,可以传入每个面的权重再计算。
代码中做法是,for循环每个triangle,计算这个triangle的三个顶点的normal,然后vertex.normal+=face normal.
由于一个顶点可能会被多个triangle共用,因此加完之后,再for循环每个vertex,调用normalize(vertex.normal).
7.2.2 Transforming Normal Vectors
法线变换,由于直接应用变换矩阵A之后,不能保持法线继续垂直,因此新的法线的计算方式不是直接应用n*A。
这里推导如下,
得到B = (A–1)T,A的逆矩阵的转置矩阵。
注意当A变换矩阵是正交矩阵的时候,即只有线性变化,只有transform和rotation,没有scale,那A的逆==A的转置,则B=A,而对于有nonuniform or shear transformation,还是需要计算逆的转置(dx中接口static XMMATRIX InverseTranspose(CXMMATRIX M))。
static XMMATRIX InverseTranspose(CXMMATRIX M) {
XMMATRIX A = M;
A.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
XMVECTOR det = XMMatrixDeterminant(A);
return XMMatrixTranspose(XMMatrixInverse(&det, A)); }
这个方法中因为要用于向量变换,因此需要清除掉translation,向量不需要translation面,因此在传入M后,需要M.r[3]=(0,0,0,1),从而清除translation。
另外,注意经过inverse transpose之后的法向量,可能已经不是unit的了,因此还需要再normalize。
7.3 LAMBERT’S COSINE LAW
兰伯特光照模型。
light vector is the vector from the surface to the light source。光的向量是从表面到光源的方向,和light ray是相反的。
7.4 DIFFUSE LIGHTING
the light rays scatter in various random directions; this is called a diffuse reflection.
不管视角在哪里,都接收到的一样的颜色,不用考虑eye loc等等。
cd = kd · ιd ⊗ md = kd D
Let ιd be the diffuse light color, md be the diffuse material color, and kd = max (L · n, 0), where L is the light vector, and n is the surface normal
7.5 AMBIENT LIGHTING
A = ιa ⊗ ma
The color ιa specifies the total amount of indirect (ambient) light a surface receives from a light source. The ambient material color ma specifies the amount of incoming ambient light that the surface reflects and absorbs.
这种环境光计算没有任何物理基础,仅仅是一种模拟,简单的把所有的间接光认为对所有物体,所有方向都一样。
7.6 SPECULAR LIGHTING
相比diffuse是视角无关的,specular是跟视角相关联的,不同的视角会影响specular的计算。
光源l反射后的方向r,越靠近r,specular越高,反之,越靠近以r为中心的cone的边缘,specular越低,cone的边缘为0。
为了控制这里的cone,添加指数幂参数p,用来控制这个cone的角度。
p越大,cone越小,衰减越厉害,表面越光滑。
所以最终specular计算,
The color ιs specifies the amount of specular light the light source emits. The specular material color ms specifies the amount of incoming specular light that the surface reflects. The factor ks scales the intensity of the specular light based on the angle between r and v. 另外还有 p指数。
所以最终兰伯特模型计算为
注意这里的r的计算。r = I - 2 (n · I)n。I表示光源的入射方向,也就是light vector(l)的相反的那个向量。
总之,兰伯特其实就是把光照分为absorb和reflect,reflect又分为漫反射diffuse,视角无关的,和高光反射specular,视角相关的,最后加上场景中所有物体都一样的环境光。
7.8 SPECIFYING MATERIALS
如何应用material属性,有多种解决方案
1.把material 属性添加到vertex的结构上,然后还会应用vertex在光栅化时的interpolate,但这种逐顶点还是太粗糙,并且添加了多的信息到vertex structure,还需要去绘制vertex color
2.texture mapping,下章介绍
3.添加到constant buffer,可以application和shader交互,且可动态改变
现在使用方法3来做,基本结构
注意定义material结构体时,specular的指数p可以放在 float4 specular 的w向量上,即alpha上。
这些变量的alpha通道都可以有用,diffuse的alpha向量会用于alpha blending。
7.9 PARALLEL LIGHTS
可以用一个vector来表示固定方向。
7.10 POINT LIGHTS
光源方向,从光源位置Q指向空间任意一点P。
7.10.1 Attenuation
计算光照强度的衰减,一般是按照距离的平方衰减,即
I0 is the light intensity at a distance d = 1 from the light source。
但是这个式子并不是总能得到比较满意的结果,因此加入更多的系数控制。
a0,a1,a2被称为衰减系数,可以根据情况配置。
所以,方程变为
环境光不受衰减影响。
另外,点光源还有range属性,表示光源范围,范围之外就不再计算光照。
7.11 SPOTLIGHTS
和点光源区别就是,spotlight会计算一个cone 范围内的光照强度,和specular类似,用
kspot (ϕ) = max (cosϕ,0)s = max (-L ·d, 0)s 表示,通过指数幂系数控制cone的衰减速度,进而控制cone的角度。
所以,方程变为
相比于点光源,会多计算一个在cone内的系数。
以上,能看到 复杂度spotlight> pointlight>directionallight。
7.12 IMPLEMENTATION
1.lighting structure等等的定义在 lightinghelper.cpp
2.声明同样放在constant buffer中,方便使用,一般放在perframe中,一帧改变一次。
3.调用c++设置到shader的接口为
为了效率,不会单独赋值dirlight结构体的每个属性,而是一次把整个结构体作为raw data拷贝过去,因此要注意structure的对齐问题 。
4.Structure Packing
由于c++和hlsl的pad填充规则不一样,因此有需要注意的问题。
按照float4对齐时,HLSL中不允许一个变量被拆分到两个float4中,因此结构体比如
HLSL会被拆分填充成下面的,C++会被填充成上面的,因此整个拷贝过去的话如果不注意内存对齐会产生问题,因此C++结构体中声明时需要按照HLSL的填充规则进行填充。
另外注意数组的拷贝,flaot2 [8]在HLSL中不会两两合并成 float4 ,而是会用 float4[8]的空间,因此在C++中声明时,也需要声明成 float4 TexOffsets[8];
当然,可以一开始就都用 float4 [4],然后到了代码里再拆,这样拷贝也不会有问题。
5.pc上 HLSL的方法一般都是inline的,因此没有调用function或者 参数传递的性能消耗。