在上一篇文章中,我们成功地为 C++ 软渲染器添加了法线贴图支持,让低模也能展现丰富的表面几何细节。然而,要进一步提升渲染的真实感,我们还需要引入更多控制光照和材质表现的细节。本文将介绍如何继续扩展我们的渲染管线,加入环境光遮蔽 (Ambient Occlusion - AO)、高光颜色 (Specular Color) 和 光泽度 (Glossiness) 贴图。
1. 回顾:基础光照与法线贴图
目前,我们的 Blinn-Phong 着色器已经能够处理:
- 漫反射贴图 (Diffuse Map): 定义物体表面的基础颜色。
- 法线贴图 (Normal Map): 提供逐像素的法线信息,模拟几何细节。
- 统一的材质属性: 如环境光颜色 (
ambientColor
)、漫反射颜色 (diffuseColor
)、高光颜色 (specularColor
) 和光泽度指数 (shininess
),这些属性对整个物体生效。
虽然效果已经不错,但真实世界的材质表现远比这复杂。例如,金属和绝缘体的反光方式不同;物体缝隙中的环境光会更少;表面的粗糙度也会影响高光的形状。
2. 新成员:增强细节的纹理贴图
为了更精细地控制渲染效果,我们引入以下三种新的纹理贴图:
2.1 环境光遮蔽 (Ambient Occlusion - AO) 贴图
- 作用: AO 贴图描述了模型表面某一点接收间接环境光的程度。它模拟了几何体自身或邻近几何体对环境光的遮挡效果。通常,缝隙、角落、褶皱等难以被环境光照射到的地方,其 AO 值较低(偏黑),而暴露在外的表面 AO 值较高(偏白)。
- 实现方式: AO 贴图通常是一张灰度图。在片元着色器中,我们采样 AO 图得到一个遮蔽因子
aoFactor
(范围 0.0 到 1.0)。这个因子用于调制 (乘以) 最终的环境光贡献。1
2// 伪代码
AmbientTerm = GlobalAmbientLight * MaterialAmbientColor * aoFactor; - 视觉效果: AO 贴图可以显著增强模型的体积感和细节,尤其是在缺少复杂全局光照计算的简单渲染管线中,能有效地模拟出接触阴影和几何体之间的遮挡感。
(示意图:左侧无 AO,右侧有 AO)
2.2 高光颜色 (Specular Color) 贴图
- 作用: 此贴图定义了表面高光反射的颜色和强度。基础的 Blinn-Phong 模型通常使用一个统一的
specularColor
。但现实中,不同材质的高光颜色不同(例如,金属的高光通常带有金属本身的颜色,而绝缘体的高光通常是白色)。Specular 贴图允许我们逐像素地控制这一点。 - 实现方式: 在片元着色器中,如果 Specular 贴图存在,我们就采样它来获取当前片元的高光颜色
mapSpecularColor
,并用它替代统一的uniform_SpecularColor
。如果贴图不存在,则回退使用统一颜色。1
2
3
4
5
6
7// 伪代码
if (useSpecularMap) {
matSpecular = sample(specularTexture, uv);
} else {
matSpecular = uniform_SpecularColor;
}
// ... 使用 matSpecular 计算高光 ... - 视觉效果: 可以表现混合材质,如金属上的锈迹(锈迹部分高光弱或无),或者带有特定颜色反射的材质。
2.3 光泽度 (Glossiness) 贴图
- 作用: 光泽度贴图(有时也叫光滑度 Smoothness 图,或者反过来用粗糙度 Roughness 图)控制表面高光的锐利程度。光滑的表面(如镜子、抛光金属)有小而亮的高光,而粗糙的表面(如磨砂塑料、石头)则有模糊而散开的高光。
- 实现方式: 在 Blinn-Phong 模型中,高光的锐利程度由
shininess
指数控制(值越高,高光越小越亮)。Gloss 贴图通常是灰度图,其值(范围 0.0 到 1.0)需要映射到一个合适的shininess
范围。例如,可以将 Gloss 值 0.0 映射到最低shininess
(如 2),将 1.0 映射到最高shininess
(如 256 或更高)。注意:从 Gloss 值到 Shininess 的映射关系可以根据需要调整,线性、指数或自定义曲线都可以。1
2
3
4
5
6
7
8
9// 伪代码
if (useGlossMap) {
glossFactor = sample(glossTexture, uv).r; // 取单通道
// 线性映射示例
currentShininess = lerp(MIN_SHININESS, MAX_SHININESS, glossFactor);
} else {
currentShininess = uniform_Shininess;
}
// ... specFactor = pow(NdotH, currentShininess) ... - 视觉效果: 极大地增强了材质的区分度,能清晰地表现出物体表面的光滑或粗糙程度。
3. 代码实现要点
将这些贴图集成到我们现有的渲染器中,主要涉及以下修改:
3.1 数据结构 (Material
, Shader
)
- 在
Material
结构体中添加aoTexture
,specularTexture
,glossTexture
成员(类型为Texture
)以及对应的加载函数。 - 在
Shader
基类中添加对应的uniform_AoTexture
,uniform_SpecularTexture
,uniform_GlossTexture
uniform 变量,以及uniform_UseAoMap
,uniform_UseSpecularMap
,uniform_UseGlossMap
的布尔标志。
1 | // include/core/material.h (部分) |
3.2 渲染器 (Renderer)
在 Renderer::drawModel 函数中设置 Shader Uniform 的部分,添加对新贴图和标志的设置:
1 | // src/core/renderer.cpp (drawModel 部分) |
3.3 片元着色器 (Fragment Shader)
这是改动最大的地方,在 BlinnPhongShader::fragment 中集成新贴图的采样和应用逻辑:
1 | // src/core/blinn_phong_shader.cpp (fragment 部分) |
4. 效果展示
当这几种贴图组合在一起时,渲染结果的真实感将得到显著提升。金属部分会呈现出带有颜色的高光,锈迹部分则显得暗淡粗糙;模型缝隙的阴影感更强,整体光照更加自然。
(示意图:对比仅有 Diffuse/Normal 与 包含 AO/Specular/Gloss 的渲染效果)
5. 总结与展望
通过引入 AO、Specular 和 Gloss 贴图,我们的软渲染器在表现材质细节方面迈进了一大步。这使得我们能够更精细地控制光照的各个方面,模拟出更加多样和逼真的表面效果。
这些贴图的概念实际上也是基于物理的渲染 (Physically Based Rendering - PBR) 工作流的核心组成部分(尽管 PBR 通常使用不同的参数组合,如 Albedo、Metallic、Roughness、AO)。虽然我们当前的 Blinn-Phong 光照模型并非严格意义上的 PBR,但对这些贴图的支持为将来向更先进的 PBR 光照模型迁移打下了良好的基础。
下一步,可以考虑实现更复杂的 PBR 光照模型(如 Cook-Torrance),或者引入环境贴图 (Environment Mapping) 来实现基于图像的光照 (Image-Based Lighting - IBL),让渲染效果更上一层楼。