opengl光照效果_UE4精品课程 | OpenGL学习笔记(九)光照

ef8612e288252da861cf761d9da99b00.gif

本文转载于拉Box小能手知乎作者

原文链接:https://zhuanlan.zhihu.com/p/72497359

本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正;

教程链接:

https://link.zhihu.com/?target=https%3A//learnopengl-cn.github.io/02%2520Lighting/06%2520Multiple%2520lights/

一,光照

现实世界的光照是极其复杂的,而且会受到诸多因素的影响,有限的计算能力所无法模拟的。因此此篇介绍OpenGL的光照使用的是简化的光照模型---冯氏光照模型(Phong Shading),对现实的情况进行近似,这样处理起来会更容易一些,而且看起来效果差不多一样。

冯氏光照模型的主要结构由3个分量组成:

1,环境(Ambient)光照

即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。

伪代码参考:

//环境光照在场景里的表现非常简单,用光的颜色乘以一个很小的常量环境因子
float ambientStrength = 0.1; //强度
vec3 ambient = ambientStrength * lightColor; //强度*颜色

2,漫反射(Diffuse)光照

模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。

7383b4f3038a7733fa8ad6d19fc5816d.png
漫反射光照

图左上方有一个光源,它所发出的光线落在物体的一个片段上。我们需要测量这个光线是以什么角度接触到这个片段的。如果光线垂直于物体表面,这束光对物体的影响会最大化,片段会更亮;

为了测量光线和片段的角度,我们使用一个叫做法向量(Normal Vector)的向量,它是垂直于片段表面的一个向量(这里以黄色箭头表示);

这两个向量之间的角度通过点乘计算出来;

伪代码参考:

//着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal; //传入法线向量
..

worldPos= vec3(model * vec4(aPos, 1.0)); //对传入的顶点数据 进行世界空间矩阵转换

in vec3 worldPos; //片段的世界位置
uniform vec3 lightPos; //灯光的位置

vec3 norm = normalize(Normal); //归一化法线向量
vec3 lightDir = normalize(lightPos - worldPos); //归一化 灯光方向

float diff = max(dot(norm, lightDir), 0.0); //求夹角
vec3 diffuse = diff * lightColor; //乘以灯光颜色

3,镜面(Specular)光照

模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

ea3c4cd0cf42b8c2974dd76d97a13d57.png
镜面光照

和漫反射光照一样,镜面光照也是依据光的方向向量和物体的法向量来决定的,但是它也依赖于观察方向,例如玩家是从什么方向看着这个片段的。

我们通过反射法向量周围光的方向来计算反射向量。然后我们计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光。

伪代码参考:

uniform vec3 viewPos;              //摄像机位置  观察方向

float specularStrength = 0.5; //镜面高光强度

vec3 viewDir = normalize(viewPos - worldPos); // 摄像机方向
vec3 reflectDir = reflect(-lightDir, norm); //反射向量方向

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); //求夹角 算镜面高光权重
vec3 specular = specularStrength * spec * lightColor; //强度*权重*颜色

4,合并光照效果

伪代码参考:

vec3 result = (ambient + diffuse + specular) * objectColor;   //物体颜色*
FragColor = vec4(result, 1.0);
6f4965d7aecbf9a6fb9a2ff428adba4f.png
冯氏光照模型

二,光源类型

1,平行光

当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论相对物体或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为平行光/定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的,平行光可以模拟太阳光。

cd7ebc7cc02cc55265968ed8ce6e327e.png
定向光示意图

因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的,因此对场景中每个物体的光照计算将会是类似的。

伪代码参考:

//定义一个光线方向向量而不是位置向量来模拟一个定向光
//着色依然为冯氏模型

//对light.direction向量取反。我们目前使用的光照计算需求一个从片段至光源的光线方向
vec3 lightDir = normalize(-light.direction);

//根据方向 采用冯氏光照模型 求出最后的结果
result = ambient +diffuse +specular

2,点光源

点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。例如灯泡和火把,它们都是点光源。

ac05cec237cf50c34dd7b87f161cfe62.png
点光源示意图

衰减

在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了;

下面这个公式根据片段距光源的距离计算了衰减值,之后我们会将它乘以光的强度向量:

f8265f2935e36732924f7de1c0274d99.png
d:代表了片段距光源的距离;
Kc:常数项 ,常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度;
Kl:一次项,一次项会与距离值相乘,以线性的方式减少强度;
Kq:二次项,二次项会与距离的平方相乘,让光源以二次递减的方式减少强度,二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了;

由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:

8d9a5d458604ea5dae43a7816f9da341.png
点光源强度随距离衰减

选择适当的值

下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。

343025ffd591bf419072fb29802c1aab.png
取值参考

伪代码参考:

//着色器
//声明所需的变量 常数项 一次项以及二次项
uniform float constant;
uniform float linear;
uniform float quadratic;

//求出 片段与灯光的距离
float distance = length(light.position - FragPos);
//根据距离算出衰减
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

//计算点光源的漫反射与镜面高光 采用冯氏光照模型
//最后乘上衰减因子 算出最终光照
diffuse *= attenuation;
specular *= attenuation;

//求出最后结果
result = ambient +diffuse +specular

//渲染逻辑端
//在渲染端 传入适当的值 这里选取光源影响距离 50 作为参考
pointlightingShader.setFloat("light.constant", 1.0f);
pointlightingShader.setFloat("light.linear", 0.09f);
pointlightingShader.setFloat("light.quadratic", 0.032f);

3,聚光灯

聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。

OpenGL中聚光灯是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:

f5cc46b80042c6f4b4e582b2817d2969.png
聚光灯示意图
<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值