着色与基础着色模型
前面我们已经可以将三维空间中的图形和物体投影到屏幕上,但还没有实现颜色的呈现。这篇文章将介绍着色(Shading)的概念,并详细讲解图形学中最经典的基础着色模型——Blinn-Phong 反射模型。
着色
着色的概念
着色并不是简单的”给像素涂上颜色”。它是运用数学和物理模型,模拟光线与物体表面材质交互、并最终计算出每个像素 RGB 颜色的过程。
在现实世界中,我们能看到物体的颜色,是因为光源发出的光子打在物体表面,经过吸收、反射、折射后,最终进入我们的眼睛。在计算机图形学中,着色就是用代码去近似这个物理过程。
着色需要三类输入:
- 物体的材质属性:表面粗糙度、金属度、底色(Albedo)、法线(Normal)等。
- 光源信息:光照方向、颜色、强度。
- 观察者视角:摄像机 / 眼睛的位置。
GPU 拿到这些数据后,在渲染管线中通过特定的数学公式(如点积
着色器
底层开发中,CPU 擅长处理复杂的逻辑控制(分支预测、文件读写等),而 GPU 则是为海量并行计算而设计的。着色器(Shader) 就是运行在 GPU 上、负责执行着色计算的小程序。
在过去,显卡并没有”着色器”这个概念。那时的显卡是固定功能管线(Fixed-Function
Pipeline),硬件把光照公式(如基础的 Lambert
模型)直接固化在芯片电路上。程序员只能通过开关(如
glEnable(GL_LIGHTING))来决定是否启用光照,无法自行编写个性化的光照算法。
而现代显卡已进化为可编程管线(Programmable Pipeline)。硬件厂商将芯片做成通用的计算单元,把控制权完全交给程序员。想怎么算光影、想做写实还是二次元卡通,全凭写进着色器里的数学公式决定。
着色器有多种类型,最核心的两类是:
- 顶点着色器(Vertex Shader)
- 执行单元:每个 3D 模型的顶点触发一次(并行执行)。
- 核心任务:空间变换。把物体在 3D 世界空间中的坐标,乘以 MVP 矩阵,最终计算出它在 2D 屏幕上的投影位置。根据着色频率的不同,也可能在此阶段进行颜色计算。
- 输入 / 输出:输入顶点的 Position、Normal、UV;输出变换后的裁剪空间坐标及插值所需数据。
- 片元 / 像素着色器(Fragment / Pixel Shader)
- 执行单元:屏幕上被三角形覆盖的每一个像素(片元)触发一次。
- 核心任务:决定颜色。Blinn-Phong 光照公式、纹理采样、法线贴图解码等,全部发生在这里。
- 输入 / 输出:输入是光栅化阶段插值出的法线和
UV;输出是一个四维向量
FragColor(RGBA),直接写入帧缓冲区。
编写着色器不能直接用 C++,而要使用专门的着色器语言。随着不同图形 API 的发展,涌现了多种着色器语言:
| 着色器语言 | 主要宿主 API | 平台 | 语法风格 | 核心定位 |
|---|---|---|---|---|
| HLSL | DirectX 11/12 | Windows, Xbox | 类似 C++ | 工业标准,AAA 级游戏首选 |
| GLSL | OpenGL / Vulkan | Android, Linux, 跨平台 | 类似 C | 移动端、跨平台项目及软渲染首选 |
| MSL | Metal | macOS, iOS | 纯正 C++14 | 苹果生态独占高性能渲染 |
| WGSL | WebGPU | 现代浏览器 | 类似 Rust | 网页端 3D 与高性能计算的未来标准 |
| SPIR-V | Vulkan 驱动底层 | 显卡芯片级 | 二进制字节码 | 高级语言编译后的最终交付形态 |
着色的流程
从模型数据到屏幕上的像素颜色,着色经历了以下三个阶段:
1. 顶点着色(Vertex Shader)
对三角网格的每一个顶点进行处理。计算顶点在相机空间中的位置,并将法线向量(Normal)、纹理坐标(UV)等着色所需数据,从模型空间变换到裁剪空间,打包传递给下游阶段。
2. 光栅化与插值(Rasterization)
将 3D 三角形映射为 2D 屏幕上的像素片元(Fragments)。利用重心坐标公式,把三角形三个顶点上的法线、UV 坐标平滑地插值到三角形内部的每一个像素上,让每个像素都拥有专属的、平滑过渡的法线方向。
3. 片元着色(Fragment / Pixel Shader)
对屏幕上的每一个像素运行片元着色器程序。每个像素拿到插值后的专属法线,结合光照方向、视线方向,代入 Blinn-Phong 或 PBR(基于物理的渲染)公式,计算出最终的 RGB 颜色并写入帧缓冲区。
着色频率
着色频率(Shading Frequency)指的是”在哪个粒度上执行着色计算”,它直接决定了渲染结果的质量和性能。
三种主要着色频率的对比如下:
1. 平面着色(Flat Shading)
- 粒度:以整个三角形面为单位,每个面只算一次。
- 做法:用面的几何法线做一次光照计算,整个面涂上相同的颜色。
- 优点:性能极高。
- 缺点:物体呈现出生硬的”低多边形(Low-Poly)“方块感,无法表现光滑曲面和高光。
2. 顶点着色(Gouraud Shading)
- 粒度:以每个顶点为单位,在 Vertex Shader 中计算颜色。
- 做法:在三角形的三个顶点上分别算出光照颜色,光栅化时通过重心坐标将颜色线性插值到三角形内部的每个像素。
- 优点:性能高(顶点数远少于像素数),相比 Flat Shading 能表现出较好的光照过渡。
- 缺点:颜色在三角形内部是线性插值的,当高光点恰好落在三角形内部(而非顶点上)时,高光会被生生抹平、拉伸,无法呈现锐利的镜面高光。
3. 像素着色(Phong Shading)
注意:这里的”Phong Shading”指的是着色频率(逐像素着色),不要与 Phong 光照模型混淆。Phong Shading 完全可以搭配 Blinn-Phong 光照模型使用。
- 粒度:以每个像素为单位,在 Fragment Shader 中计算颜色。
- 做法:顶点阶段不计算颜色,只传递法线。光栅化时插值的是法线向量。在片元着色器中,每个像素用自己专属的插值法线独立计算光照。
- 优点:能完美呈现细腻、圆润的高光和材质质感。
- 缺点:每个像素都要跑一遍完整的光照公式,对 GPU 算力要求更高(但现代硬件已完全能轻松驾驭)。
着色模型
着色模型(Shading Model)决定了光线与物体表面交互时的计算方式。不同的模型在视觉真实度、物理准确性、渲染性能和艺术风格上差异巨大。
这里按”物理准确度”的高低来分类:
1. 经验 / 半经验模型(Empirical Models)
不追求严格的物理规律(如能量守恒),纯粹靠数学公式去拟合和”欺骗”人眼。公式通常极为廉价,只涉及点积和乘加运算。
经典代表:
- Lambert 模型:最基础的漫反射模型,假设光线向四周均匀散射。
- Phong 模型:引入由视线和反射光线夹角决定的镜面高光。
- Blinn-Phong 模型:对 Phong
的改进,引入半程向量(Halfway Vector),用
替代 ,解决了高光计算中视线逆向反射的痛点,至今仍是实时渲染和软渲染器的标配。
2. 基于物理的渲染模型(Physically Based Rendering, PBR)
严格遵循物理世界的两大铁律:能量守恒(反射出的光不能多于入射光)和微表面理论(将材质表面视为无数肉眼不可见的微小镜面)。
经典代表:
- Cook-Torrance 模型:现代 PBR 的基石。其镜面反射项由
(法线分布函数)、 (几何遮蔽函数)和 (菲涅尔方程)三项组合而成。 - Disney Principled BRDF:迪士尼提出的”原则性”模型,将晦涩的物理参数精简为艺术家直观可控的”粗糙度、金属度、高光度”等滑条。现代商业引擎(UE5、Unity)基本以此作为工业标准。
Blinn-Phong 反射模型
在正式介绍 Blinn-Phong 之前,需要明确一个核心前提——“Shading is Local”。当着色器运行时,它只能看到局部的信息:
- 自身的属性:着色点
的法线 、UV 坐标、世界空间位置。 - 光源的属性:光线的方向
、颜色、强度。 - 相机的属性:视线的方向
。
因此,GPU 天然“不知道”阴影和间接光照的存在。但这并不意味着我们无法渲染出逼真的效果——通过阴影映射(Shadow Mapping)、环境光遮蔽(SSAO)等技术,我们可以近似出阴影和环境反射;而通过光线追踪(Ray Tracing),我们甚至可以将”Shading is Local”彻底翻转为”Shading is Global”。
如图所示:物体经过 Blinn-Phong 着色后并没有出现影子——Shading
下面,正式开始拆解 Blinn-Phong 模型的三大组成部分。
Blinn-Phong 模型认为,一个物体表面呈现的最终颜色,由三种完全不同的光线交互叠加而成:
即:总光量 = 环境光 + 漫反射 + 镜面高光
完整的数学表达式为:
为完成上述计算,需要准备四个核心方向向量(全部要求是单位向量):
(Normal):表面的法线向量,垂直于表面朝外。 (Light):从片元指向光源的方向向量。 (View):从片元指向摄像机 / 眼睛的方向向量。 (Halfway):半程向量——Blinn-Phong 模型的精髓所在。
环境光(Ambient)
物理本质:现实中,光线在各种物体间反复弹射(间接光照)。即使没有任何光源直射,暗部也不会是绝对的死黑——总有微弱的散射光填充阴影区域。
数学实现:Blinn-Phong 用一个最简单的常数来近似整个间接光照:
:环境光的光源颜色和强度(通常是一个全局的弱光,如 vec3(0.1, 0.1, 0.1))。:材质对环境光的反射系数(通常直接取物体的材质底色 Albedo)。
环境光的作用是给画面定一个保底的暗部亮度,防止没有光照的背面彻底变成纯黑剪影。这是一种极大的简化——它假设环境中处处都是同等亮度的”背景光”,不区分方向、不产生阴影。
漫反射(Diffuse)
物理本质:光线打在粗糙(微观层面凹凸不平)的表面上,向四周均匀地散射开来。因此,无论观察者从哪个角度看,漫反射的亮度都是完全相同的——与视线向量
数学实现:著名的兰伯特余弦定律(Lambert’s Cosine Law)。
:漫反射系数(通常就是物体的材质颜色 / Albedo)。 :光源的颜色和强度。 :光源到着色点之间的 3D 几何距离。 :着色点的单位法线向量。 :从着色点指向光源的单位方向向量。
公式推导
为什么有
考虑一个点光源(Point
Light),它每秒钟向外均匀地发射固定数量的光子。这些光子组成一个不断膨胀的球体波前。球的表面积公式为
假设在距离 1 单位处,单位面积能捕获到固定数量的光子。传播到距离
为什么有
一束平行光斜射在表面上时,同样的光通量要覆盖更大的面积。这个“摊薄”的比例恰好由光线方向与法线夹角的余弦给出——即
- 当光线垂直入射时(
), ,单位面积接收到的光最多。 - 当光线擦着表面掠过时(
), ,几乎接收不到光。 - 当光线从背面射来时(
),物理上没有意义, 直接取 舍弃。
将距离衰减和角度衰减相乘,再乘以材质本身的反射系数
镜面反射(Specular)
物理本质:与向四周均匀散射的漫反射不同,镜面反射是方向性的。当光线打在光滑的表面(如金属、玻璃、水面)上时,会沿一个特定方向集中反射。只有视线
Phong 反射模型(1975)
Bui Tuong Phong 在 1975 年首次提出用完美镜面反射向量
已知入射光方向
:镜面反射系数。高光通常保留光源本身的颜色,因此 一般设为白色。 :光源的颜色与强度。 :光源到着色点之间的 3D 几何距离。 :高光指数(Shininess),控制高光光斑的”胖瘦”。
Phong 模型的痛点
尽管直观,Phong 的镜面反射公式存在两个问题:
- 计算开销大:每个像素都要算一遍完整的反射向量
(两次点积 + 一次标量-向量乘法 + 一次减法),对早期硬件是不小的负担。 - 不自然的截断:当
(即视线跑到了反射方向的”背面”)时,高光直接归零,在边缘处产生生硬的暗边。而现实中即使视角偏离了完美反射方向,也应有微弱的高光残留。
Blinn-Phong 的半程向量(1977)
两年后,Jim Blinn 提出了一个优雅的改进。他注意到一个关键事实:
当表面法线
恰好是光照方向 和视线方向 的角平分线时,镜面反射光正好射入观察者的眼睛。
他将这条角平分线命名为半程向量(Halfway Vector)
直观理解:
是光子来的方向, 是你看向表面的方向。将两者相加并归一化,得到的就是它们的”正中间方向”。 - 如果法线
恰好指向 ,说明表面的”镜子朝向”刚好把入射光反射到眼睛里——此时高光最亮。 偏离 越远,反射光越暗。
用
为什么 Blinn-Phong 优于 Phong?
- 计算更便宜:
只需一次向量加法 + 一次归一化,比算 的指令数更少。在 GPU 上,归一化( normalize)比多次点积和乘加更快。 恒成立:Blinn-Phong 的高光始终比 Phong 更宽、更柔和,视觉上更接近真实材质的高光分布。Phong 模型边缘的生硬截断问题也得以缓解。 可预计算:使用平行光(Directional Light)时, 是全局常量;若同时使用正交投影, 也是常量。此时 在 CPU 端算一次即可,不必每个像素重复计算,极大节省了片元着色器的指令开销。
高光指数 (Shininess)
- 只有
非常接近 (即 几乎完美对准 )的部分才保持较高亮度 - 其余区域迅速衰减到接近
越大,高光越”瘦”
| 视觉效果 | 类比材质 | |
|---|---|---|
| 大而模糊的高光 | 粗糙表面:木材、粉笔、橡胶 | |
| 中等大小的高光 | 塑料、皮肤、漆面 | |
| 小而锐利的高光 | 抛光金属、玻璃、瓷器 |
完整效果示例
将环境光、漫反射、镜面反射三项叠加,就得到了 Blinn-Phong 模型的最终渲染效果:
总结
Blinn-Phong 反射模型以其极低的计算成本和直观的参数,至今仍然是实时渲染、软渲染器、以及许多教学场景中的首选着色模型。它的核心思路——将复杂的光照拆解为环境光、漫反射和镜面反射三个独立可加的项——也为后续更复杂的 PBR 模型奠定了基础。
当然,Blinn-Phong 也有其局限:
- 环境光的常数近似过于粗糙,无法表现真实间接光照的方向性和颜色变化。
- 不保证能量守恒:三项直接相加,可能出现反射总能量超过入射光的情况(在 PBR 中这是严格禁止的)。
- 缺乏菲涅尔效应:现实中的材质在掠射角下反射率会显著增大(Fresnel Effect),Blinn-Phong 无法表现这一点。
这次的插图来自画师 DityPretty
图片地址:https://www.pixiv.net/artworks/120341051