OpenGL学习记录:光照


光照篇

终于是来到了光照篇,这一章会比上一章更加复杂一些,代码量也会增加很多,屏住呼吸,我们一起踏上旅程吧~~~!

首先我们要对之前的代码做一些修改,之前加载纹理的代码写得太丑了,我们这里把它写成一个加载、生成纹理的方法:

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
unsigned int LoadImageToGPU(const char* filename,GLint internalFormat,GLenum format,int textureSlot) 
{
unsigned int TexBuffer;
glGenTextures(1, &TexBuffer);
glActiveTexture(GL_TEXTURE0 + textureSlot);
glBindTexture(GL_TEXTURE_2D, TexBuffer);

int width, height, nrChannel;
unsigned char* data = stbi_load(filename, &width, &height, &nrChannel, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Load Image Failed" << std::endl;
}
stbi_image_free(data);

return TexBuffer;
}

//然后在main函数里bind上纹理
unsigned int TexbufferA;
TexbufferA = LoadImageToGPU("container.jpg", GL_RGB, GL_RGB, 0);
unsigned int TexBufferB;
TexBufferB = LoadImageToGPU("awesomeface.png", GL_RGBA, GL_RGBA, 1);

//在渲染函数里面把纹理送给纹理采集器
myShader->use();
glUniform1i(glGetUniformLocation(myShader->ID, "texture1"), 0);
glUniform1i(glGetUniformLocation(myShader->ID, "texture2"), 1);

颜色

我们可以直接给光源一个三维向量,这样就可以模拟出这个光源的颜色,实际上这三个数值就对应了它的RBG值。颜色和颜色的叠加直接用向量乘法实现就行。


构建光照环境

顶点着色器中我们不需要什么变化,我们只需要在片段着色器中做修改就可以了,定义两个光照的uniform:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#version 330 core                                      
in vec4 vertexColor;
in vec2 TexCoord;

out vec4 FragColor;

uniform sampler2D texture1;
uniform sampler2D texture2;
uniform vec3 objColor;
uniform vec3 ambientColor;

void main()
{
//FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
FragColor = vec4(objColor * ambientColor, 1.0) * mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

然后配置uniform:

1
2
glUniform3f(glGetUniformLocation(myShader->ID, "objColor"), 1.0f, 0.5f, 0.3f);
glUniform3f(glGetUniformLocation(myShader->ID, "ambientColor"), 1.0f, 1.0f, 1.0f);

这样就可以得到加了颜色的笑脸方块:


Phong光照模型

现实世界的光照太过复杂,很难直接去计算,在图形学中大部分都是用经验模型去模拟现实的光照,因为只要“看起来是对的,那就是对的”。现在我们就用OpenGL实现一个基础的Phong光照。冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照,将这三个叠加,就得到了我们想模拟的现实中的光照。

漫反射光照

计算漫反射光照最重要的就是角度,光的入射角度与法向量的夹角就可以反映出在这个点的光的强度,也就是这束光对这个片段的颜色影响大小。我们要uniform一个光源的位置,然后用物体颜色点去减去光源点,就可以的到这个点的入射光线,然后再用入射光线乘法相向量,就得到了我们想要的diffuse。

先来看看着色器:

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
48
49
50
51
52
53
54
55
56
57
58
59
//顶点着色器
#version 330 core
layout(location = 0) in vec3 aPos;
//layout(location = 1) in vec3 aColor;
//layout(location = 2) in vec2 aTexCoord;
layout(location = 3) in vec3 aNormal;//存物体法向量

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;

//out vec4 vertexColor;
//out vec2 TexCoord;
out vec3 FragPos;//把物体表面的坐标点输出到片段着色器
out vec3 Normal;
void main()
{
gl_Position = projMat * viewMat * modelMat * vec4(aPos, 1.0);
FragPos = vec3(modelMat * vec4(aPos, 1.0f));//物体表面像素坐标
Normal = mat3(modelMat) * aNormal;//物体的法向量
//vertexColor = vec4(aColor.xyz,1.0);
//TexCoord = aTexCoord;
}

//片段着色器
#version 330 core
//in vec4 vertexColor;
//in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;

out vec4 FragColor;

//uniform sampler2D texture1;
//uniform sampler2D texture2;
uniform vec3 objColor;//物体光
uniform vec3 ambientColor;//环境光
uniform vec3 lightPos;
uniform vec3 lightColor;

void main()
{
//FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
vec3 lightDir = normalize(lightPos - FragPos);
vec3 diffuse = max(dot(lightDir , Normal),0) * lightColor ;//一定要避免出现负值,不然会对镜面反射光等造成影响
FragColor = vec4((diffuse + ambientColor) * objColor, 1.0);
}

//老规矩更改Attribute,因为要去挖法向量
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(3);

//用Uniform去设置片段着色器里的变量
glUniform3f(glGetUniformLocation(myShader->ID, "objColor"), 1.0f, 0.5f, 0.3f);//设置物体颜色
glUniform3f(glGetUniformLocation(myShader->ID, "ambientColor"), 1.0f, 1.0f, 1.0f);//设置环境光
glUniform3f(glGetUniformLocation(myShader->ID, "lightPos"), 10.0f, 10.0f, 5.0f);//设置灯的位置
glUniform3f(glGetUniformLocation(myShader->ID, "lightColor"), 1.0f, 1.0f, 1.0f);//设置光的颜色

然后就可以看到我们单独做出的白光打在立方体上的漫反射光照:

总结一下漫反射光照的计算:dot(lightDir , Normal)* lightColor

镜面反射

镜面反射需要考虑我们的眼睛的所在位置,也就是视线方向,我们通过入射光向量和入射点法向量来计算反射向量,这个用OpenGL的reflect API就可以做到。然后我们计算反射光向量和视线方向的角度差,这个直接用点乘去实现,如果夹角越小,那么镜面光的影响就会越大(我们的眼睛看到的反射光就更多,就感觉更亮)。它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光,这和真实物理世界里的现象是一样的。

我们同样需要uniform一个camera的位置,然后由这个去得到我们的视角方向,顶点着色器没有任何改变,片段着色器如下:

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
#version 330 core                                      
//in vec4 vertexColor;
//in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;

out vec4 FragColor;

//uniform sampler2D texture1;
//uniform sampler2D texture2;
uniform vec3 objColor;
uniform vec3 ambientColor;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 cameraPos;

void main()
{
//FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
vec3 lightDir = normalize(lightPos - FragPos);

vec3 reflectDir = reflect(-lightDir , Normal);//反射光向量
vec3 viewDir = normalize(cameraPos - FragPos);//视角方向
vec3 specular = pow(max(dot(reflectDir , viewDir),0),32) * lightColor;//高光程度,核心代码

vec3 diffuse = max(dot(lightDir , Normal),0) * lightColor ;

FragColor = vec4((diffuse + ambientColor + specular) * objColor, 1.0);
//环境光漫反射光高光叠加,计算Phong光照模型
}

这样我们就完全实现了Phong光照模型,在某一个特定的角度你可以看到镜面高光:

是不是很神奇?模拟出了你在现实世界中的情况,某一个角度可看到镜面反射的高光,这仅仅只是两个向量的点积带来的,一个反射光线向量,一个视角方向。

总结一下高光反射的计算:(dot(reflectDir , viewDir) ^ specular Amount ) * lightColor。

Phong光照模型:(环境光+高光反射+漫反射光) * 物体颜色


材质

在现实中不同的材质对于光照的反射效果是不同的。这里我们就去做一个材质属性,去模拟不同的材质对光照的效果。

我们先做一个Material的材质结构体,我们让他能在最后对关照的效果做一些调整:

1
2
3
4
5
6
7
8
9
10
11
12
struct Material{
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;

//直接在之前的结果上乘一个系数就行,让我们做的材质属性可以造成影响
vec3 specular = material.specular * pow(max(dot(reflectDir , viewDir),0),32) * lightColor;
vec3 diffuse = material.diffuse * max(dot(lightDir , Normal),0) * lightColor ;
vec3 ambient = material.ambient * ambientColor;

然后在main函数里面给uniform赋值就可以了;

1
2
3
4
glUniform3f(glGetUniformLocation(myShader->ID, "material.ambient"), 1.0f, 1.0f, 1.0f);
glUniform3f(glGetUniformLocation(myShader->ID, "material.diffuse"), 1.0f, 1.0f, 1.0f);
glUniform3f(glGetUniformLocation(myShader->ID, "material.specular"), 1.0f, 1.0f, 1.0f);
glUniform1f(glGetUniformLocation(myShader->ID, "material.shininess"), 32.0f);

然后我们来做一个class,把这个属性封装成一个类。因为直接这样去赋值太麻烦,而且也不直观,我们直接做一个材质类,在构造函数里面就把这些属性的值给赋了,我们在创建一个材质时就可以直接对于这个材质赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Material
{
public:
Shader* shader;
glm::vec3 diffuse;
glm::vec3 specular;
glm::vec3 ambient;
float shininess;

Material(Shader* _shader, glm::vec3 _diffuse, glm::vec3 _specular, glm::vec3 _ambient, float _shininess);
};

Material::Material(Shader* _shader, glm::vec3 _diffuse, glm::vec3 _specular, glm::vec3 _ambient, float _shininess) :
shader(_shader),
diffuse(_diffuse),
specular(_specular),
ambient(_ambient),
shininess(_shininess)
{

}

然后我们在主函数中:

1
2
3
4
5
Material* material = new Material(myShader,
glm::vec3(1.0f,1.0f,1.0f),
glm::vec3(1.0f,1.0f,1.0f),
glm::vec3(1.0f,1.0f,1.0f),
32.0f);

然后为了避免在uniform赋值的时候要把三个向量拆开,我们再去在shader.h里面做一个SetUniform的方法:

1
2
3
4
5
6
7
8
9
void Shader::SetUniform3f(const char* paramNameString, glm::vec3 param)
{
glUniform3f(glGetUniformLocation(ID, paramNameString), param.x, param.y, param.z);
}

void Shader::SetUniform1f(const char* paramNameString, float param)
{
glUniform1f(glGetUniformLocation(ID, paramNameString), param);
}

这样就可以很容易在main函数里面直接把一个vec3和float赋给uniform:

1
2
3
4
myMaterial->shader->SetUniform3f("material.ambient", myMaterial->ambient);
myMaterial->shader->SetUniform3f("material.diffuse", myMaterial->diffuse);
myMaterial->shader->SetUniform3f("material.specular", myMaterial->specular);
myMaterial->shader->SetUniform1f("material.shininess", myMaterial->shininess);

这样我们就有了一个很好的给物体附上材质的结构,不需要在渲染循环里面定义数据,所有的材质属性在这个材质创建出来时就已经赋予了。


光照贴图

在之前我们已经实现了贴图了,在这里我们主要是要重构一下我们上贴图的代码,让他的结构更好,主要的想法是让之前指定数值的方式变一下,让专门的贴图去做镜面光和漫反射光。

现在引入漫反射镜面光贴图(Map)。这允许我们对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。所以之前的片段着色器代码我们要做一些改变,所有的vec3我们都要改成用贴图控制的sampler2D,因此我们还需要额外的纹理贴图输入。所以顶点着色器也要大改,首先要新增location,还需要给片段着色器输出贴图坐标。我们首先把之前加载的两个纹理全部取消,让立方体不上任何贴图,然后我们来改我们的代码。。。要改的地方太多,搞了我一节课。。。直接上改动了的代码吧:

顶点着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#version 330 core 								
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;

uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;

out vec3 FragPos;
out vec3 Normal;
out vec2 texCoord;
void main()
{
gl_Position = projMat * viewMat * modelMat * vec4(aPos.xyz, 1.0);
FragPos = (modelMat * vec4(aPos.xyz, 1.0)).xyz;
Normal = mat3(transpose(inverse(modelMat))) * aNormal;
texCoord = aTexCoord;
}

顶点着色器重新定义之后就直接导致了Attribute要改变:

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

片段着色器:

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
#version 330 core                                         
in vec3 FragPos;
in vec3 Normal;
in vec2 texCoord;

struct Material{
vec3 ambient;
sampler2D diffuse;
sampler2D specular;
float shininess;
};
uniform Material material;
uniform vec3 objColor;
uniform vec3 ambientColor;
uniform vec3 lightPos;
uniform vec3 lightColor;
uniform vec3 cameraPos;

out vec4 FragColor;

void main()
{
vec3 lightDir = normalize(lightPos - FragPos);

vec3 reflectDir = reflect(-lightDir , Normal);
vec3 viewDir = normalize(cameraPos - FragPos);

//把之前数值的影响改变为贴图的采样
vec3 specular = texture(material.specular,texCoord).rgb * pow(max(dot(reflectDir , viewDir),0),32) * lightColor;
vec3 diffuse = texture(material.diffuse,texCoord).rgb * max(dot(lightDir, Normal),0) * lightColor ;
vec3 ambient = texture(material.diffuse,texCoord).rgb * ambientColor;

FragColor = vec4((diffuse + ambient + specular) * objColor, 1.0);
}

shader:

1
2
3
4
5
6
7
8
9
10
enum Slot
{
DIFFUSE,
SPECULAR
};

void use();
void SetUniform3f(const char* paramNameString,glm::vec3 param);
void SetUniform1f(const char* paramNameString, float param);
void SetUniform1i(const char* paramNameString, int slot);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Shader::SetUniform3f(const char* paramNameString, glm::vec3 param)
{
glUniform3f(glGetUniformLocation(ID, paramNameString), param.x, param.y, param.z);
}

void Shader::SetUniform1f(const char* paramNameString, float param)
{
glUniform1f(glGetUniformLocation(ID, paramNameString), param);
}

void Shader::SetUniform1i(const char* paramNameString, int slot)
{
glUniform1i(glGetUniformLocation(ID, paramNameString), slot);
}

main:

1
2
3
4
5
6
7
8
9
10
Material* myMaterial = new Material(myShader,
LoadImageToGPU("container2.png",GL_RGBA,GL_RGBA,Shader::DIFFUSE),
LoadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA, Shader::SPECULAR),
glm::vec3(1.0f,1.0f,1.0f),
64.0f);

myMaterial->shader->SetUniform1i("material.diffuse", Shader::DIFFUSE);
myMaterial->shader->SetUniform1i("material.specular", Shader::SPECULAR);
myMaterial->shader->SetUniform3f("material.ambient", myMaterial->ambient);
myMaterial->shader->SetUniform1f("material.shininess", myMaterial->shininess);

搞定了,是不是有点累?说实话有点烦,这东西搞了我一节课课。。。不说了,回去看我小ig的比赛了,要是输了这周末狂写码!!!

这就是加上了镜面光贴图后的效果,你可以看到金属一样的反光:


光源

在这里我们会去学习光源,分别对平行光、点光源和聚光灯进行实现。在之前我们做了一个Material的class,我们可以很方便地去使用材质,现在我们也来做一个Light类,以便我们去做出各种各样的灯光,然后去使用它。之前我们只是很简单地去指定灯的颜色和位置,现在我们来做一个大工程吧~~

平行光

首先去做一个平行光的类:

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
class LightDirectional
{
public:
LightDirectional(Shader* _shader, glm::vec3 _angles, glm::vec3 _color = glm::vec3(1.0f, 1.0f, 1.0f));

Shader* shader;

glm::vec3 position;
glm::vec3 angles;
glm::vec3 direction = glm::vec3(0, 0, 1.0f);
glm::vec3 color;

void UpdateDirection();//这个函数用于我们方便去调光的方向做测试
};

LightDirectional::LightDirectional(Shader* _shader,glm::vec3 _angles,glm::vec3 _color):
shader(_shader),
angles(_angles),
color(_color)
{
UpdateDirection();
}

void::LightDirectional::UpdateDirection() {
direction = glm::vec3(0, 0, 1.0f);
direction = glm::rotateZ(direction, angles.z);
direction = glm::rotateX(direction, angles.x);
direction = glm::rotateY(direction, angles.y);
direction = -1.0f * direction;
}

之前我们做的光的方向vec3 lightDir = normalize(lightPos - FragPos),其实只是点光源的计算方法而已,并且这种计算方法还有问题。我们现在应该直接喂给光的方向,这样才是真正的平行光,于是在片段着色器里面,我们uniform一个lightDir,然后直接在main里面给它赋值:

1
2
3
4
5
6
7
8
9
10
//vec3 lightDir = normalize(lightPos - FragPos);
//因为是平行光,我们不再需要去根据与物体像素点的相交计算光的方向,直接给出就好
vec3 reflectDir = reflect(-lightDir , Normal);

//main
LightDirectional light = LightDirectional(myShader, glm::vec3(glm::radians(45.0f), 0, 0));

light.shader->SetUniform3f("lightColor", light.color);
light.shader->SetUniform3f("lightDir", light.direction);
//在main函数里面我们只需要去给之前的方向和颜色赋值就好,

这样一来其实和之前的不一样的地方就只有:增加了一个平行光的类,直接给出lightDir的值不让他去和物体像素位置做计算。效果上就是只有一个方向有光,我们从45°打,就只有上面和前面有光:

点光源

点光源和平行光不一样的地方就是,他不管角度,只管位置,但是其他地方他们都十分像。首先我们要把之前的vec3 lightDir = normalize(lightPos - FragPos)打开,这里需要正常地去计算光的方向,这也和我们最开始做的光一样(一开始我们做的光其实就是一个点光源地效果),直接看点光源类地代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LightPoint
{
public:
LightPoint(Shader* _shader, glm::vec3 _position, glm::vec3 _color = glm::vec3(1.0f, 1.0f, 1.0f));

Shader* shader;
glm::vec3 position;
glm::vec3 color;

};

LightPoint::LightPoint(Shader* _shader, glm::vec3 _position, glm::vec3 _color):
shader(_shader),
position(_position),
color(_color)
{

}

就这么简单,点光源就出来了:

但这个和现实差别很大,远处的箱子所受到地光照居然和近处是一样的,这里我们就需要做一个光照衰减。我们直接套用别人的公式就行了,用一个二次多项式去做衰减:

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//fragment
struct LightPoint{
float constant;
float linear;
float quadratic;
};
uniform LightPoint lightPoint;

//计算衰减值
float dist = length(lightPos - FragPos);
float attenuation = 1.0 / (lightPoint.constant + lightPoint.linear * dist + lightPoint.quadratic * (dist * dist));

//衰减值作用在漫反射和高光反射上面,因为只有这两个是被光源影响的
FragColor = vec4((ambient + (diffuse + specular) * attenuation) * objColor, 1.0);

初始化三个计算参数,main里面赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//.cpp
LightPoint::LightPoint(Shader* _shader, glm::vec3 _position, glm::vec3 _color):
shader(_shader),
position(_position),
color(_color)
{
constant = 1.0f;
linear = 0.09f;
quadratic = 0.032f;
}

//main
lightpoint.shader->SetUniform3f("lightPos", lightpoint.position);
lightpoint.shader->SetUniform3f("lightColor", lightpoint.color);
lightpoint.shader->SetUniform1f("lightPoint.constant", lightpoint.constant);
lightpoint.shader->SetUniform1f("lightPoint.linear", lightpoint.linear);
lightpoint.shader->SetUniform1f("lightPoint.quadratic", lightpoint.quadratic);

效果如下:

这下可以很明显地看出,远一点的箱子没有被照得那么亮了。

聚光灯

和舞台上面的聚光灯一样,它只朝一个特定方向而不是所有方向照射光线,只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。在实现这个灯时,我们要给出这个灯的最小照射cos值,然后当某个片元与光源的向量夹角小于了这个最大值,那么这个片元就无法被照射出。

首先我们需要计算出一个片元与光源的向量与光源方向的夹角的cos值,然后把这个值与最小的cos进行比较,如果小,就不去计算这个片元被光的照射。需要注意的是因为我们想得到的是cos值,所以我们要注意把得到的向量转换为单位向量。然后为了处理边缘太硬的问题,我们再设置一个内圈然后在这个环里面去做光由亮变暗的渐变:

1
2
3
4
5
6
7
8
9
10
11
12
13
float cosTheta = dot(normalize(FragPos-lightPos),-1 * lightDirUniform);
float spotIndex;

if(cosTheta>lightSpot.cosMinInner){
spotIndex = 1.0f;
}
else if(cosTheta>lightSpot.cosMinOuter){
spotIndex = (cosTheta - lightSpot.cosMinOuter) / (lightSpot.cosMinInner - lightSpot.cosMinOuter);
}
else{
spotIndex = 0;
}
FragColor = vec4((ambient + diffuse + specular) * spotIndex * objColor, 1.0);

创建一个lightSpot类去在创建灯时就初始化它的位置、中心光照射方向、颜色,照射最大范围(最小cos值)。

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
class LightSpot
{
public:
LightSpot(Shader* _shader, glm::vec3 _position, glm::vec3 _angles, float range, glm::vec3 _color = glm::vec3(1.0f, 1.0f, 1.0f));

Shader* shader;
glm::vec3 position;
glm::vec3 angles;
glm::vec3 direction = glm::vec3(0, 0, 1.0f);
glm::vec3 color;

float cosMinInner;
float cosMinOuter = cosMinInner - 0.05f;

void UpdateDirection();
};

LightSpot::LightSpot(Shader* _shader, glm::vec3 _position , glm::vec3 _angles,float _range,glm::vec3 _color):
shader(_shader),
position(_position),
angles(_angles),
color(_color),
cosMinInner(_range)
{
UpdateDirection();
}

void LightSpot::UpdateDirection()
{
direction = glm::vec3(0, 0, 1.0f);
direction = glm::rotateZ(direction, angles.z);
direction = glm::rotateX(direction, angles.x);
direction = glm::rotateY(direction, angles.y);
direction = -1.0f * direction;
}

main:

1
2
3
4
5
6
7
LightSpot lightSpot = LightSpot(myShader, glm::vec3(0.0f, 5.0f, 0.0f), glm::vec3(glm::radians(90.0f), 0, 0), 0.9f);

lightSpot.shader->SetUniform3f("lightPos", lightSpot.position);
lightSpot.shader->SetUniform3f("lightColor", lightSpot.color);
lightSpot.shader->SetUniform3f("lightDirUniform", lightSpot.direction);
lightSpot.shader->SetUniform1f("lightSpot.cosMinInner", lightSpot.cosMinInner);
lightSpot.shader->SetUniform1f("lightSpot.cosMinOuter", lightSpot.cosMinOuter);

这样就可以得到很好的聚光灯效果:


多光源

现在我们来做多光源,我们需要把之前的代码重构,用一个计算光照的函数去计算所有的光照效果,然后作为颜色的输出。在片段着色器里面我们分别用三个函数去计算,然后再去叠加:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
out vec4 FragColor;	

vec3 CalLightDirectional(LightDirectional light, vec3 uNormal, vec3 dirToCamera){
//diffuse = dot(光入射向量,法向量) * 灯颜色
float diffuseIntensity = max(dot(light.dirToLight , uNormal),0);
vec3 diffuseColor = diffuseIntensity * light.color * texture(material.diffuse,texCoord).rgb;

//specular = dot(光的出射向量,视线方向向量)^高光度
float specularIntensity = pow(max(dot(normalize(reflect(-light.dirToLight,uNormal)),dirToCamera),0),material.shininess);
vec3 specularColor = specularIntensity * light.color * texture(material.specular, texCoord).rgb;

vec3 result = diffuseColor + specularColor;
return result;
}

vec3 CalLightPoint(LightPoint light, vec3 uNormal, vec3 dirToCamera){

float dist = length(light.pos - FragPos);
float attenuation = 1 / (light.constant + light.linear * dist + light.quadratic * dist * dist);

float diffuseIntensity = max(dot(normalize(light.pos - FragPos), uNormal),0) * attenuation;
vec3 diffuseColor = diffuseIntensity * light.color * texture(material.diffuse,texCoord).rgb;

float specularIntensity = pow(max(dot(-normalize(reflect(light.pos - FragPos,uNormal)),dirToCamera),0),material.shininess);
vec3 specularColor = specularIntensity * light.color * texture(material.specular, texCoord).rgb;

vec3 result = (diffuseColor + specularColor) * attenuation;
return result;
}

vec3 CalLightSpot(LightSpot light, vec3 uNormal, vec3 dirToCamera){

float diffuseIntensity = max(dot(normalize(light.pos - FragPos), uNormal),0);
vec3 diffuseColor = diffuseIntensity * light.color * texture(material.diffuse,texCoord).rgb;

float specularIntensity = pow(max(dot(-normalize(reflect(light.pos - FragPos,uNormal)),dirToCamera),0),material.shininess);
vec3 specularColor = specularIntensity * light.color * texture(material.specular, texCoord).rgb;

float cosTheta = dot(normalize(FragPos-light.pos),-1 * light.dirToLight);
float spotIndex;

if(cosTheta>light.cosMinInner){
spotIndex = 1.0f;
}
else if(cosTheta>light.cosMinOuter){
spotIndex = (cosTheta - light.cosMinOuter) / (light.cosMinInner - light.cosMinOuter);
}
else{
spotIndex = 0;
}
vec3 result = (diffuseColor + specularColor) * spotIndex * objColor;

return result;
}

void main()
{
vec3 finalResult = vec3(0.0f,0.0f,0.0f);
vec3 uNormal = normalize(Normal);
vec3 dirToCamera = normalize(cameraPos - FragPos);

finalResult += CalLightDirectional(lightDirectional, uNormal, dirToCamera);

finalResult += CalLightPoint(lightP0, uNormal, dirToCamera);
finalResult += CalLightPoint(lightP1, uNormal, dirToCamera);
finalResult += CalLightPoint(lightP2, uNormal, dirToCamera);
finalResult += CalLightPoint(lightP3, uNormal, dirToCamera);

finalResult += CalLightSpot(lightS, uNormal, dirToCamera);

FragColor = vec4(finalResult, 1.0);
}

在main函数里面进行赋值:

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
//创建多光源
LightDirectional lightD = LightDirectional(myShader, glm::vec3(glm::radians(90.0f), 0, 0));

LightPoint lightP0 = LightPoint(myShader, glm::vec3(1.0f, 0.0f, 0.0f),glm::vec3(1.0f,0.0f,0.0f));
LightPoint lightP1 = LightPoint(myShader, glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0));
LightPoint lightP2 = LightPoint(myShader, glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(0, 0.0f, 1.0f));
LightPoint lightP3 = LightPoint(myShader, glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f));

LightSpot lightS = LightSpot(myShader, glm::vec3(0, 5.0f, 0), glm::vec3(glm::radians(90.0f), 0, 0), 0.9f);

lightD.shader->SetUniform3f("lightDirectional.color", lightD.color);
lightD.shader->SetUniform3f("lightDirectional.dirToLight", lightD.direction);

lightP0.shader->SetUniform3f("lightP0.pos", lightP0.position);
lightP0.shader->SetUniform3f("lightP0.color", lightP0.color);
lightP0.shader->SetUniform1f("lightP0.constant", lightP0.constant);
lightP0.shader->SetUniform1f("lightP0.linear", lightP0.linear);
lightP0.shader->SetUniform1f("lightP0.quadratic", lightP0.quadratic);

lightP1.shader->SetUniform3f("lightP1.pos", lightP1.position);
lightP1.shader->SetUniform3f("lightP1.color", lightP1.color);
lightP1.shader->SetUniform1f("lightP1.constant", lightP1.constant);
lightP1.shader->SetUniform1f("lightP1.linear", lightP1.linear);
lightP1.shader->SetUniform1f("lightP1.quadratic", lightP0.quadratic);

lightP2.shader->SetUniform3f("lightP2.pos", lightP2.position);
lightP2.shader->SetUniform3f("lightP2.color", lightP2.color);
lightP2.shader->SetUniform1f("lightP2.constant", lightP2.constant);
lightP2.shader->SetUniform1f("lightP2.linear", lightP2.linear);
lightP2.shader->SetUniform1f("lightP2.quadratic", lightP2.quadratic);

lightP3.shader->SetUniform3f("lightP3.pos", lightP3.position);
lightP3.shader->SetUniform3f("lightP3.color", lightP3.color);
lightP3.shader->SetUniform1f("lightP3.constant", lightP3.constant);
lightP3.shader->SetUniform1f("lightP3.linear", lightP3.linear);
lightP3.shader->SetUniform1f("lightP3.quadratic", lightP3.quadratic);

lightS.shader->SetUniform3f("lightS.pos", lightS.position);
lightS.shader->SetUniform3f("lightS.color", lightS.color);
lightS.shader->SetUniform3f("lightS.dirToLight", lightS.direction);
lightS.shader->SetUniform1f("lightS.cosMinInner", lightS.cosMinInner);
lightS.shader->SetUniform1f("lightS.cosMinOuter", lightS.cosMinOuter);

这样就得到了多光源照射的效果:


好了,第二章也学习完了,现在OpenGL的基本使用差不多可以了,我想先跳过模型加载的章节,直接进入高级OpenGL和高级光照,最后我们再来做模型加载。


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC