前言:该系列教程主要参考自网站www.opengl-tutorial.org和learnopengl.com/,基于开源GUI框架imgui v1.61实现,imgui自带的例子里面直接集成了glfw+gl3w环境,本系列教程将gl3w换成了glew,glew具体环境配置可参考:OpenGL环境配置教程:VS2012 + GLEW + GLFW + GLM。
教程目录(持续更新中):
现代OpenGL教程(一):绘制三角形(ImGui+OpenGL3.3)
现代OpenGL教程(二):矩阵变换(ImGui+OpenGL3.3)
现代OpenGL教程(三):绘制彩色立方体(ImGui+OpenGL3.3)
现代OpenGL教程(四):立方体纹理贴图(ImGui+OpenGL3.3)
现代OpenGL教程(五):obj模型加载(ImGui+OpenGL3.3)
现代OpenGL教程(六):鼠标和键盘(ImGui+OpenGL3.3)
现代OpenGL教程(七):基础光照——颜色(ImGui+OpenGL3.3)
现代OpenGL教程(八):基础光照——Phong光照模型(ImGui+OpenGL3.3)
现代OpenGL教程(九):基础光照——材质(ImGui+OpenGL3.3)
本节教程在上一节(现代OpenGL教程(七):基础光照——颜色(imgui+OpenGL3.3))的基础上,实现了一个简单的Phong光照模型。本例完整代码下载地址:https://github.com/maijiaquan/blog-code/tree/master/opengl-phong
Phong光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。下面这张图展示了这些光照分量看起来的样子:
在上一节(现代OpenGL教程(七):基础光照——颜色(imgui+OpenGL3.3))中,我们模拟了光源照射物体然后反射出颜色的效果。本节的环境光照的实现非常简单,用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色:
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
运行效果:
在现实生活中,如果我们在黑暗中用光照射不会发光的物体,则有如下现象:当入射光垂直于物体表面,即直射物体时,我们会觉得亮度很高。入射光越倾斜,亮度越低。
入射光在 L 1 L_1 L1的位置照射物体,光线方向跟物体表面法线 n n n的夹角为 θ \theta θ。 θ \theta θ越大,即入射光越倾斜,则亮度应该越低。现将入射光移动到 L 2 L_2 L2,此时 θ = 0 \theta=0 θ=0,即入射光垂直于物体表面,亮度会达到最大值。
这就是漫反射光照要模拟的效果:让亮度随着夹角 θ \theta θ的减小而增大,当 θ = 0 \theta=0 θ=0时,亮度达到最大值。
在OpenGL中实现漫反射光照,实质上是通过漫反射分量来改变物体片段的颜色。
计算漫反射分量所需的参数:
norm
:物体当前片段的法向量lightDir
:光线方向向量1.计算光线的方向向量
求光线的方向向量lightDir
,其实就是求光源位置lightPos
和片段位置FragPos
的向量差。此外,为了简化计算,还要将其标准化为单位向量:
vec3 lightDir = normalize(lightPos - FragPos);
2.计算漫反射分量
物体当前片段的法向量 n n n和光线的方向向量lightDir
进行点乘运算dot()
,得到的结果,再乘以光的颜色,得到漫反射分量。该运算符合之前的描述:两个向量之间的夹角 θ \theta θ 越大,漫反射分量就会越小。此外,如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致漫反射分量变为负数。为此,我们使用max()
函数返回两个参数之间较大的参数,从而保证漫反射分量不会变成负数:
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
3.颜色输出
最后,我们得到了环境光分量和漫反射分量,把它们相加,结果乘以物体的颜色,来获得片段最后的输出颜色:
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
除了依赖光线方向向量和物体法向量,镜面光照还依赖于观察方向,例如玩家是从什么方向看着这个片段的。日常生活中,当视线方向在反射向量附近,我们会看到一个高光。为了模拟这一现象,需要计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大,因此可以跟漫反射一样用点乘来实现该效果。镜面光照示意图:
计算镜面光分量所需的参数:
norm
:物体当前片段的法向量lightDir
:光线方向向量viewDir
:视线方向向量reflectDir
:反射向量specularStrength
:镜面强度,用于控制镜面光强度shininess
:反光度,一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。1.计算视线方向向量
通过计算观察者位置viewPos
和物体当前片段位置FragPos
的向量差,求得视线方向向量viewDir
。
片段着色器中添加一个uniform作为viewPos:
uniform vec3 viewPos;
...
vec3 viewDir = normalize(viewPos - FragPos);
OpenGL中把观察者坐标传进去:
setVec3(cubeProgramID, "viewPos", position.x, position.y, position.z);
2.计算反射向量
注意要对lightDir向量进行取反,因为reflect()
要求第一个向量是从光源指向片段位置的向量:
vec3 reflectDir = reflect(-lightDir, norm);
3.计算镜面分量
先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的反光度shininess
次幂。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。
float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
vec3 specular = specularStrength * spec * lightColor;
4.颜色输出
将镜面分量加到环境光分量和漫反射分量里,结果再乘以物体的颜色:
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
运行效果:
1.不同镜面光强度specularStrength
、不同反光度shininess
下的光照效果:
本例完整代码下载地址:https://github.com/maijiaquan/blog-code/tree/master/opengl-phong
参考资料:基础光照 - LearnOpenGL CN