DongGu
着色与基础着色模型

着色与基础着色模型

前面我们已经可以将三维空间中的图形和物体投影到屏幕上,但还没有实现颜色的呈现。这篇文章将介绍着色(Shading)的概念,并详细讲解图形学中最经典的基础着色模型——Blinn-Phong 反射模型

着色

着色的概念

着色并不是简单的”给像素涂上颜色”。它是运用数学和物理模型,模拟光线与物体表面材质交互、并最终计算出每个像素 RGB 颜色的过程。

在现实世界中,我们能看到物体的颜色,是因为光源发出的光子打在物体表面,经过吸收、反射、折射后,最终进入我们的眼睛。在计算机图形学中,着色就是用代码去近似这个物理过程。

着色需要三类输入:

  • 物体的材质属性:表面粗糙度、金属度、底色(Albedo)、法线(Normal)等。
  • 光源信息:光照方向、颜色、强度。
  • 观察者视角:摄像机 / 眼睛的位置。

GPU 拿到这些数据后,在渲染管线中通过特定的数学公式(如点积 ),计算出屏幕上每一个像素的 RGB 终值。

着色器

底层开发中,CPU 擅长处理复杂的逻辑控制(分支预测、文件读写等),而 GPU 则是为海量并行计算而设计的。着色器(Shader) 就是运行在 GPU 上、负责执行着色计算的小程序。

在过去,显卡并没有”着色器”这个概念。那时的显卡是固定功能管线(Fixed-Function Pipeline),硬件把光照公式(如基础的 Lambert 模型)直接固化在芯片电路上。程序员只能通过开关(如 glEnable(GL_LIGHTING))来决定是否启用光照,无法自行编写个性化的光照算法。

而现代显卡已进化为可编程管线(Programmable Pipeline)。硬件厂商将芯片做成通用的计算单元,把控制权完全交给程序员。想怎么算光影、想做写实还是二次元卡通,全凭写进着色器里的数学公式决定。

着色器有多种类型,最核心的两类是:

  1. 顶点着色器(Vertex Shader)
    • 执行单元:每个 3D 模型的顶点触发一次(并行执行)。
    • 核心任务:空间变换。把物体在 3D 世界空间中的坐标,乘以 MVP 矩阵,最终计算出它在 2D 屏幕上的投影位置。根据着色频率的不同,也可能在此阶段进行颜色计算。
    • 输入 / 输出:输入顶点的 Position、Normal、UV;输出变换后的裁剪空间坐标及插值所需数据。
  2. 片元 / 像素着色器(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 Shadow

下面,正式开始拆解 Blinn-Phong 模型的三大组成部分。

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 年首次提出用完美镜面反射向量 来计算高光。

Phong 反射向量

已知入射光方向 和表面法线 关于 的镜面反射方向 为:

就是光线被表面”弹飞”后的出射方向。视线 越接近,人眼接收到的反射光就越强。Phong 用 来度量这个接近程度:

  • :镜面反射系数。高光通常保留光源本身的颜色,因此 一般设为白色。
  • :光源的颜色与强度。
  • :光源到着色点之间的 3D 几何距离。
  • :高光指数(Shininess),控制高光光斑的”胖瘦”。

Phong 模型的痛点

尽管直观,Phong 的镜面反射公式存在两个问题:

  1. 计算开销大:每个像素都要算一遍完整的反射向量 (两次点积 + 一次标量-向量乘法 + 一次减法),对早期硬件是不小的负担。
  2. 不自然的截断:当 (即视线跑到了反射方向的”背面”)时,高光直接归零,在边缘处产生生硬的暗边。而现实中即使视角偏离了完美反射方向,也应有微弱的高光残留。

Blinn-Phong 的半程向量(1977)

两年后,Jim Blinn 提出了一个优雅的改进。他注意到一个关键事实:

当表面法线 恰好是光照方向 和视线方向 角平分线时,镜面反射光正好射入观察者的眼睛。

他将这条角平分线命名为半程向量(Halfway Vector)

直观理解

  • 是光子来的方向, 是你看向表面的方向。将两者相加并归一化,得到的就是它们的”正中间方向”。
  • 如果法线 恰好指向 ,说明表面的”镜子朝向”刚好把入射光反射到眼睛里——此时高光最亮。
  • 偏离 越远,反射光越暗。

替代 Phong 模型中的 ,就得到了 Blinn-Phong 镜面反射公式

为什么 Blinn-Phong 优于 Phong?

  1. 计算更便宜 只需一次向量加法 + 一次归一化,比算 的指令数更少。在 GPU 上,归一化(normalize)比多次点积和乘加更快。
  2. 恒成立:Blinn-Phong 的高光始终比 Phong 更宽、更柔和,视觉上更接近真实材质的高光分布。Phong 模型边缘的生硬截断问题也得以缓解。
  3. 可预计算:使用平行光(Directional Light)时, 是全局常量;若同时使用正交投影, 也是常量。此时 在 CPU 端算一次即可,不必每个像素重复计算,极大节省了片元着色器的指令开销。

高光指数 (Shininess)

是控制高光”胖瘦”的核心参数。数学上, 的值落在 区间内,对其取 次方后:

  • 只有 非常接近 (即 几乎完美对准 )的部分才保持较高亮度
  • 其余区域迅速衰减到接近
  • 越大,高光越”瘦”
值范围 视觉效果 类比材质
大而模糊的高光 粗糙表面:木材、粉笔、橡胶
中等大小的高光 塑料、皮肤、漆面
小而锐利的高光 抛光金属、玻璃、瓷器
高光指数对比

完整效果示例

将环境光、漫反射、镜面反射三项叠加,就得到了 Blinn-Phong 模型的最终渲染效果:

Blinn-Phong 最终效果

总结

Blinn-Phong 反射模型以其极低的计算成本和直观的参数,至今仍然是实时渲染、软渲染器、以及许多教学场景中的首选着色模型。它的核心思路——将复杂的光照拆解为环境光、漫反射和镜面反射三个独立可加的项——也为后续更复杂的 PBR 模型奠定了基础。

当然,Blinn-Phong 也有其局限:

  • 环境光的常数近似过于粗糙,无法表现真实间接光照的方向性和颜色变化。
  • 不保证能量守恒:三项直接相加,可能出现反射总能量超过入射光的情况(在 PBR 中这是严格禁止的)。
  • 缺乏菲涅尔效应:现实中的材质在掠射角下反射率会显著增大(Fresnel Effect),Blinn-Phong 无法表现这一点。

这次的插图来自画师 DityPretty

图片地址:https://www.pixiv.net/artworks/120341051

本文作者:DongGu
本文链接:https://donggu.xyz/2026/05/29/图形学入门/着色与基础着色模型/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可