0%

【光栅化渲染器】(八)光源

到目前为止我们的渲染管线已经基本为完整了,从模型加载到顶点着色器,再经过裁剪到片元着色器,但在片元着色器中我们目前还只是输出顶点颜色或者纹理值,还没有加入光照计算,这一节我们就来加入光照的计算。最简单的 Blinn Phong 模型我们已经非常熟悉了,所以这一节的重点并不是光照的计算,而是了解三种光源是如何实现的。

1 光源概述

在 RTR 一书中,作者将光源分为三种,分别是:平行光(direction)、点光源(point)和聚光灯(spot)。我们分别来实现这三种光源。

2 准备工作

在实现光源之前我们需要一些函数和变量,首先是在 Global.h 中定义的环境光项:

1
2
// 环境光
const glm::vec3 Ambient = glm::vec3(0.5, 0.5, 0.5);

还有计算反射方向的函数:

1
2
3
4
5
6
// 反射的计算 2n * cos(n,l) - l = r
// lightDir是光指向片元的方向
glm::vec3 reflect(const glm::vec3& lightDir, const glm::vec3& normal) {

return lightDir - 2 * glm::dot(normal, lightDir) * normal;
}

3 平行光

在实现各类光源之前我们先实现一个光源基类,这样其他光源都派生自该基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 光源基类
class Light {
public:
glm::vec3 Position;
glm::vec3 Color;
glm::vec3 Specular;
glm::vec3 Direction;
float Intensity;

virtual ~Light() {
}
Light(
const glm::vec3& pos = glm::vec3(0, 0, 0),
const glm::vec3& color = glm::vec3(1, 1, 1),
const glm::vec3& specular = glm::vec3(1, 1, 1),
const glm::vec3& dir = glm::vec3(0, -1, 0),
const float& i = 1.0f) :
Position(pos),
Color(color),
Specular(specular),
Direction(dir),
Intensity(i)
{}
};

然后是平行光,平行光不需要位置:

1
2
3
4
5
6
7
8
9
10
11
// 平行光,不需要位置
class DirectionLight : public Light {
public:
DirectionLight(
const glm::vec3& dir = glm::normalize(glm::vec3(0, -1, 1)),
const glm::vec3& color = glm::vec3(1, 1, 1),
const glm::vec3& specular = glm::vec3(1, 1, 1),
const float& i = 1.0f) :
Light(glm::vec3(0, 0, 0), color, specular, dir, i)
{}
};

然后我们新建一个 BlinnPhongShader 类继承于基本的 Shader 类,在其中实现平行光的计算,并修改片元着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#pragma once
#ifndef BLINNPHONG_H
#define BLINNPHONG_H
#include "Shader.h"
#include "Light.h"
#include "Material.h"
#include "Camera.h"

class BlinnPhongShader : public Shader {

public:

BlinnPhongShader() = default;
virtual ~BlinnPhongShader() = default;

// 向量都要是单位向量
// ViewDir是片元指向摄像机的方向
static glm::vec3 CalcDirLight(
const DirectionLight& dirLight,
const glm::vec3& worldNormal,
const glm::vec3& worldViewDir,
const glm::vec3& albedo
) {
float diff = max(glm::dot(worldNormal, -dirLight.Direction), 0);

glm::vec3 halfDir = glm::normalize(worldViewDir - dirLight.Direction);
float spec = pow(max(glm::dot(halfDir, worldNormal), 0), currentMat->Gloss);

glm::vec3 diffuse = dirLight.Color * diff * albedo;
glm::vec3 specular = dirLight.Specular * spec;
return (diffuse + specular) * dirLight.Intensity;
}

glm::vec4 FragmentShader(const V2F& v) {
glm::vec3 albedo = texture->Sample2D(v.texcoord) * currentMat->Color;
glm::vec3 worldNormal = glm::normalize(v.normal);
glm::vec3 worldViewDir = glm::normalize(camera->Position - glm::vec3(v.worldPos));

glm::vec3 result = Ambient * albedo;
for (int i = 0; i < dirLtNums; i++)
result += BlinnPhongShader::CalcDirLight(*(dirLights + i), worldNormal, worldViewDir, albedo);

return glm::vec4(result, 1.0);
}
};

#endif

然后在主函数中加入一个平行光:

1
2
3
4
5
6
7
8
9
10
// 平行光源
DirectionLight dir(glm::vec3(0, 0, 1), glm::vec3(1, 1, 1), glm::vec3(1, 1, 1), 1.0);
dirLights = &dir;
dirLtNums = 1;

// 使用光照Shader
BlinnPhongShader shader;
// 加载模型

...

查看效果:

image-20220525112039177

4 点光源

点光源无所谓方向,它向四周均匀发光,光照强度和距离成反比,我们使用常数项、一次项和二次项来控制光照衰减,首先是点光源类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 点光源,不需要方向
class PointLight : public Light {

public:
float Constant; //常数项
float Linear; //一次项
float Quadratic; // 二次项

PointLight(
const glm::vec3& pos = glm::vec3(0, 0, 0),
const glm::vec3& color = glm::vec3(1, 1, 1),
const glm::vec3& specular = glm::vec3(1, 1, 1),
const float& i = 1.0f,
const float& c = 1.0f,
const float& l = 0.09f,
const float& q = 0.032f
) : Light(pos, color, specular, glm::vec3(0, 0, 0), i), Constant(c), Linear(l), Quadratic(q)
{}
};

然后是计算点光源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 计算点光源
static glm::vec3 CalcPtLight(
const PointLight& ptLight,
const glm::vec3& worldPos,
const glm::vec3& worldNormal,
const glm::vec3& worldViewDir,
const glm::vec3& albedo
) {
// 到光源的距离
float distance = glm::distance(worldPos, ptLight.Position);
// 强度衰减因子
float attenuation = 1.0 / (ptLight.Constant + ptLight.Linear * distance +
ptLight.Quadratic * (distance * distance));

glm::vec3 lightDir = glm::normalize(worldPos - ptLight.Position);
float diff = max(glm::dot(worldNormal, -lightDir), 0);

glm::vec3 halfDir = glm::normalize(worldViewDir - lightDir);
float spec = pow(max(glm::dot(halfDir, worldNormal), 0), currentMat->Gloss);

glm::vec3 diffuse = ptLight.Color * diff * albedo;
glm::vec3 specular = ptLight.Specular * spec;

diffuse *= attenuation;
specular *= attenuation;

return (diffuse + specular) * ptLight.Intensity;
}

在片元着色器中加入点光源贡献:

1
2
3
4
5
6
7
8
9
10
11
12
13
glm::vec4 FragmentShader(const V2F& v) {
glm::vec3 albedo = texture->Sample2D(v.texcoord) * currentMat->Color;
glm::vec3 worldNormal = glm::normalize(v.normal);
glm::vec3 worldViewDir = glm::normalize(camera->Position - glm::vec3(v.worldPos));

glm::vec3 result = Ambient * albedo;
for (int i = 0; i < dirLtNums; i++)
result += BlinnPhongShader::CalcDirLight(*(dirLights + i), worldNormal, worldViewDir, albedo);
for (int i = 0; i < ptLtNums; i++)
result += BlinnPhongShader::CalcPtLight(*(ptLights + i), glm::vec3(v.worldPos), worldNormal, worldViewDir, albedo);

return glm::vec4(result, 1.0);
}

然后在主函数中加入一个点光源:

1
2
3
PointLight pt(glm::vec3(1, 1, 1));
ptLights = &pt;
ptLtNums = 1;

查看效果:

image-20220525112022188

5 聚光灯

聚光灯可以理解为手电筒,只有在与手电筒正方向夹角在一定范围内的像素才会被照亮。像素到光源位置的方向与光源正向夹角被称为切光角,切光角在设定范围内的像素,按照点光源的方式计算光照。切光角之外的像素不会被照亮。

image-20220525110724976

为了避免边缘突变,可以设立一个外切光角,在内外切光角之间使用插值乘以计算结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 聚光灯
class SpotLight : public PointLight {

public:
float innerCutOff; // 内切光角范围
float outterCutOff; // 外切光角范围

SpotLight(
const glm::vec3& pos = glm::vec3(0, 0, 0),
const glm::vec3& dir = glm::vec3(0, 0, -1),
const glm::vec3& color = glm::vec3(1, 1, 1),
const glm::vec3& specular = glm::vec3(1, 1, 1),
const float& i = 1.0f,
const float& c = 1.0f,
const float& l = 0.09f,
const float& q = 0.032f,
const float& icut = glm::cos(glm::radians(12.5f)),
const float& ocut = glm::cos(glm::radians(17.5))
) : PointLight(pos, color, specular, i, c, l, q), innerCutOff(icut), outterCutOff(ocut)
{
Direction = dir;
}
};

计算聚光灯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 计算聚光灯,切光角以内按点光源计算
static glm::vec3 CalcSpLight(
const SpotLight& spLight,
const glm::vec3& worldPos,
const glm::vec3& worldNormal,
const glm::vec3& worldViewDir,
const glm::vec3& albedo
) {

glm::vec3 lightDir = glm::normalize(worldPos - spLight.Position);
// 切光角
float theta = glm::dot(lightDir, glm::normalize(spLight.Direction));
// 内外切光角插值系数
float weight = saturate((theta - spLight.outterCutOff) / (spLight.innerCutOff - spLight.outterCutOff));
float intensity = Lerp(0, 1, weight);

// 按点光源计算
float distance = glm::distance(worldPos, spLight.Position);
float attenuation = 1.0 / (spLight.Constant + spLight.Linear * distance +
spLight.Quadratic * (distance * distance));
float diff = max(glm::dot(worldNormal, -lightDir), 0);

glm::vec3 halfDir = glm::normalize(worldViewDir - lightDir);
float spec = pow(max(glm::dot(halfDir, worldNormal), 0), currentMat->Gloss);

glm::vec3 diffuse = spLight.Color * diff * albedo;
glm::vec3 specular = spLight.Specular * spec;

diffuse *= (attenuation * intensity);
specular *= (attenuation * intensity);

return (diffuse + specular) * spLight.Intensity;
}

在片元着色器中加入聚光灯贡献:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
glm::vec4 FragmentShader(const V2F& v) {
glm::vec3 albedo = texture->Sample2D(v.texcoord) * currentMat->Color;
glm::vec3 worldNormal = glm::normalize(v.normal);
glm::vec3 worldViewDir = glm::normalize(camera->Position - glm::vec3(v.worldPos));

glm::vec3 result = Ambient * albedo;
for (int i = 0; i < dirLtNums; i++)
result += BlinnPhongShader::CalcDirLight(*(dirLights + i), worldNormal, worldViewDir, albedo);
for (int i = 0; i < ptLtNums; i++)
result += BlinnPhongShader::CalcPtLight(*(ptLights + i), glm::vec3(v.worldPos), worldNormal, worldViewDir, albedo);
for (int i = 0; i < spLtNums; i++)
result += BlinnPhongShader::CalcSpLight(*(spLights + i), glm::vec3(v.worldPos), worldNormal, worldViewDir, albedo);

return glm::vec4(result, 1.0);
}

在主函数中加入聚光灯:

1
2
3
4
// 聚光灯
SpotLight sp(camera->Position, camera->Front);
spLights = &sp;
spLtNums = 1;

最终的效果:

image-20220525111959282

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。