06/06/2020
灯光模拟
原理
视觉上,对世界的感知依靠的是光照及其材质的交互。在开启光照的同时,我们不再直接指出顶点的颜色,而是指定材质与光照,在运用光照方程基于两者的交互计算顶点颜色,使物体的颜色更趋于真实
物体材质(Material)
材质可以看作是确定光照与物体如何进行交互的属性集,即物体的材质特性会反射一部分光到人眼,最终我们观察到的物体颜色就是被反射的那部分光的颜色。此属性集中的属性有:
- 表面反射光的颜色
- 吸收光的颜色
- 表面下材质的折射率
- 表面的光滑度
- 表面的透明度
光源
光源可以发出各种强度的红,绿,蓝三色混合光
颜色向量
- 一个4D的颜色向量,由red,green,blue,alpha,每个分量的取值范围0到1,对于alpha分量表示透明度,0表示完全透明,1表示完全不透明
- 头文件DirectXColor.h中,定义了一些4D的颜色向量
法向量
平面法线是一种描述多边形的单位向量,曲面法线是一种垂直于曲面上一点处的切平面的单位向量,对于光照计算,我们需要通过三角形网格曲面上每一点处的曲面法线来确定光线照到对应点上的角度。通过每个点的顶点法线,在光栅化阶段对每个顶点做差值计算,求出每个点的近似曲面法线
- 法向量是单位向量
- 非平面顶点的法向量很难求出来,我们通常选择求出
变化法向量
- 切向量与法向量正交,当物体进行非等比缩放变换A,这两个向量无法保持正交性,即当顶点坐标进行矩阵变换的额时候,也需要针对法向量进行变换,同时还需要重新标准化法向量
注意:尽管运用了逆转置变换,但法向量仍可能失去其单位长度,所以在变换后,可能需对它再次进行规范化处理
InverseTranspose逆转置矩阵
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));
}
关键向量
- E 是观察者的观察位置
- n 是平面单位法向量
- I 光的入射方向
- r 光经过法向量的反射方向
- L 光向量为单位向量,与入射方向相反,即从被照射点向光源的方向向量
- R 为光反射的单位向量
光的种类
分量乘法
分量乘法表示每一个对应分量进行相乘,但需要保证两个分量数量相同,比如 A = (x,y,z), B = (i,j,k)
漫反射光照
- 光从入射点发生散射,光的吸收和散射程度与物体的材质相关。
琅勃余弦定理
主要计算漫反射因素,首先必须大于等于0,因为光向量必须与法向量保持再同一面上,因为背面不被反射,如果值越大,表示漫反射的强度越大
最后漫反射呈现的颜色向量为
- 材质的慢反射系数
- 漫反射光的颜色向量
环境光照
光照模型没有考虑场景中其他物体反射来的间接光照,所以光照方程引进一个环境光。最简单计算方式是环境光颜色向量 分量乘法物理材质对环境光的反射颜色向量为
- 材质的环境反射系数
- 环境光颜色向量
镜面光照
镜面反射,光完全被反射回去,犹如照射到镜子上,镜面反射的强度与人眼所在位置有联系,因为当人眼位置与反射光的位置重叠时候,看到的光是最亮的,表示镜面反射因素公式为
- p为镜面系数,系数越大,越光华啊,必须大于等于1
- toEye 表示物体到眼睛的单位向量
- 的值越大,表示两个向量角度越小
- 如果法向量与光向量相反,将没有镜面反射光
最后呈现的镜面反射光的颜色向量为
- 材质的镜面反射系数
- 镜面光的颜色向量
光照模型
HLSL code
方向光(DirectionalLight)
物体材质
// 物体表面材质
struct Material
{
float4 Ambient;
float4 Diffuse;
float4 Specular; // w = SpecPower
float4 Reflect;
};
//光向量
struct DirectionalLight
{
DirectX::XMFLOAT4 ambient;
DirectX::XMFLOAT4 diffuse;
DirectX::XMFLOAT4 specular;
DirectX::XMFLOAT3 direction;
float pad; // 最后用一个浮点数填充使得该结构体大小满足16的倍数,便于我们以后在HLSL设置数组
};
void ComputeDirectionalLight(Material mat, DirectionalLight L,
float3 normal, float3 toEye,
out float4 ambient,
out float4 diffuse,
out float4 spec)
{
// 初始化输出
ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
// 光向量与照射方向相反
float3 lightVec = -L.Direction;
// 添加环境光
ambient = mat.Ambient * L.Ambient;
// 添加漫反射光和镜面光
float diffuseFactor = dot(lightVec, normal);
// 展开,避免动态分支
[flatten]
if (diffuseFactor > 0.0f)
{
float3 v = reflect(-lightVec, normal);
float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
spec = specFactor * mat.Specular * L.Specular;
}
}
点光源(PointLight)
光照模型公式
- d 为光源到物体表示一点的距离
- 模拟光的衰弱,
- 物体离点光源越远,衰弱因子越接近于0
HLSL码模拟
/ 点光
struct PointLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;
float3 Position;
float Range;
float3 Att;
float Pad;
};
void ComputePointLight(Material mat, PointLight L, float3 pos, float3 normal, float3 toEye,
out float4 ambient, out float4 diffuse, out float4 spec)
{
// 初始化输出
ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
// 从表面到光源的向量
float3 lightVec = L.Position - pos;
// 表面到光线的距离
float d = length(lightVec);
// 灯光范围测试
if (d > L.Range)
return;
// 标准化光向量
lightVec /= d;
// 环境光计算
ambient = mat.Ambient * L.Ambient;
// 漫反射和镜面计算
float diffuseFactor = dot(lightVec, normal);
// 展开以避免动态分支
[flatten]
if (diffuseFactor > 0.0f)
{
float3 v = reflect(-lightVec, normal);
float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
spec = specFactor * mat.Specular * L.Specular;
}
// 光的衰弱
float att = 1.0f / dot(L.Att, float3(1.0f, d, d * d));
diffuse *= att;
spec *= att;
}
聚光灯(SpotLight)
光照强度因子
- spot 光的汇聚程度
- d为照射强度
光照模型公式
HLSL
struct SpotLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;
float3 Position;
float Range;
float3 Direction;
float Spot;
float3 Att;
float Pad;
};
void ComputeSpotLight(Material mat, SpotLight L, float3 pos, float3 normal, float3 toEye,
out float4 ambient, out float4 diffuse, out float4 spec)
{
// 初始化输出
ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
// // 从表面到光源的向量
float3 lightVec = L.Position - pos;
// 表面到光源的距离
float d = length(lightVec);
// 范围测试
if (d > L.Range)
return;
// 标准化光向量
lightVec /= d;
// 计算环境光部分
ambient = mat.Ambient * L.Ambient;
// 计算漫反射光和镜面反射光部分
float diffuseFactor = dot(lightVec, normal);
// 展开以避免动态分支
[flatten]
if (diffuseFactor > 0.0f)
{
float3 v = reflect(-lightVec, normal);
float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
spec = specFactor * mat.Specular * L.Specular;
}
// 计算汇聚因子和衰弱系数 ---- 主要部分
float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);
float att = spot / dot(L.Att, float3(1.0f, d, d * d));
ambient *= spot;
diffuse *= att;
spec *= att;
}
总结
- 局部光照只需要套用光照模型的公式
- 法向量是确认光向量是再正面还是在背面
- 方向光,点光源,聚光灯的简单模拟
Direct12: 游戏开发
X_Jun的博客园