前言
在Qt中使用OpenGL(一)
在Qt中使用OpenGL(二)
在Qt中使用OpenGL(三)
在Qt中使用OpenGL(四)
在Qt中使用OpenGL(五)
在Qt中使用OpenGL(六)
在前面的文章中,我们实现了对模型的封装。目前我们对于简单的3D世界的构建已经快要接近完成了。
接下来,我们需要的实现的,就是3D世界中很重要的一个元素:光照。
现实中的光照
很显然,在OpenGL的3D世界中,你看到的绝大部分看似符合现实世界中的样子,都是通过数学手段模拟出来的。
光照当然也不例外。
让我们思考一个问题,在阴天你根本看不到太阳的时候,事实上你依旧可以看清楚周围的事物,无论你从任何角度观察一个物体,大概率你看到的这个物体的亮度在各个方向上看起来都是一样。
可是当晚上你在屋里开了灯,你应该能够注意到一个物体的各个表面的亮度并不是一致的——面向灯的那一面比较亮,背向灯的那一面比较暗。而且面向灯的那一面,你还能看到一些闪亮亮的高光的部分。
我们接下来要做的事情,就是将你在现实中看到的情况给模拟出来。
环境光,漫反射与镜面反射
首先,在阴天,虽然没有太阳的直射,你也可以清晰的看到周围的事物,是因为环境光的缘故。
也就是你所处的位置,周围的环境的亮度。
它的成因在于绝大部分的物体都可以反射与透射太阳光,于是虽然没有太阳光的直射,在各种各样的物体上来回反射的太阳光造就了你的周围环境的亮度。
对比一下,哪怕在屋子里你拉上了窗帘,实际上你依旧可以看到屋子里的物品,只不过变暗了而已。这就是光无孔不入的反射造成的。
为了模拟这个现象,我们需要有一个叫做环境光的概念,它实际上就表示了在没有光线直射时,物体受到的光照。
那么我们很容易的就可以推测出来,接下来我们就需要一个在有光线直射时,物体受到的光照的概念了吧。对,它叫做漫反射。
你看到的物体的颜色,绝大部分都是因为它可以反射一部分的光造成的。
例如,光线照射到了物体上,但是物体只会反射绿色,那么你看到的物体就是绿色的。
没错,你周围的所有绿色植物,是因为它们不喜欢绿色的光,将绿色反射回去了,你看到的绿色植物才是绿色的。
什么是镜面反射?顾名思义,将所有的光给反射回去的,就是镜面反射。有时,你会在物体上看到亮闪闪的部分,那就是镜面。比如光滑的地面,虽然不是镜子,但是有时是可以看到反射过来的光源的。这一部分的反射,就被称为镜面反射
有了这三个概念,我们就可以将这三个概念赋予给模型了。
没错,我们依旧需要使用模型中的逻辑来处理光照,虽然光照是一个全局性的概念,但是我们只能逐个模型的进行处理。
一般的,我们将这三个概念,统称材质
法线 与 材质
再继续之前,我们还需要了解另一个概念,它和漫反射还有镜面反射息息相关。
让我们思考一个简单的问题,我们怎么知道,物体的一个面,是朝向光源的?
现实中我们可以很容的通过物体和光源的位置来观察得到,但是具体到了OpenGL的3D世界中,我们怎么就能说一个面就是朝向光源而不是背向光源的呢?
索性我们不需要进行过多的思考,因为在3D世界中,已经存在着这么一个概念了,那就是法线。
什么是法线?数学概念中,垂直于一个平面的向量就被称为这个平面的法线。有了法线,我们就可以计算这个面到底是朝向光源还是背向光源了。
简单来讲,就是计算一下法线向量和从平面上一个点到光源方向向量的点乘,也就是cos值即可。‘
众所周知,cos值在角度[-90°, +90°]范围内的时候是正值,并且越接近±90°值越接近0。让我们想象一下,一个平面的法线,就是你站在这个平面上向上方看,平面上到光源的向量,就是你站在这个平面上向光源看。如果你需要低下头才能看到光源,就证明这个光源和你的头顶的夹角大于90°了,同时这个光源已经在这个平面的下方了,对吧。
于是,只要cos值大于0,就证明这个面被光照到了。
有了以上的知识,我们就可以开始改造之前的模型类了。
首先,我们需要在顶点信息中增加法线的概念,以便可以用来计算光是否照到了这个顶点所在的平面。
其次,我们需要材质这个概念,来模拟环境光,漫反射与镜面反射。
所以,我们就可以这么定义顶点与材质:
struct Vertex
{
QVector3D pos;
QVector2D texture;
QVector3D normal;
};
struct Material
{
float ambient = 1;
float diffuse = 0;
float specular = 0;
float shininess = 16;
};
normal就是法线。
ambient 指的是环境光,范围[0,1]。事实上,之前我们的所有模型就是按照100%的环境光来渲染的。也就是哪怕没有任何直射的光线,你也可以以100%亮度看到这个物体。如果你完全不关心光照,那么就可以将这个值设置为1即可(但你依旧会受到光源颜色的影响)。
diffuse是指漫反射,范围[0,1]。一般情况下,环境光和漫反射加起来要达到1,但是达不到也不要紧。
因为,环境光和漫反射共同决定了你看到的物体的颜色是什么(对应到3D中,实际上就指是你能以多高的亮度看到物体的贴图),如果最终的值不到1,那么只不过是一个看起来比较暗的物体罢了,可以用来模拟晚上或者昏暗的地下室+昏暗的灯光。
specular是指镜面反射,范围[0,1]。值越大,越闪亮。
shininess是指最终计算出来的镜面反射的效果。值越大,光斑越明显。
然后,我们仿照纹理,也增加添加材质的接口(因为一个模型既然可以有多个纹理,那有多个材质也无可厚非):
public:
void setMaterial(const Material &material, int index = -1);
private:
QMap<int, Material> m_materials;
void Model::setTexture(QOpenGLTexture *texture, int index)
{
if (index == -1)
{
if (m_textures.isEmpty())
{
index = 0;
}
else
{
index = m_textures.keys().last() + 1;
}
}
m_textures.insert(index, texture);
}
设置法线和材质
我们依旧使用之前的色子模型来举例,由于我们修改了基类,于是最新的包含了法线初始化顶点数据的代码如下:
setVertices({
// 顶点 纹理 法线
// 1
{
{
-1, 1, 1,}, {
0.50, 0.25}, {
0, 0, 1}}, // 左上
{
{
-1, -1, 1,}, {
0.50, 0.50}, {
0, 0, 1}}, // 左下
{
{
1, -1, 1,}, {
0.75, 0.50}, {
0, 0, 1}}, // 右下
{
{
1, 1, 1,}, {
0.75, 0.25}, {
0, 0, 1}}, // 右上
// 6
{
{
1, 1, -1,}, {
0.00, 0.25}, {
0, 0, -1}}, // 左上
{
{
1, -1, -1,}, {
0.00, 0.50}, {
0, 0, -1}}, // 左下
{
{
-1, -1,