一、 光照模型基础:从颜色到立体感
在 3D 渲染中,仅仅贴上纹理会让物体看起来像平面的纸模。为了体现物体的体积感和材质感,我们需要模拟光线与物体表面的交互。
1. 法线(Normal)的重要性
光照计算的核心在于角度。我们需要知道光线是从哪里射入的,以及物体表面是朝向哪里的。
- 顶点法线:在输入布局(Input Layout)中,除了位置(Position)和纹理坐标(UV),通常还需要引入法线(Normal)。
- 插值:顶点法线在光栅化阶段会被线性插值传递给像素着色器(Pixel Shader),这就是为什么平滑的球体表面能呈现连续的光照变化。
2. 冯氏光照模型 (Phong Reflection Model)
这是最经典的局部光照模型,它将光照分解为三个分量:
A. 环境光 (Ambient)
- 物理含义:模拟光的散射,即那些经过多次反射后充满整个环境的基础亮度。
- 计算:
全局环境色 * 材质反射率。 - 效果:保证物体在没有直接光照的背面也不会完全变成死黑。
B. 漫反射 (Diffuse)
- 物理含义:模拟粗糙表面的光线均匀散射(朗伯余弦定律)。
- 计算:取决于光线方向 (L) 与 法线方向 (N) 的夹角余弦值。
- 公式:
- 点积原理:当光线垂直射向表面时(夹角0度,点积1),最亮;当光线与表面平行或从背面射入时(夹角>=90度,点积<=0),无光照。
C. 镜面光 (Specular)
- 物理含义:模拟光滑表面的高光反射。
- 计算:取决于反射光方向 (R) 与 观察者视线方向 (V) 的夹角。
- 公式:
- 高光指数 (Shininess):指数越大,光斑越小且锐利(如金属);指数越小,光斑越散(如塑料)。
3. HLSL 实现逻辑
在像素着色器中,我们通常在一个统一的空间(如世界空间 World Space)进行计算:
// 伪代码示例float3 ambient = globalAmbient * materialColor;float3 diffuse = saturate(dot(normal, lightDir)) * lightColor * materialColor;// Blinn-Phong 改进:使用半程向量 (Half Vector) 替代反射向量计算,性能更好float3 halfVector = normalize(lightDir + viewDir);float3 specular = pow(saturate(dot(normal, halfVector)), shininess) * lightColor;
return float4(ambient + diffuse + specular, 1.0f);二、 CPU 与 GPU 的通信桥梁:常量缓冲区 (Constant Buffer)
在渲染每一帧时,世界矩阵(World Matrix)、光照位置、相机位置等数据都在不断变化。cbuffer 是 DirectX 11 专门用于传输这类“每帧更新”数据的机制。
1. 16字节对齐规则 (16-Byte Alignment)
这是初学者最容易踩的坑。GPU 读取常量缓冲区时,是按 4个浮点数(float4,即 16字节) 为一个寄存器单位(Register)读取的。
- HLSL 端:变量会自动跨越寄存器边界,但如果 CPU 端的数据结构没对齐,数据就会错位。
- C++ 端:结构体大小必须是 16 的倍数。
错误示例:
struct CBuffer { XMFLOAT3 pos; // 12 bytes float time; // 4 bytes (刚好凑齐16,没问题) XMFLOAT3 dir; // 12 bytes // --- 这里结束,总共 28 bytes --- // 下一个结构体开始时,GPU 会期待从 32 byte 处开始读取,导致错位。};正确做法:使用填充变量(Padding)或 DirectXMath 的对齐类型。
struct CBuffer { XMMATRIX world; // 64 bytes (OK) XMFLOAT3 pos; // 12 bytes float padding; // 4 bytes (手动补齐到 16)};2. 更新策略:Map/Unmap vs UpdateSubresource
- UpdateSubresource:简单直接,驱动程序会处理内存复制。适合更新频率较低或数据量极小的情况。
- Map (Write Discard):对于每帧都要更新的缓冲区(如变换矩阵),使用
D3D11_MAP_WRITE_DISCARD。这告诉 GPU:“旧的数据我不要了,给我一块新内存写”。这是实现动态缓冲区高性能更新的标准模式,可以避免 GPU 等待 CPU 的同步阻塞。
三、 输出合并阶段 (Output Merger Stage)
这是图形管线的最后一环,决定了像素着色器计算出的颜色最终如何写入渲染目标(Render Target)。
1. 深度测试 (Depth Testing / Z-Buffering)
在 3D 空间中,物体有前后遮挡关系。
-
原理:每个像素除了存储颜色,还存储一个深度值(0.0 ~ 1.0)。
-
逻辑:当新像素要写入时,比较它的深度值与缓冲区中已有的深度值。
-
如果新像素更近(Depth < OldDepth),则通过测试,覆盖颜色并更新深度。
-
如果新像素更远,则丢弃(Discard)。
-
Early-Z:现代 GPU 会在像素着色器运行之前进行一次粗略的深度测试,如果注定被遮挡,就不跑昂贵的着色器代码了,极大地节省了性能。
2. 混合状态 (Blending)
用于实现半透明效果(如玻璃、水、特效)。
-
公式:
-
Src (Source):当前像素着色器输出的颜色。
-
Dest (Destination):渲染目标上原本存在的颜色。
-
常用组合:
-
标准透明:SrcFactor =
SRC_ALPHA, DestFactor =INV_SRC_ALPHA。 -
叠加模式(Add):SrcFactor =
ONE, DestFactor =ONE(常用于火焰、光效)。
3. 光栅化状态 (Rasterizer State)
虽然从流程上属于 RS 阶段,但常与 OM 阶段一起配置。
- 剔除 (Culling):决定背面是否渲染。通常设置为
D3D11_CULL_BACK(逆时针为正面)以提高性能。 - 填充模式 (Fill Mode):可以选择
SOLID(实体填充)或WIREFRAME(线框模式,常用于调试几何体)。