learnOpenGL笔记-基础光照

冯氏光照模型

现实世界中光照极其复杂,而要正确的模拟光照就要基于一定的物理特性。而其中的一个简化的模型称为冯氏光照模型,他主要由三个部分构成,环境光照、漫反射、镜面反射。

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

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

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

环境光照

一般来说,环境光通常是整个环境中各个光源反射共同作用的结果(光源照射到物体后反射的光也可以看做为一个光源),这种情况下的光照称为全局光照。

而在此处我们不希望考虑的那么负复杂,所以会使用一个简化的全局光照模型,即环境光照。我们用一个很小的常量来表示环境光,这实现起来比较容易。

修改片段着色器:

void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}

这里我们为了能看清变化,我们需要修改窗口背景颜色,每个颜色分量乘以0.1。

glClearColor(0.02f, 0.03f, 0.03f, 0.1f);

注意,我们并不把环境光照运用在光源立方体上。可得出渲染结果。

漫反射

漫反射光照使物体与光线照射方向越接近的片段能从光源获得更多的亮度。

为了测量光线与片段接触的角度,需要用的法线向量。我们可知单位向量之间点乘可以得多两向量之间夹角的余弦值,则我们可用光线方向单位向量点乘法向量得到光线与法线的夹角余弦。

当两个单位向量之间的夹角余弦约接近于1,就说明光线越接近垂直方向入射。

则我们需要两个数据:

  • 法线向量:垂直于顶点表面的法向量。

  • 光线单位向量:由光源位置和片段位置之间的差值并归一化得到。

法向量

对于一个顶点的法线,我们可以根据顶点周围的点,每三个点确定一个平面,取各个平面的法线,最后将法线叠加在一起,形成该顶点的法线。例如:

    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

此为一个正方形面,前三个点为位置向量,后三个点为法向量,由于所有顶点都在一个平面上,所以各个顶点的法向量朝向一样。其他各正方形面为相同操作。

由于我们向顶点数组中添加了额外的数据,所以要对顶点着色器进行修改:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

同时还要更新顶点属性指针,但我们只需更新物体的顶点数据,无需为灯的着色器或者属性指针更新数据,但是必须要修改一下定点属性指针的大小。

立方体的定点属性指针更新:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

灯的顶点属性更新(只改大小):

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

对于光照的计算都是在片段着色器中进行的,所以需要将法向量由顶点着色器传入到片段着色器中

out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    Normal = aNormal;
}

在片段着色器中接收顶点着色器传来的法向量;

in vec3 Normal;

计算漫反射

我们已经有了法向量,接下来光源的位置向量以及片段的位置向量。

由于光源是一个静态变量,我们可以简单的将其声明为一个uniform。

uniform vec3 lightPos;

由于光源的位置不会变,我们可以在game loop外更新uniform,使用已定义的光源位置lightPos作为光源位置:

lightingShader.setVec3("lightPos", lightPos);

接下来是片段位置,我们会在世界坐标下进行所有的光照计算,而顶点数组中定义的是局部坐标下的位置,所以需要先将其在顶点着色器中转为世界坐标,即左乘模型矩阵即可。

out vec3 FragPos;  
out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = aNormal;
}

在片段着色器中将顶点着色器中输出的片段位置接收。

in vec3 FragPos;

现在,法向量、光源向量以及片段位置向量都已经有了,接下来就将法向量进行标准化,通过将光源向量以及片段位置向量作差得到光线方向向量,并进行标准化。转换为单位向量。

vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

将得到的法向量与光线方向向量进行点乘,得到夹角的余弦值,其值为0-1之间,夹角越小,结果值越接近于1。然后再与光的颜色相乘,就可以的到漫反射分量。又因为角度大于90度时,点乘结果为负值,而负值对于颜色无意义,则将结果与0做比较,当角度大于90度返回0,也就是无漫反射,全吸收。

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

最后,将环境光与漫反射相加并乘以物体的颜色,由此获得最后的输出颜色。

vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);

镜面反射

镜面反射其实就是高光的表现,镜面反射由光照方向向量、法向量与观察角度有关。镜面反射取决于表面的反射性质,但我们的观察角正好在反射光线周围时,就会产生高光。

我们的观察角度与反射角度之间会产生一个夹角,夹角越小,镜面反射光的作用就越大。

对于观察向量来说,我们可以通过观察者的世界坐标位置与片段位置相减来得到,计算出镜面反射光照强度,最后乘以光源的颜色,并将其与漫反射和环境光相加。

要得到观察者的世界坐标,直接使用摄影机的位置即可。我们通过设置一个uniform,将摄影机的位置传到顶点着色器中。

uniform vec3 viewPos;
lightingShader.setVec3("viewPos", camera.Position);

现在我们已经获得所有需要的变量,可以计算高光强度了。首先,我们定义一个镜面强度变量,给镜面高光一个中等亮度颜色,让它不要产生过度的影响。

float specularStrength = 0.5;

接下来计算视线方向向量以及反射方向向量。

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

最后计算镜面反射分量,在这里要引入一个反光度的变量。

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

可以看到这里加上了一个32次幂,因为在实际生活中我们只要离开一点位置就看不到高光了(或者说是高光只是集中在一个小地方), 而cos(α)的数值太大, 即便偏离高光位置较远还是能看到。因此我们引入反光度来平衡这一问题。

可以看到,如果不引入反光度,高光会在很大一个范围内发生,而但次数越大,高光所发生的范围就越小,也就更符合常理一些了。不同次幂产生不同的效果

最后一件事是将它加入到环境分量和漫反射分量里,并乘以实际物体的颜色。

vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);

最终实现效果:

核心代码

片段着色器

#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 FragPos;

uniform vec3 lightPos;
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 viewPos;

void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    vec3 normal = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(normal,lightDir),0.0);
    vec3 diffuse = diff * lightColor;

    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir,normal);
    float ref = pow(max(dot(reflectDir,viewDir),0.0),32);
    vec3 reflection = ref * lightColor;

    vec3 result = (ambient + diffuse + reflection) * objectColor;
    FragColor = vec4(result, 1.0);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值