OpenGL学习(八)phong光照模型

本文介绍了在OpenGL中使用Phong光照模型来模拟物体光照效果的过程。通过在Model类中进行改动,支持多个模型的绘制,并详细阐述了环境光、漫反射和高光的计算方法。在着色器中实现光照计算,使场景更加逼真。
摘要由CSDN通过智能技术生成

前言

上一篇回顾:OpenGL学习(七)通过assimp库读取多种格式的模型

在上一篇博客中,我们实现了最简单的网格对象 Mesh,并且从 assimp 库接收我们需要的信息,同时进行绘制。这意味着我们逐渐步入现代。

今天我们要利用 phong 光照模型,实现对物体的光照效果的模拟,让场景更加真实。

注:
本篇博客代码基于上一篇博客:OpenGL学习(七)通过assimp库读取多种格式的模型

Model 类的小小改动

在开始之前,我们要对我们昨天(上一篇博客)新鲜封装的 model 类进行一些改动。在昨天,我们直接传递了一个模型变换矩阵 model 到着色器,因为我们只绘制一个物体,足够了。

希望你不会发现上一篇博客我偷懒了,这本来就应该是在 Model 类定义的时候应该完成的。。。

如果要绘制多个不同的物体,那么问题来了。不同的物体我们需要传递不同的模型变换矩阵,于是我们把这一步骤放到了 Model 类的 draw 函数中。

我们添加三个成员,表示一个模型对象的平移,旋转,缩放:

class Model
{
   
public:
	// ...
    glm::vec3 translate=glm::vec3(0,0,0), rotate = glm::vec3(0, 0, 0), scale = glm::vec3(1, 1, 1);
    // ...

紧接着我们在 draw 函数中,绘制之前,传递本模型的模型变换矩阵即可:

// 传模型矩阵
glm::mat4 unit(    // 单位矩阵
    glm::vec4(1, 0, 0, 0),
    glm::vec4(0, 1, 0, 0),
    glm::vec4(0, 0, 1, 0),
    glm::vec4(0, 0, 0, 1)
);
glm::mat4 scale = glm::scale(unit, this->scale);
glm::mat4 translate = glm::translate(unit, this->translate);

glm::mat4 rotate = unit;    // 旋转
rotate = glm::rotate(rotate, glm::radians(this->rotate.x), glm::vec3(1, 0, 0)); 
rotate = glm::rotate(rotate, glm::radians(this->rotate.y), glm::vec3(0, 1, 0));  
rotate = glm::rotate(rotate, glm::radians(this->rotate.z), glm::vec3(0, 0, 1));

// 模型变换矩阵
glm::mat4 model = translate * rotate * scale;
GLuint mlocation = glGetUniformLocation(program, "model");    // 名为model的uniform变量的位置索引
glUniformMatrix4fv(mlocation, 1, GL_FALSE, glm::value_ptr(model));   // 列优先矩阵

遇到任何问题?回顾之前的博客:OpenGL学习(三)三维绘制与模型变换矩阵

对于多个模型,我们创建一个全局 vector 变量,名叫 models,她存储不同的 Model 对象。

std::vector<Model> models;

我们在初始化的时候,就应该指定模型的平移旋转缩放参数,同时将 Model 对象加入 models :

Model tree1 = Model();
tree1.translate = glm::vec3(0.25, 0, -1);
tree1.scale = glm::vec3(0.00025, 0.00025, 0.00025);
tree1.load("models/tree/tree02.obj");
models.push_back(tree1);

在 display 中,我们直接绘制所有的 Model 对象即可:

for (auto m : models)
{
   
    m.draw(program);
}

我们现在可以自由的绘制多个模型:

在这里插入图片描述

phong 光照模型

在现实世界中,光线从各种光源出发,经过无数次反弹,最终进入眼镜。这种现象对于计算机来说几乎不可解,因为复杂度高的一。

在这里插入图片描述

时下流行的方法是从摄像机方向,逐像素,向场景中投射(一条或者多条)光线,光线沿途不断反弹,每次反弹都搜集信息(比如碰撞点的颜色),直到满足某些条件就终止。常用的方法有蒙卡洛特路径追踪,辐射度方法等。这些方法统称为光线追踪,简称光追。

在这里插入图片描述

光线追踪最大的难点就是求交,即当前光线何时碰撞到实体?碰撞的实体颜色是什么?此外,光追需要大量的迭代才能够拟合,这意味着我们要向一个像素发送若干条光线(128,256,甚至更多)才能够达到不错的效果,计算量也是一大难点!

至于求交等大量计算的工作,图形界的大佬们提出了体积树(BVH)的方式来进行空间划分求交,而老黄等一众显卡开发商则将这些操作 “焊死” 在显卡的集成电路和驱动程序中,就如同 OpenGL 的裁剪,光栅化等操作。于是乎,RTX 系显卡就会有一些单元叫做 “光追单元”,这个和经典显卡的几何单元,光栅化单元异曲同工。。。

在这里插入图片描述

我们可以注意到了,这些新式的东西,并不在 OpenGL 流水线的范畴内。这意味着我们只有使用其他的图形 API(比如 Vulkan)才能有机会使用这些新东东。唔,OpenGL 毕竟是上个时代的了(雾)

在这里插入图片描述

如果你读过我之前写的这篇博客:从零开始编写minecraft光影包(9)高级水面绘制 反射与屏幕空间反射,你可能会说了,我们不是已经实现了光线追踪嘛?

其实这种叫做屏幕空间光线追踪,它的信息都来自于我们的屏幕空间,换句话说,我们只能记录那些我们看到的东西,这也是传统的 OpenGL 流水线约束造成的。对于屏幕空间外的信息我们一无所知!

那么传统的 OpenGL 流水线有没有光追呢?有!Minecraft 光影 SEUS 的作者 SE 大佬就实现了。至于天才 SE 的 PTGI 是怎么记录屏幕空间外的信息,唔。。。我不清楚,估计是用了 shadow 阴影帧缓冲的颜色附件?毕竟这是唯一我能够想到的获取屏幕空间外信息的方式

在这里插入图片描述

回想我们玩游戏的时候,屏幕空间外的物品,仍然会将阴影投影到我们的屏幕上。这样一解释就合理了。

啊啊啊啊扯远了扯远了,我爬我爬 dbq Orz 咚咚咚

phong 光照简介

因为经典的全局光照模型太过复杂,而且对于 OpenGL 流水线来说非常难实现,于是早些年的图形程序员提出了一个简单的模型,叫做 phong 光照模型,该模型能够以极低的代价模拟真实的光照场景,在计算机游戏实时渲染领域是性价比极高的模型。直到现在,很多计算机游戏仍然是沿用这一套模型。

phong 光照模型将物体的光线分为三大类,分别是:

  1. 环境光 ambient
  2. 漫反射光 diffuse
  3. 镜面高光 specular

其中环境光 ambient 是一个固定的数值,漫反射光 diffuse 和光源的角度,物体的法向量有关,而镜面高光则和 specular 视线方向,光源角度有关。

最后的光照总和可以用如下的公式简单的描述:

l i g h t = a m b i e n t + d i f f u s e + s p e c u l a r light = ambient + diffuse + specular light=ambient+diffuse+specular

环境光

其中环境光 ambient 是一个固定的数值,物体的每一个像素众生平等。环境光是为了模拟那些经过 n 次反射的光,就如同你半夜起来上厕所,周围不是全黑的,通常是有一丝亮度的,这就是环境光。

环境光的计算十分简单:

a m b i e n t = K a ambient = Ka ambient=Ka

其中 Ka 是环境光的系数,由物体的材质决定。

漫反射

而漫反射光则需要考虑光源和物体的位置关系了。根据物理定律,光线直射物体的时候,反射的光最多,而光线平视物体的时候,我们几乎无法接收到反射光。

在这里插入图片描述

假设光线方向为 L,L’ 为光线的反方向,法线方向为 N,我们可以得出,当光线垂直摄入时,即 N 和 L 成 180 度,即 N 和 L’ 成 0 度时,最亮!

于是有:

d i f f u s e = c o s ( t h e t a ) ∗ K d = d o t ( − L , N ) ∗ K d diffuse = cos(theta) * Kd = dot(-L, N) * Kd diffuse=cos(theta)Kd=dot(L,N)Kd

其中 -L 是 L’ 即入射光的反方向,而 Kd 则是材质的漫反射系数。

高光

高光的原理是反射光线之后,大部分光线都会位于反射光线附近,这部分会高亮过其他地方:

在这里插入图片描述
如何判断高光什么时候入眼呢?我们视线方向,和反射光线的方向越近(θ 越小),就能够看到越多的高光!

在这里插入图片描述

注:
这里取的是反射光线的反方向

我们令反射光线为 R,视线方向为 V,于是有高光公式:

s p e c u l a r = c o s ( t h e t a ) ∗ K s = d o t ( − R , V ) ∗ K s specular = cos(theta) * Ks = dot(-R, V) * Ks specular=cos(theta)Ks=dot(R,V)Ks

其中我们给 cos(θ) 做一个指数,即 cos 的 a 次方,其中 a 是高光衰减系数。所以最终有:

k s cos ⁡ α ϕ \boldsymbol{k}_{s}\cos ^{\alpha} \boldsymbol{\phi}

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值