在这篇文章中,我们继续使用自己开发的软件渲染流程,来实现 Lambertian 光照模型,并验证效果。
1. Lambertian 光照模型
Lambertian 光照模型,主要用于模拟那些可以完全漫反射光线的表面。Lambertian 模型理解起来比较直观,表面的明暗是由表面法线与光线方向之间的角度决定的。
直观的点在于:光线垂直照射表面,最亮;平行照不到光,最暗。即,和角度的余弦相关。
如图 1 所示,在此次实验环境中,我们定义光线的方向就是照向表面的方向。所以,设法线为 N,光线方向为 L,那么光线作用的量为:
光线可能从背面,即 N 和 L 之间的角度大于 90 度,余弦值会为负。因为都是不贡献光照效果,所以不需要负数:
已经获取了光的“贡献量”,接着进行简单的反射颜色模拟。此处颜色采用 RGB 方式表示,光源颜色和物体表面颜色进行元素积操作(逐分量进行乘法操作),可以模拟反射光的颜色。
同样比较符合日常直觉:白光反射物体固有色;黑色物体不反射光;有颜色的光会影响物体的固有色。
我们设光线的颜色为 light_color,物体表面的颜色为 surface_color,可以得到反射光的颜色为:
- light_color × surface_color
再加上光的“贡献量”,可以得到最终看到的表面颜色:
- final_color = light_color × surface_color × max(dot(N,-L),0)
我们还能继续完善一下,我们不希望物体完全照不到光。可以增加一个比较暗的环境光,设为 ambient_color。那么,最终表面反射的颜色为:
- final_color = light_color × surface_color × max(dot(N,-L),0)
- + ambient_color × surface_color
2. 代码实现
在了解了 Lambertian 光照模型的理论之后,我们进行代码实现。如代码清单 1 所示,我们在顶点着色器中获取顶点位置、法线和 uv 坐标,并传递给后续片元着色器。
Lambertian 的具体实现逻辑在片段着色器中。我们首先获取到法线和 uv 坐标。根据 uv 坐标,可以得到表面的颜色值。光源的方向和颜色,以及环境光的颜色,在本次实现中都是常量。所以,公式中涉及的所有参数都“集齐”了,按照先前所讲的公式进行计算即可。
注意,最终的颜色值可能不在 0 到 1 范围内,我们使用 clamp 进行截断。
3. 测试
最后,我们编写测试用例,对实现的 Lambertian 效果进行验证。
采用的场景还是贴图立方体,但是和之前的数据定义方式不同。之前立方体的三角形顶点是共用的,但是现在不行了。因为相同的顶点可能属于不同的面,所以它们的法线可能不同,需要单独指定。
如代码清单 2 所示,我们单独定义 12 个面,即 36 个点。vertices 有 36 行,太长,所以此处进行了省略。同时,vertices 采用交错格式,一个顶点数据包括位置、法线和 uv 坐标。
其余的操作都是之前接触过的接口。创建 vao 和 vbo。现在只需要创建一个 vbo,并指定三个 layout。指定纹理贴图。设置着色器 “uniform”。开启深度测试。引入摄像机模块。
图 2 是运行的效果,光线设置从右侧打过来,可以看到效果正确。
本章的完整代码见 tag/lambertian。