# 前言

1. 熟悉基本的Phong光照模型
2. 掌握常量缓冲区打包规则
3. 熟悉几何模型的顶点位置、法向量计算，能够使用Geometry，实现原理可以留到看完纹理映射和法线贴图再回头看
4. 熟悉光栅化状态

DirectX11 With Windows SDK完整目录

Github项目源码

# 法向量

struct VertexPosNormalColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT4 color;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
};

const D3D11_INPUT_ELEMENT_DESC VertexPosNormalColor::inputLayout[3] = {
{ "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},
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

$\vec{n_{avg}}=\frac{\vec{n_0} + \vec{n_1} + \vec{n_2} + \vec{n_3}}{\parallel\vec{n_0} + \vec{n_1} + \vec{n_2} + \vec{n_3}\parallel }$

$f(x,y,z)≡0$

$\frac{\partial{f}}{\partial{x}}\vec{i}+\frac{\partial{f}}{\partial{y}}\vec{j}+\frac{\partial{f}}{\partial{z}}\vec{k}$

$f(x,y,z)=x^2+y^2+z^2-1≡0$

$\vec{n}=(x,y,z)$

## 法向量的变换

$\mathbf{u}\cdot\mathbf{n}=\mathbf{u}\mathbf{n^T}=0 \\ \mathbf{u}(\mathbf{AA^{-1}})\mathbf{n^T}=0 \\ (\mathbf{uA})(\mathbf{A^{-1}n^T})=0 \\ (\mathbf{uA})((\mathbf{A^{-1}n^T})^T)^T=0 \\ (\mathbf{uA})(\mathbf{n}(\mathbf{A^{-1}})^T)^T=0 \\ \mathbf{uA}\cdot\mathbf{n}(\mathbf{A^{-1}})^T=0 \\$

# 物体材质

// 物体表面材质
// 物体表面材质
struct Material
{
DirectX::XMFLOAT4 ambient;
DirectX::XMFLOAT4 diffuse;
DirectX::XMFLOAT4 specular; // w = 镜面反射强度
DirectX::XMFLOAT4 reflect;
};

// 物体表面材质
struct Material
{
float4 Ambient;
float4 Diffuse;
float4 Specular; // w = SpecPower
float4 Reflect;
};

# 光的种类

## 环境光(Ambient Lighting)

$\mathbf{A} = \mathbf{l_a} \otimes \mathbf{m_a}$

(la.r * ma.r, la.g * ma.g, la.b * ma.b, la.a * ma.a)

## 漫反射光(Diffuse Lighting)

### 朗伯余弦定理

$k_d = max(\mathbf{L} \cdot \mathbf{n}, 0)$

$k_d * \mathbf{l_d} \otimes \mathbf{m_d} = k_d \mathbf{D}$

## 镜面反射光(Specular Lighting)

$k_s=\begin{cases} max(\mathbf{R} \cdot \mathbf{toEye}, 0)^p, \mathbf{L} \cdot \mathbf{n} > 0\\ 0, \mathbf{L} \cdot \mathbf{n} <= 0\\ \end{cases}$

$k_s * \mathbf{l_s} \otimes \mathbf{m_s} = k_s \mathbf{S}$

# 光照模型

## 平行光/方向光

$\mathbf{litColor}=\mathbf{A} + k_d \mathbf{D} + k_s \mathbf{S}$

$k_d = max(\mathbf{L} \cdot \mathbf{n}, 0)\\$

$k_s=\begin{cases} max(\mathbf{R} \cdot \mathbf{toEye}, 0)^p, \mathbf{L} \cdot \mathbf{n} > 0\\ 0, \mathbf{L} \cdot \mathbf{n} <= 0\\ \end{cases}$

struct DirectionalLight
{
DirectX::XMFLOAT4 ambient;
DirectX::XMFLOAT4 diffuse;
DirectX::XMFLOAT4 specular;
DirectX::XMFLOAT3 direction;
};

struct DirectionalLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;
float3 Direction;
};

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;
}
}

## 点光

(Q - P) / length(Q - P)

### 光的衰弱

d为光源到物体一点的距离，则有：

$I(d) = \frac{I_{0}}{a_{0}+a_{1}d+a_{2}d^2}$

$\mathbf{litColor}=\mathbf{A} + \frac{k_d \mathbf{D} + k_s \mathbf{S}}{a_{0}+a_{1}d+a_{2}d^2}$

// 点光
struct PointLight
{
PointLight() { memset(this, 0, sizeof(PointLight)); }

DirectX::XMFLOAT4 ambient;
DirectX::XMFLOAT4 diffuse;
DirectX::XMFLOAT4 specular;

// 打包成4D向量: (position, range)
DirectX::XMFLOAT3 position;
float range;

// 打包成4D向量: (A0, A1, A2, pad)
DirectX::XMFLOAT3 att;
};

// 点光
struct PointLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;

float3 Position;
float Range;

float3 Att;
};

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;
}

## 聚光灯

(Q - P) / length(Q - P)

$k_{spot} = max(\mathbf{-L} \cdot \mathbf{d}, 0)^{spot}$

$\mathbf{LitColor} = k_{spot}(\mathbf{A} + \frac{k_d \mathbf{D} + k_s \mathbf{S}}{a_{0}+a_{1}d+a_{2}d^2})$

struct SpotLight
{
DirectX::XMFLOAT4 ambient;
DirectX::XMFLOAT4 diffuse;
DirectX::XMFLOAT4 specular;

// 打包成4D向量: (position, range)
DirectX::XMFLOAT3 position;
float range;

// 打包成4D向量: (direction, spot)
DirectX::XMFLOAT3 direction;
float spot;

DirectX::XMFLOAT3 att;
};

struct SpotLight
{
float4 Ambient;
float4 Diffuse;
float4 Specular;

float3 Position;
float Range;

float3 Direction;
float Spot;

float3 Att;
};

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;
}

# HLSL代码

// Light.hlsli
#include "LightHelper.hlsli"

cbuffer VSConstantBuffer : register(b0)
{
matrix g_World;
matrix g_View;
matrix g_Proj;
matrix g_WorldInvTranspose;
}

cbuffer PSConstantBuffer : register(b1)
{
DirectionalLight g_DirLight;
PointLight g_PointLight;
SpotLight g_SpotLight;
Material g_Material;
float3 g_EyePosW;
}

struct VertexIn
{
float3 PosL : POSITION;
float3 NormalL : NORMAL;
float4 Color : COLOR;
};

struct VertexOut
{
float4 PosH : SV_POSITION;
float3 PosW : POSITION;     // 在世界中的位置
float3 NormalW : NORMAL;    // 法向量在世界中的方向
float4 Color : COLOR;
};

// Light_VS.hlsl
#include "Light.hlsli"

// 顶点着色器
VertexOut VS(VertexIn vIn)
{
VertexOut vOut;
matrix viewProj = mul(g_View, g_Proj);
float4 posW = mul(float4(vIn.PosL, 1.0f), g_World);

vOut.PosH = mul(posW, viewProj);
vOut.PosW = posW.xyz;
vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);
vOut.Color = vIn.Color; // 这里alpha通道的值默认为1.0
return vOut;
}

// Light_PS.hlsl
#include "Light.hlsli"

// 像素着色器
float4 PS(VertexOut pIn) : SV_Target
{
// 标准化法向量
pIn.NormalW = normalize(pIn.NormalW);

// 顶点指向眼睛的向量
float3 toEyeW = normalize(g_EyePosW - pIn.PosW);

// 初始化为0
float4 ambient, diffuse, spec;
float4 A, D, S;
ambient = diffuse = spec = A = D = S = float4(0.0f, 0.0f, 0.0f, 0.0f);

ComputeDirectionalLight(g_Material, g_DirLight, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S;

ComputePointLight(g_Material, g_PointLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S;

ComputeSpotLight(g_Material, g_SpotLight, pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
ambient += A;
diffuse += D;
spec += S;

float4 litColor = pIn.Color * (ambient + diffuse) + spec;

litColor.a = g_Material.Diffuse.a * pIn.Color.a;

return litColor;
}

# 常用几何模型

(2018/12/15更新)目前的几何模型网格数据采用模板形式，可以做到支持VertexPosVertexPosColorVertexPosTexVertexPosNormalTexVertexPosNormalColorVertexPosNormalTangentTex的顶点（但不支持VertexPosSize）以及WORDDWORD索引的输出。如果对tex和tangent部分的内容不清楚，建议先阅读完纹理映射和法线贴图后再回过头来看

(2020/1/19更新)几何模型网格数据默认将使用32位的DWORD类型

namespace Geometry
{
// 网格数据
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
struct MeshData
{
std::vector<VertexType> vertexVec;    // 顶点数组
std::vector<IndexType> indexVec;    // 索引数组

MeshData()
{
// 需检验索引类型合法性
static_assert(sizeof(IndexType) == 2 || sizeof(IndexType) == 4, "The size of IndexType must be 2 bytes or 4 bytes!");
static_assert(std::is_unsigned<IndexType>::value, "IndexType must be unsigned integer!");
}
};

// 创建球体网格数据，levels和slices越大，精度越高。
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
MeshData<VertexType, IndexType> CreateSphere(float radius = 1.0f, UINT levels = 20, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

// 创建立方体网格数据
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
MeshData<VertexType, IndexType> CreateBox(float width = 2.0f, float height = 2.0f, float depth = 2.0f,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

// 创建圆柱体网格数据，slices越大，精度越高。
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
MeshData<VertexType, IndexType> CreateCylinder(float radius = 1.0f, float height = 2.0f, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

// 创建只有圆柱体侧面的网格数据，slices越大，精度越高
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius = 1.0f, float height = 2.0f, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

// 创建圆锥体网格数据，slices越大，精度越高。
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
MeshData<VertexType, IndexType> CreateCone(float radius = 1.0f, float height = 2.0f, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });

// 创建只有圆锥体侧面网格数据，slices越大，精度越高。
template<class VertexType = VertexPosNormalTex, class IndexType = DWORD>
MeshData<VertexType, IndexType> CreateConeNoCap(float radius = 1.0f, float height = 2.0f, UINT slices = 20,
const DirectX::XMFLOAT4& color = { 1.0f, 1.0f, 1.0f, 1.0f });
}

namespace Geometry
{
namespace Internal
{
//
// 以下结构体和函数仅供内部实现使用
//

struct VertexData
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT4 tangent;
DirectX::XMFLOAT4 color;
DirectX::XMFLOAT2 tex;
};

// 根据目标顶点类型选择性将数据插入
template<class VertexType>
inline void InsertVertexElement(VertexType& vertexDst, const VertexData& vertexSrc)
{
static std::string semanticName;
// 第一步，建立内存映射关系
static const std::map<std::string, std::pair<size_t, size_t>> semanticSizeMap = {
{"POSITION", std::pair<size_t, size_t>(0, 12)},
{"NORMAL", std::pair<size_t, size_t>(12, 24)},
{"TANGENT", std::pair<size_t, size_t>(24, 40)},
{"COLOR", std::pair<size_t, size_t>(40, 56)},
{"TEXCOORD", std::pair<size_t, size_t>(56, 64)}
};

// 第二步...
}
}
// ...

struct VertexPosNormalTex
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT3 normal;
DirectX::XMFLOAT2 tex;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
};

const D3D11_INPUT_ELEMENT_DESC VertexPosNormalTex::inputLayout[3] = {
{ "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 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};

// 根据目标顶点类型选择性将数据插入
template<class VertexType>
inline void Geometry::InsertVertexElement(VertexType& vertexDst, const VertexData& vertexSrc)
{
static std::string semanticName;
// 第一步，建立内存映射关系
static const std::map<std::string, std::pair<size_t, size_t>> semanticSizeMap = {
{"POSITION", std::pair<size_t, size_t>(0, 12)},
{"NORMAL", std::pair<size_t, size_t>(12, 24)},
{"TANGENT", std::pair<size_t, size_t>(24, 40)},
{"COLOR", std::pair<size_t, size_t>(40, 56)},
{"TEXCOORD", std::pair<size_t, size_t>(56, 64)}
};

// 第二步，确定当前顶点需要插入的字节偏移位置
for (size_t i = 0; i < ARRAYSIZE(VertexType::inputLayout); i++)
{
semanticName = VertexType::inputLayout[i].SemanticName;
const auto& range = Geometry::semanticSizeMap.at(semanticName);
memcpy_s(reinterpret_cast<char*>(&vertexDst) + VertexType::inputLayout[i].AlignedByteOffset,
range.second - range.first,
reinterpret_cast<const char*>(&vertexSrc) + range.first,
range.second - range.first);
}
}

## Geometry::CreateBox函数--创建立方体

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateBox(float width, float height, float depth, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX;

MeshData<VertexType, IndexType> meshData;
meshData.vertexVec.resize(24);

Internal::VertexData vertexDataArr[24];
float w2 = width / 2, h2 = height / 2, d2 = depth / 2;

// 右面(+X面)
vertexDataArr[0].pos = XMFLOAT3(w2, -h2, -d2);
vertexDataArr[1].pos = XMFLOAT3(w2, h2, -d2);
vertexDataArr[2].pos = XMFLOAT3(w2, h2, d2);
vertexDataArr[3].pos = XMFLOAT3(w2, -h2, d2);
// 左面(-X面)
vertexDataArr[4].pos = XMFLOAT3(-w2, -h2, d2);
vertexDataArr[5].pos = XMFLOAT3(-w2, h2, d2);
vertexDataArr[6].pos = XMFLOAT3(-w2, h2, -d2);
vertexDataArr[7].pos = XMFLOAT3(-w2, -h2, -d2);
// 顶面(+Y面)
vertexDataArr[8].pos = XMFLOAT3(-w2, h2, -d2);
vertexDataArr[9].pos = XMFLOAT3(-w2, h2, d2);
vertexDataArr[10].pos = XMFLOAT3(w2, h2, d2);
vertexDataArr[11].pos = XMFLOAT3(w2, h2, -d2);
// 底面(-Y面)
vertexDataArr[12].pos = XMFLOAT3(w2, -h2, -d2);
vertexDataArr[13].pos = XMFLOAT3(w2, -h2, d2);
vertexDataArr[14].pos = XMFLOAT3(-w2, -h2, d2);
vertexDataArr[15].pos = XMFLOAT3(-w2, -h2, -d2);
// 背面(+Z面)
vertexDataArr[16].pos = XMFLOAT3(w2, -h2, d2);
vertexDataArr[17].pos = XMFLOAT3(w2, h2, d2);
vertexDataArr[18].pos = XMFLOAT3(-w2, h2, d2);
vertexDataArr[19].pos = XMFLOAT3(-w2, -h2, d2);
// 正面(-Z面)
vertexDataArr[20].pos = XMFLOAT3(-w2, -h2, -d2);
vertexDataArr[21].pos = XMFLOAT3(-w2, h2, -d2);
vertexDataArr[22].pos = XMFLOAT3(w2, h2, -d2);
vertexDataArr[23].pos = XMFLOAT3(w2, -h2, -d2);

for (UINT i = 0; i < 4; ++i)
{
// 右面(+X面)
vertexDataArr[i].normal = XMFLOAT3(1.0f, 0.0f, 0.0f);
vertexDataArr[i].tangent = XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f);
vertexDataArr[i].color = color;
// 左面(-X面)
vertexDataArr[i + 4].normal = XMFLOAT3(-1.0f, 0.0f, 0.0f);
vertexDataArr[i + 4].tangent = XMFLOAT4(0.0f, 0.0f, -1.0f, 1.0f);
vertexDataArr[i + 4].color = color;
// 顶面(+Y面)
vertexDataArr[i + 8].normal = XMFLOAT3(0.0f, 1.0f, 0.0f);
vertexDataArr[i + 8].tangent = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 8].color = color;
// 底面(-Y面)
vertexDataArr[i + 12].normal = XMFLOAT3(0.0f, -1.0f, 0.0f);
vertexDataArr[i + 12].tangent = XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 12].color = color;
// 背面(+Z面)
vertexDataArr[i + 16].normal = XMFLOAT3(0.0f, 0.0f, 1.0f);
vertexDataArr[i + 16].tangent = XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 16].color = color;
// 正面(-Z面)
vertexDataArr[i + 20].normal = XMFLOAT3(0.0f, 0.0f, -1.0f);
vertexDataArr[i + 20].tangent = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f);
vertexDataArr[i + 20].color = color;
}

for (UINT i = 0; i < 6; ++i)
{
vertexDataArr[i * 4].tex = XMFLOAT2(0.0f, 1.0f);
vertexDataArr[i * 4 + 1].tex = XMFLOAT2(0.0f, 0.0f);
vertexDataArr[i * 4 + 2].tex = XMFLOAT2(1.0f, 0.0f);
vertexDataArr[i * 4 + 3].tex = XMFLOAT2(1.0f, 1.0f);
}

for (UINT i = 0; i < 24; ++i)
{
Internal::InsertVertexElement(meshData.vertexVec[i], vertexDataArr[i]);
}

meshData.indexVec = {
0, 1, 2, 2, 3, 0,        // 右面(+X面)
4, 5, 6, 6, 7, 4,        // 左面(-X面)
8, 9, 10, 10, 11, 8,    // 顶面(+Y面)
12, 13, 14, 14, 15, 12,    // 底面(-Y面)
16, 17, 18, 18, 19, 16, // 背面(+Z面)
20, 21, 22, 22, 23, 20    // 正面(-Z面)
};

return meshData;
}

## Geometry::CreateSphere函数--创建球体

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateSphere(float radius, UINT levels, UINT slices, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX;

MeshData<VertexType, IndexType> meshData;
UINT vertexCount = 2 + (levels - 1) * (slices + 1);
UINT indexCount = 6 * (levels - 1) * slices;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount);

Internal::VertexData vertexData;
IndexType vIndex = 0, iIndex = 0;

float phi = 0.0f, theta = 0.0f;
float per_phi = XM_PI / levels;
float per_theta = XM_2PI / slices;
float x, y, z;

// 放入顶端点
vertexData = { XMFLOAT3(0.0f, radius, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.0f, 0.0f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

for (UINT i = 1; i < levels; ++i)
{
phi = per_phi * i;
// 需要slices + 1个顶点是因为 起点和终点需为同一点，但纹理坐标值不一致
for (UINT j = 0; j <= slices; ++j)
{
theta = per_theta * j;
x = radius * sinf(phi) * cosf(theta);
z = radius * sinf(phi) * sinf(theta);
// 计算出局部坐标、法向量、Tangent向量和纹理坐标
XMFLOAT3 pos = XMFLOAT3(x, y, z), normal;

vertexData = { pos, normal, XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(theta / XM_2PI, phi / XM_PI) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}
}

// 放入底端点
vertexData = { XMFLOAT3(0.0f, -radius, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.0f, 1.0f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

// 逐渐放入索引
if (levels > 1)
{
for (UINT j = 1; j <= slices; ++j)
{
meshData.indexVec[iIndex++] = 0;
meshData.indexVec[iIndex++] = j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = j;
}
}

for (UINT i = 1; i < levels - 1; ++i)
{
for (UINT j = 1; j <= slices; ++j)
{
meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;

meshData.indexVec[iIndex++] = i * (slices + 1) + j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = i * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i - 1) * (slices + 1) + j;
}
}

// 逐渐放入索引
if (levels > 1)
{
for (UINT j = 1; j <= slices; ++j)
{
meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j;
meshData.indexVec[iIndex++] = (levels - 2) * (slices + 1) + j % (slices + 1) + 1;
meshData.indexVec[iIndex++] = (levels - 1) * (slices + 1) + 1;
}
}

return meshData;
}

## Geometry::CreateCylinderNoCap函数--创建圆柱体的侧面

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateCylinderNoCap(float radius, float height, UINT slices, UINT stacks, float texU, float texV, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX;

MeshData<VertexType, IndexType> meshData;
UINT vertexCount = (slices + 1) * (stacks + 1);
UINT indexCount = 6 * slices * stacks;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount);

float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;
float stackHeight = height / stacks;

Internal::VertexData vertexData;

// 自底向上铺设侧面端点
UINT vIndex = 0;
for (UINT i = 0; i < stacks + 1; ++i)
{
float y = -h2 + i * stackHeight;
// 当前层顶点
for (UINT j = 0; j <= slices; ++j)
{
theta = j * per_theta;
float u = theta / XM_2PI;
float v = 1.0f - (float)i / stacks;
vertexData = { XMFLOAT3(radius * cosf(theta), y, radius * sinf(theta)), XMFLOAT3(cosf(theta), 0.0f, sinf(theta)),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(u * texU, v * texV) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}
}

// 放入索引
UINT iIndex = 0;
for (UINT i = 0; i < stacks; ++i)
{
for (UINT j = 0; j < slices; ++j)
{
meshData.indexVec[iIndex++] = i * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j + 1;

meshData.indexVec[iIndex++] = i * (slices + 1) + j;
meshData.indexVec[iIndex++] = (i + 1) * (slices + 1) + j + 1;
meshData.indexVec[iIndex++] = i * (slices + 1) + j + 1;
}
}

return meshData;
}

## Geometry::CreateCylinder函数--创建圆柱体

template<class VertexType, class IndexType>
inline MeshData<VertexType, IndexType> CreateCylinder(float radius, float height, UINT slices, UINT stacks, float texU, float texV, const DirectX::XMFLOAT4 & color)
{
using namespace DirectX;

auto meshData = CreateCylinderNoCap<VertexType, IndexType>(radius, height, slices, stacks, texU, texV, color);
UINT vertexCount = (slices + 1) * (stacks + 3) + 2;
UINT indexCount = 6 * slices * (stacks + 1);
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount);

float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;

IndexType vIndex = (slices + 1) * (stacks + 1), iIndex = 6 * slices * stacks;
IndexType offset = vIndex;
Internal::VertexData vertexData;

// 放入顶端圆心
vertexData = { XMFLOAT3(0.0f, h2, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f),
XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

// 放入顶端圆上各点
for (UINT i = 0; i <= slices; ++i)
{
theta = i * per_theta;
float u = cosf(theta) * radius / height + 0.5f;
float v = sinf(theta) * radius / height + 0.5f;
vertexData = { XMFLOAT3(radius * cosf(theta), h2, radius * sinf(theta)), XMFLOAT3(0.0f, 1.0f, 0.0f),
XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(u, v)  };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}

// 放入底端圆心
vertexData = { XMFLOAT3(0.0f, -h2, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

// 放入底部圆上各点
for (UINT i = 0; i <= slices; ++i)
{
theta = i * per_theta;
float u = cosf(theta) * radius / height + 0.5f;
float v = sinf(theta) * radius / height + 0.5f;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(u, v) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}

// 放入顶部三角形索引
for (UINT i = 1; i <= slices; ++i)
{
meshData.indexVec[iIndex++] = offset;
meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
meshData.indexVec[iIndex++] = offset + i;
}

// 放入底部三角形索引
offset += slices + 2;
for (UINT i = 1; i <= slices; ++i)
{
meshData.indexVec[iIndex++] = offset;
meshData.indexVec[iIndex++] = offset + i;
meshData.indexVec[iIndex++] = offset + i % (slices + 1) + 1;
}

return meshData;
}

## Geometry::CreateConeNoCap函数--创建圆锥体的侧面

template<class VertexType, class IndexType>
MeshData<VertexType, IndexType> CreateConeNoCap(float radius, float height, UINT slices, const DirectX::XMFLOAT4& color)
{
using namespace DirectX;

MeshData<VertexType, IndexType> meshData;
meshData.vertexVec.resize(2 * slices);
meshData.indexVec.resize(3 * slices);

float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;
UINT iIndex = 0;
UINT vIndex = 0;
Internal::VertexData vertexData;

// 放入圆锥尖端顶点(每个顶点包含不同的法向量和切线向量)
for (UINT i = 0; i < slices; ++i)
{
theta = i * per_theta + per_theta / 2;
vertexData = { XMFLOAT3(0.0f, h2, 0.0f), XMFLOAT3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}

// 放入圆锥底面顶点
for (UINT i = 0; i < slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(radius * cosf(theta) / len, height / len, radius * sinf(theta) / len),
XMFLOAT4(-sinf(theta), 0.0f, cosf(theta), 1.0f), color, XMFLOAT2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}

// 放入索引
for (UINT i = 0; i < slices; ++i)
{
meshData.indexVec[iIndex++] = i;
meshData.indexVec[iIndex++] = slices + (i + 1) % slices;
meshData.indexVec[iIndex++] = slices + i % slices;
}

return meshData;
}

## Geometry::CreateCone函数--创建圆锥体

template<class VertexType, class IndexType>
MeshData<VertexType, IndexType> CreateCone(float radius, float height, UINT slices, const DirectX::XMFLOAT4& color)
{
using namespace DirectX;
auto meshData = CreateConeNoCap<VertexType, IndexType>(radius, height, slices, color);

UINT vertexCount = 3 * slices + 1;
UINT indexCount = 6 * slices;
meshData.vertexVec.resize(vertexCount);
meshData.indexVec.resize(indexCount);

float h2 = height / 2;
float theta = 0.0f;
float per_theta = XM_2PI / slices;
UINT iIndex = 3 * slices;
UINT vIndex = 2 * slices;
Internal::VertexData vertexData;

// 放入圆锥底面顶点
for (UINT i = 0; i < slices; ++i)
{
theta = i * per_theta;
vertexData = { XMFLOAT3(radius * cosf(theta), -h2, radius * sinf(theta)), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(cosf(theta) / 2 + 0.5f, sinf(theta) / 2 + 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);
}
vertexData = { XMFLOAT3(0.0f, -h2, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f),
XMFLOAT4(-1.0f, 0.0f, 0.0f, 1.0f), color, XMFLOAT2(0.5f, 0.5f) };
Internal::InsertVertexElement(meshData.vertexVec[vIndex++], vertexData);

// 放入索引
UINT offset = 2 * slices;
for (UINT i = 0; i < slices; ++i)
{
meshData.indexVec[iIndex++] = offset + slices;
meshData.indexVec[iIndex++] = offset + i % slices;
meshData.indexVec[iIndex++] = offset + (i + 1) % slices;
}

return meshData;
}

# 光栅化状态

Direct3D是基于状态机的，我们可以通过修改这些状态来修改渲染管线的当前行为。有三种状态值得我们现在以及后续留意：

1. 光栅化状态（光栅化阶段）
2. 混合状态（输出合并阶段）
3. 深度/模板状态（输出合并阶段）

1. 顶点着色器、几何着色器完成了顶点输出后，光栅化阶段会负责对前面传来的顶点数据，尤其是对4D位置向量(SV_POSITION)进行透视除法，判断顶点是否在NDC空间内。
2. 其次，根据光栅化状态来决定顶点顺(逆)时针排布的三角形是否通过。
3. 它还能指定额外的裁剪区域，即矩形区域外的三角形(或者部分)被裁剪掉，仅留下剩余在矩形区域内的像素片元传递到像素着色器中进行处理。
5. 由于光栅化阶段会进行视口变换，在像素着色器中，SV_POSITIONx分量和y分量都已经经过视口变换成为最终的屏幕坐标，且带有小数点0.5，这是因为要取到像素的中心位置，即对于800x600的视口区域，实际上的屏幕坐标取值范围为[0.5, 799.5]x[0.5, 599.5]z分量取值范围为[0, 1]。这一点读者可以修改像素着色器使得SV_POSITION与像素颜色结果有关联，然后进入调试以验证。

## ID3D11Device::CreateRasterizerState方法--创建光栅化状态

typedef struct D3D11_RASTERIZER_DESC
{
D3D11_FILL_MODE FillMode;          // 填充模式
D3D11_CULL_MODE CullMode;          // 裁剪模式
BOOL FrontCounterClockwise;        // 是否三角形顶点按逆时针排布时为正面
INT DepthBias;                     // 忽略
FLOAT DepthBiasClamp;              // 忽略
FLOAT SlopeScaledDepthBias;        // 忽略
BOOL DepthClipEnable;              // 是否允许深度测试将范围外的像素进行裁剪，默认TRUE
BOOL MultisampleEnable;            // 是否允许多重采样
BOOL AntialiasedLineEnable;        // 是否允许反走样线，仅当多重采样为FALSE时才有效
}     D3D11_RASTERIZER_DESC;

D3D11_FILL_WIREFRAME = 2 线框填充方式
D3D11_FILL_SOLID = 3 面填充方式

D3D11_CULL_NONE = 1 无背面裁剪，即三角形无论处在视野的正面还是背面都能看到
D3D11_CULL_FRONT = 2 对处在视野正面的三角形进行裁剪
D3D11_CULL_BACK = 3 对处在视野背面的三角形进行裁剪

HRESULT ID3D11Device::CreateRasterizerState(
const D3D11_RASTERIZER_DESC *pRasterizerDesc,    // [In]光栅化状态描述
ID3D11RasterizerState **ppRasterizerState) = 0;  // [Out]输出光栅化状态

ID3D11RasterizerState *pRasterizerState);  // [In]光栅化状态，若为nullptr，则使用默认光栅化状态

FillMode = D3D11_FILL_SOLID;
CullMode = D3D11_CULL_BACK;
FrontCounterClockwise = FALSE;
DepthBias = 0;
SlopeScaledDepthBias = 0.0f;
DepthBiasClamp = 0.0f;
DepthClipEnable    = TRUE;
ScissorEnable = FALSE;
MultisampleEnable = FALSE;
AntialiasedLineEnable = FALSE;

## 绘制线框

1. 在输入装配阶段，以图元linelist的方式进行装配，然后还修改索引缓冲区，使得每两个索引对应一条线。
2. 修改光栅化阶段，以线框(wireframe)的方式进行像素点的标记。

// ******************
// 初始化光栅化状态
//
D3D11_RASTERIZER_DESC rasterizerDesc;
ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));
rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
rasterizerDesc.CullMode = D3D11_CULL_NONE;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;

# GameApp类

GameApp类的变化如下：

class GameApp : public D3DApp
{
public:

struct VSConstantBuffer
{
DirectX::XMMATRIX world;
DirectX::XMMATRIX view;
DirectX::XMMATRIX proj;
DirectX::XMMATRIX worldInvTranspose;

};

struct PSConstantBuffer
{
DirectionalLight dirLight;
PointLight pointLight;
SpotLight spotLight;
Material material;
DirectX::XMFLOAT4 eyePos;
};

public:
GameApp(HINSTANCE hInstance);
~GameApp();

bool Init();
void OnResize();
void DrawScene();

private:
bool InitEffect();
bool InitResource();
bool ResetMesh(const Geometry::MeshData<VertexPosNormalColor>& meshData);

private:
ComPtr<ID3D11InputLayout> m_pVertexLayout;        // 顶点输入布局
ComPtr<ID3D11Buffer> m_pVertexBuffer;            // 顶点缓冲区
ComPtr<ID3D11Buffer> m_pIndexBuffer;            // 索引缓冲区
ComPtr<ID3D11Buffer> m_pConstantBuffers[2];        // 常量缓冲区
UINT m_IndexCount;                                // 绘制物体的索引数组大小

VSConstantBuffer m_VSConstantBuffer;            // 用于修改用于VS的GPU常量缓冲区的变量
PSConstantBuffer m_PSConstantBuffer;            // 用于修改用于PS的GPU常量缓冲区的变量

DirectionalLight m_DirLight;                    // 默认环境光
PointLight m_PointLight;                        // 默认点光
SpotLight m_SpotLight;                            // 默认汇聚光

ComPtr<ID3D11RasterizerState> m_pRSWireframe;    // 光栅化状态: 线框模式
bool m_IsWireframeMode;                            // 当前是否为线框模式

};

GameApp类中则新添加了几个结构体和对应的一些成员。

## GameApp::ResetMesh方法--重新设置要使用的模型

bool GameApp::ResetMesh(const Geometry::MeshData<VertexPosNormalColor>& meshData)
{
// 释放旧资源
m_pVertexBuffer.Reset();
m_pIndexBuffer.Reset();

// 设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = (UINT)meshData.vertexVec.size() * sizeof(VertexPosNormalColor);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建顶点缓冲区
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = meshData.vertexVec.data();

// 输入装配阶段的顶点缓冲区设置
UINT stride = sizeof(VertexPosNormalColor);    // 跨越字节数
UINT offset = 0;                            // 起始偏移量

// 设置索引缓冲区描述
m_IndexCount = (UINT)meshData.indexVec.size();
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = m_IndexCount * sizeof(DWORD);
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
// 新建索引缓冲区
InitData.pSysMem = meshData.indexVec.data();
// 输入装配阶段的索引缓冲区设置
m_pd3dImmediateContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

return true;
}

## GameApp::InitResource方法的变化

bool GameApp::InitResource()
{
// ******************
// 初始化网格模型
//
auto meshData = Geometry::CreateBox<VertexPosNormalColor>();
ResetMesh(meshData);

// ******************
// 设置常量缓冲区描述
//
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.ByteWidth = sizeof(VSConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
// 新建用于VS和PS的常量缓冲区
cbd.ByteWidth = sizeof(PSConstantBuffer);

// ******************
// 初始化默认光照
// 方向光
m_DirLight.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
m_DirLight.diffuse = XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f);
m_DirLight.specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_DirLight.direction = XMFLOAT3(-0.577f, -0.577f, 0.577f);
// 点光
m_PointLight.position = XMFLOAT3(0.0f, 0.0f, -10.0f);
m_PointLight.ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
m_PointLight.diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
m_PointLight.specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_PointLight.att = XMFLOAT3(0.0f, 0.1f, 0.0f);
m_PointLight.range = 25.0f;
// 聚光灯
m_SpotLight.position = XMFLOAT3(0.0f, 0.0f, -5.0f);
m_SpotLight.direction = XMFLOAT3(0.0f, 0.0f, 1.0f);
m_SpotLight.ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);
m_SpotLight.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
m_SpotLight.specular = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
m_SpotLight.att = XMFLOAT3(1.0f, 0.0f, 0.0f);
m_SpotLight.spot = 12.0f;
m_SpotLight.range = 10000.0f;
// 初始化用于VS的常量缓冲区的值
m_VSConstantBuffer.world = XMMatrixIdentity();
m_VSConstantBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(
XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
));
m_VSConstantBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));
m_VSConstantBuffer.worldInvTranspose = XMMatrixIdentity();

// 初始化用于PS的常量缓冲区的值
m_PSConstantBuffer.material.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
m_PSConstantBuffer.material.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
m_PSConstantBuffer.material.specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 5.0f);
// 使用默认平行光
m_PSConstantBuffer.dirLight = m_DirLight;
// 注意不要忘记设置此处的观察位置，否则高亮部分会有问题
m_PSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f);

// 更新PS常量缓冲区资源
D3D11_MAPPED_SUBRESOURCE mappedData;
memcpy_s(mappedData.pData, sizeof(PSConstantBuffer), &m_PSConstantBuffer, sizeof(PSConstantBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0);

// ******************
// 初始化光栅化状态
//
D3D11_RASTERIZER_DESC rasterizerDesc;
ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));
rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
rasterizerDesc.CullMode = D3D11_CULL_NONE;
rasterizerDesc.FrontCounterClockwise = false;
rasterizerDesc.DepthClipEnable = true;

// ******************
// 给渲染管线各个阶段绑定好所需资源
//

// 设置图元类型，设定输入布局
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout.Get());
// 将着色器绑定到渲染管线
// VS常量缓冲区对应HLSL寄存于b0的常量缓冲区
// PS常量缓冲区对应HLSL寄存于b1的常量缓冲区

// ******************
// 设置调试对象名
//
D3D11SetDebugObjectName(m_pVertexLayout.Get(), "VertexPosNormalTexLayout");
D3D11SetDebugObjectName(m_pConstantBuffers[0].Get(), "VSConstantBuffer");
D3D11SetDebugObjectName(m_pConstantBuffers[1].Get(), "PSConstantBuffer");

return true;
}

{
static float phi = 0.0f, theta = 0.0f;
phi += 0.0001f, theta += 0.00015f;
XMMATRIX W = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
m_VSConstantBuffer.world = XMMatrixTranspose(W);
m_VSConstantBuffer.worldInvTranspose = XMMatrixTranspose(InverseTranspose(W));

// ...

// 更新常量缓冲区，让立方体转起来
D3D11_MAPPED_SUBRESOURCE mappedData;
memcpy_s(mappedData.pData, sizeof(VSConstantBuffer), &m_VSConstantBuffer, sizeof(VSConstantBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[0].Get(), 0);

memcpy_s(mappedData.pData, sizeof(PSConstantBuffer), &m_PSConstantBuffer, sizeof(PSConstantBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0);

}

# 练习题

1. 尝试修改本章Demo的光照，让方向光只射出红光，点光灯只射出绿光，聚光灯只射出蓝光。
2. 尝试修改本章Demo所用到的材质，让其只反射红光。
3. 尝试修改本章Demo所用到的聚光灯，通过鼠标滚轮的形式，对光照汇聚强度增加/减少，范围为2-512，观察效果。
4. 尝试修改本章Demo所用到的材质，看看如果镜面反射强度的值小于1会发生什么情况。