剖析带光照的头骨几何演示程序
2023-06-21
带光照的头骨几何演示程序:
本程序是结合了 几何体演示程序 和 从文件中加载模型 这两个程序并加以改动的。
主要改动有以下几点:
- 使用实体模式渲染
- 增加了 3 个光源,并可以使用按键(1、2、3)动态控制光源生效的数量
- 内部结构的改动(适当地进行了一些结构封装)
三点布光
光源以及模型材质的初始化如下:
LitSkullApp::LitSkullApp(HINSTANCE hInstance)
: D3DApp(hInstance), mShapesVB(0), mShapesIB(0), mSkullVB(0), mSkullIB(0),
mSkullIndexCount(0), mLightCount(1), mEyePosW(0.0f, 0.0f, 0.0f),
mTheta(1.5f * MathHelper::Pi), mPhi(0.1f * MathHelper::Pi),
mRadius(15.0f) {
...
// 主光源
mDirLights[0].Ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
mDirLights[0].Diffuse = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
mDirLights[0].Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
mDirLights[0].Direction = XMFLOAT3(0.57735f, -0.57735f, 0.57735f);
// 补光源
mDirLights[1].Ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
mDirLights[1].Diffuse = XMFLOAT4(0.20f, 0.20f, 0.20f, 1.0f);
mDirLights[1].Specular = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);
mDirLights[1].Direction = XMFLOAT3(-0.57735f, -0.57735f, 0.57735f);
// 背光源
mDirLights[2].Ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
mDirLights[2].Diffuse = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
mDirLights[2].Specular = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
mDirLights[2].Direction = XMFLOAT3(0.0f, -0.707f, -0.707f);
mGridMat.Ambient = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
mGridMat.Diffuse = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
mGridMat.Specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);
mCylinderMat.Ambient = XMFLOAT4(0.7f, 0.85f, 0.7f, 1.0f);
mCylinderMat.Diffuse = XMFLOAT4(0.7f, 0.85f, 0.7f, 1.0f);
mCylinderMat.Specular = XMFLOAT4(0.8f, 0.8f, 0.8f, 16.0f);
mSphereMat.Ambient = XMFLOAT4(0.1f, 0.2f, 0.3f, 1.0f);
mSphereMat.Diffuse = XMFLOAT4(0.2f, 0.4f, 0.6f, 1.0f);
mSphereMat.Specular = XMFLOAT4(0.9f, 0.9f, 0.9f, 16.0f);
mBoxMat.Ambient = XMFLOAT4(0.651f, 0.5f, 0.392f, 1.0f);
mBoxMat.Diffuse = XMFLOAT4(0.651f, 0.5f, 0.392f, 1.0f);
mBoxMat.Specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);
mSkullMat.Ambient = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
mSkullMat.Diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
mSkullMat.Specular = XMFLOAT4(0.8f, 0.8f, 0.8f, 16.0f);
}
从代码可以看出,3 个光源都是平行光,且它们的方向如下:
图一:三点布光
可见三个平行光源是按照三点布光(Three-Point Lighting)照明技术去设置的。简单来说,三点布光是一种常用的照明技术,广泛应用于摄影、电影制作和三维计算机图形等领域。它由三个主要光源组成,分别为主光源(Key Light)、补光源(Fill Light)和背光源(Back Light),用于创造出具有阴影、层次感和立体感的照明效果。
以下是三个主要光源的功能和角色:
- 主光源(Key Light):主光源是场景中的主要光源,通常位于物体的前方或侧面。它提供了主要的照明,并产生明暗对比,定义物体的形状和表面细节。主光源通常设置在相对较高的位置,以模拟太阳光的照射角度。
- 补光源(Fill Light):补光源位于主光源的相对侧面,用于填充主光源所造成的阴影,并减轻阴影的对比度。补光源的亮度通常比主光源较弱,以确保仍然保留一定的阴影效果,同时使整体画面更加均衡。
- 背光源(Back Light):背光源位于物体的背后,用于从背后照亮物体,产生轮廓和边缘的高光效果。它有助于将物体与背景分离,并为物体提供一定的立体感。背光源通常设置在相对较高的位置,以便在物体周围形成明亮的边缘光。
通过综合使用这三个光源,三点布光能够创造出具有深度、层次感和纹理的照明效果。这种技术可以使被拍摄或渲染的物体更加真实、立体和吸引人。
结构调整
随着 Demo 的愈加复杂,Effect 文件的读取以及它跟 C++ 的交互被封装成独立的类(不同的 Demo 程序可能会有不同的 Effect 实现,因此没有把它放到 Common 目录,而是每个程序单独实现):
图二:Effect 类图
顶点和顶点输入布局也被重新封装:
namespace Vertex {
struct PosNormal {
XMFLOAT3 Pos;
XMFLOAT3 Normal;
};
} // namespace Vertex
class InputLayoutDesc {
public:
// Init like const int A::a[4] = {0, 1, 2, 3}; in .cpp file.
static const D3D11_INPUT_ELEMENT_DESC PosNormal[2];
};
class InputLayouts {
public:
static void InitAll(ID3D11Device *device);
static void DestroyAll();
static ID3D11InputLayout *PosNormal;
};
其对应的实现为:
const D3D11_INPUT_ELEMENT_DESC InputLayoutDesc::PosNormal[2] = {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
D3D11_INPUT_PER_VERTEX_DATA, 0}};
ID3D11InputLayout *InputLayouts::PosNormal = 0;
void InputLayouts::InitAll(ID3D11Device *device) {
//
// PosNormal
//
D3DX11_PASS_DESC passDesc;
Effects::BasicFX->Light1Tech->GetPassByIndex(0)->GetDesc(&passDesc);
HR(device->CreateInputLayout(InputLayoutDesc::PosNormal, 2,
passDesc.pIAInputSignature,
passDesc.IAInputSignatureSize, &PosNormal));
}
void InputLayouts::DestroyAll() { ReleaseCOM(PosNormal); }
切换光源
在程序中使用按键(1、2、3)可以动态控制光源生效的数量。我们看下像素着色器的代码:
float4 PS(VertexOut pin, uniform int gLightCount) : SV_Target {
// Interpolating normal can unnormalize it, so normalize it.
pin.NormalW = normalize(pin.NormalW);
// The toEye vector is used in lighting.
float3 toEye = gEyePosW - pin.PosW;
// Cache the distance to the eye from this surface point.
float distToEye = length(toEye);
// Normalize.
toEye /= distToEye;
//
// Lighting.
//
// Start with a sum of zero.
float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
// Sum the light contribution from each light source.
[unroll] for (int i = 0; i < gLightCount; ++i) {
float4 A, D, S;
ComputeDirectionalLight(gMaterial, gDirLights[i], pin.NormalW, toEye, A, D,
S);
ambient += A;
diffuse += D;
spec += S;
}
float4 litColor = ambient + diffuse + spec;
// Common to take alpha from diffuse material.
litColor.a = gMaterial.Diffuse.a;
return litColor;
}
从代码中可以看到,我们增加了一个参数 gLightCount 用以控制生效的光源数量。在这个程序中,我们使用了 3 个 technique:
technique11 Light1 {
pass P0 {
SetVertexShader(CompileShader(vs_5_0, VS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_5_0, PS(1)));
}
}
technique11 Light2 {
pass P0 {
SetVertexShader(CompileShader(vs_5_0, VS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_5_0, PS(2)));
}
}
technique11 Light3 {
pass P0 {
SetVertexShader(CompileShader(vs_5_0, VS()));
SetGeometryShader(NULL);
SetPixelShader(CompileShader(ps_5_0, PS(3)));
}
}
可以看出,每个 technique 分别代表使用 1~3 个光源进行着色。而在 UpdateScene() 中,我们捕获了按键的结果:
void LitSkullApp::UpdateScene(float dt) {
...
//
// Switch the number of lights based on key presses.
//
if (GetAsyncKeyState('0') & 0x8000)
mLightCount = 0;
if (GetAsyncKeyState('1') & 0x8000)
mLightCount = 1;
if (GetAsyncKeyState('2') & 0x8000)
mLightCount = 2;
if (GetAsyncKeyState('3') & 0x8000)
mLightCount = 3;
}
接着在 DrawScene() 中我们根据按键的结果选取对应的 technique 进行着色处理:
void LitSkullApp::DrawScene() {
...
// Figure out which technique to use.
// 默认是一个主光源
ID3DX11EffectTechnique *activeTech = Effects::BasicFX->Light1Tech;
switch (mLightCount) {
case 1:
activeTech = Effects::BasicFX->Light1Tech;
break;
case 2:
activeTech = Effects::BasicFX->Light2Tech;
break;
case 3:
activeTech = Effects::BasicFX->Light3Tech;
break;
}
D3DX11_TECHNIQUE_DESC techDesc;
activeTech->GetDesc(&techDesc);
for (UINT p = 0; p < techDesc.Passes; ++p) {
// 绘制处理
...
}
HR(mSwapChain->Present(0, 0));
}
* 未经同意不得转载。