点光与聚光
点光
从上图我们很容易理解,点光的光照向量会随着点的位置而变化(从目标点指向光源,跟光的传播方向相反),而平行光的光照向量则为常量。
点光的结构及其初始化如下:
struct PointLight {
PointLight() { ZeroMemory(this, sizeof(this)); }
XMFLOAT4 Ambient;
XMFLOAT4 Diffuse;
XMFLOAT4 Specular;
// Packed into 4D vector: (Position, Range)
XMFLOAT3 Position;
float Range;
// Packed into 4D vector: (A0, A1, A2, Pad)
XMFLOAT3 Att;
float
Pad; // Pad the last float so we can set an array of lights if we wanted.
};
LightingApp::LightingApp(HINSTANCE hInstance)
: D3DApp(hInstance), mLandVB(0), mLandIB(0), mWavesVB(0), mWavesIB(0),
mFX(0), mTech(0), mfxWorld(0), mfxWorldInvTranspose(0), mfxEyePosW(0),
mfxDirLight(0), mfxPointLight(0), mfxSpotLight(0), mfxMaterial(0),
mfxWorldViewProj(0), mInputLayout(0), mEyePosW(0.0f, 0.0f, 0.0f),
mTheta(1.5f * MathHelper::Pi), mPhi(0.1f * MathHelper::Pi),
mRadius(80.0f) {
...
// Point light--position is changed every frame to animate in UpdateScene
// function.
mPointLight.Ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
mPointLight.Diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
mPointLight.Specular = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
mPointLight.Att = XMFLOAT3(0.0f, 0.1f, 0.0f);
mPointLight.Range = 25.0f;
...
}
其中,Att 表示衰减系数,我们知道 Shading point(或者说它周围的单位面积)在任何一个位置所能接收到的能量,跟光线传播的距离成平方反比(具体见 Games-101-Illumination 光的辐射相关知识):
其中 表示跟光源距离为 1 处的能量。但是如果直接按照这个公式计算,通常得不到让人满意的效果,因此我们经常会使用另一个可调参的公式去代替:
可以灵活地设置 Att = 的值来取得不同的效果。
而 Range 则表示点光的照射范围。我们以一个点光照射在一个平面的截面分析:
从上图我们可以看到,Range 其实就是点光朝四面八方发射光线的最长有效距离,从上图体现为圆的半径(实际上是球的半径,上图只是一个截面)。点 处在 Range 内,所以参与光照的运算;而点 在 Range 外,所以不参与光照的运算。如此一来,加上前面平行光作用下的山峰河谷的基础,点光部分的像素着色就很好理解了:
//---------------------------------------------------------------------------------------
// Computes the ambient, diffuse, and specular terms in the lighting equation
// from a point light. We need to output the terms separately because
// later we will modify the individual terms.
//---------------------------------------------------------------------------------------
void ComputePointLight(Material mat, PointLight L, float3 pos, float3 normal,
float3 toEye, out float4 ambient, out float4 diffuse,
out float4 spec) {
// Initialize outputs.
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);
// The vector from the surface to the light.
float3 lightVec = L.Position - pos;
// The distance from surface to light.
float d = length(lightVec);
// Range test.
if (d > L.Range)
return;
// Normalize the light vector.
lightVec /= d;
// Ambient term.
ambient = mat.Ambient * L.Ambient;
// Add diffuse and specular term, provided the surface is in
// the line of site of the light.
float diffuseFactor = dot(lightVec, normal);
// Flatten to avoid dynamic branching.
[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;
}
// Attenuate
float att = 1.0f / dot(L.Att, float3(1.0f, d, d * d));
diffuse *= att;
spec *= att;
}
从 整体演示 中我们发现,点光会绕着地形旋转。这部分逻辑的代码在 UpdateScene() 中:
void LightingApp::UpdateScene(float dt) {
...
//
// Animate the lights.
//
// Circle light over the land surface.
mPointLight.Position.x = 70.0f * cosf(0.2f * mTimer.TotalTime());
mPointLight.Position.z = 70.0f * sinf(0.2f * mTimer.TotalTime());
mPointLight.Position.y =
MathHelper::Max(
GetHillHeight(mPointLight.Position.x, mPointLight.Position.z),
-3.0f) +
10.0f;
}
聚光
聚光跟点光类似,但它跟前面的点光有三个区别:
- 聚光会把光线限制在一个圆锥体内
- 圆锥体内同一平面的点跟光的方向向量(上图 )越接近,能量越强(能量:)
- 最长有效距离(Range)通常比较大
通常我们会通过设置上图三 角的大小来指定一个聚光。那要怎样通过数学的方式来描述聚光的区域范围以及能量衰减呢?这里可以取巧去套用高光反射的公式(具体见 Games-101-Illumination 高光相关知识):
那么在 内的点都能参与光照运算,而且指数 对应的是上述聚光与点光区别里面第二点的衰减速度(注意区别于从光源源头到目标点传播过程的衰减,它们是不一样的两个维度的衰减)。
聚光的结构与初始化如下:
struct SpotLight {
SpotLight() { ZeroMemory(this, sizeof(this)); }
XMFLOAT4 Ambient;
XMFLOAT4 Diffuse;
XMFLOAT4 Specular;
// Packed into 4D vector: (Position, Range)
XMFLOAT3 Position;
float Range;
// Packed into 4D vector: (Direction, Spot)
XMFLOAT3 Direction;
float Spot;
// Packed into 4D vector: (Att, Pad)
XMFLOAT3 Att;
float
Pad; // Pad the last float so we can set an array of lights if we wanted.
};
LightingApp::LightingApp(HINSTANCE hInstance)
: D3DApp(hInstance), mLandVB(0), mLandIB(0), mWavesVB(0), mWavesIB(0),
mFX(0), mTech(0), mfxWorld(0), mfxWorldInvTranspose(0), mfxEyePosW(0),
mfxDirLight(0), mfxPointLight(0), mfxSpotLight(0), mfxMaterial(0),
mfxWorldViewProj(0), mInputLayout(0), mEyePosW(0.0f, 0.0f, 0.0f),
mTheta(1.5f * MathHelper::Pi), mPhi(0.1f * MathHelper::Pi),
mRadius(80.0f) {
...
// Spot light--position and direction changed every frame to animate in
// UpdateScene function.
mSpotLight.Ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
mSpotLight.Diffuse = XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f);
mSpotLight.Specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
mSpotLight.Att = XMFLOAT3(1.0f, 0.0f, 0.0f); // 跟点光一样,表示光源源头到目标点的传播过程的衰减系数
mSpotLight.Spot = 96.0f; // 相当于指数 p
mSpotLight.Range = 10000.0f;
}
本程序的聚光的位置和方向跟相机的位置方向是完全一样的,也就是聚光会从相机位置射向世界空间中心,对应代码逻辑在 UpdateScene() 中:
void LightingApp::UpdateScene(float dt) {
// Convert Spherical to Cartesian coordinates.
float x = mRadius * sinf(mPhi) * cosf(mTheta);
float z = mRadius * sinf(mPhi) * sinf(mTheta);
float y = mRadius * cosf(mPhi);
mEyePosW = XMFLOAT3(x, y, z);
// Build the view matrix.
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, V);
// The spotlight takes on the camera position and is aimed in the
// same direction the camera is looking. In this way, it looks
// like we are holding a flashlight.
mSpotLight.Position = mEyePosW;
XMStoreFloat3(&mSpotLight.Direction, XMVector3Normalize(target - pos));
}
从 整体演示 中我们也可以直观地看到这一点。
聚光部分的像素着色如下:
//---------------------------------------------------------------------------------------
// Computes the ambient, diffuse, and specular terms in the lighting equation
// from a spotlight. We need to output the terms separately because
// later we will modify the individual terms.
//---------------------------------------------------------------------------------------
void ComputeSpotLight(Material mat, SpotLight L, float3 pos, float3 normal,
float3 toEye, out float4 ambient, out float4 diffuse,
out float4 spec) {
// Initialize outputs.
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);
// The vector from the surface to the light.
float3 lightVec = L.Position - pos;
// The distance from surface to light.
float d = length(lightVec);
// Range test.
if (d > L.Range)
return;
// Normalize the light vector.
lightVec /= d;
// Ambient term.
ambient = mat.Ambient * L.Ambient;
// Add diffuse and specular term, provided the surface is in
// the line of site of the light.
float diffuseFactor = dot(lightVec, normal);
// Flatten to avoid dynamic branching.
[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;
}
// Scale by spotlight factor and attenuate.
float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);
// Scale by spotlight factor and attenuate.
float att = spot / dot(L.Att, float3(1.0f, d, d * d));
ambient *= spot;
diffuse *= att;
spec *= att;
}