LearnOpenGL-笔记-其三
LearnOpenGL-笔记-其三
在之前的章节中我们学习了基本的窗口构建方法、着色器的定义与使用以及摄像机的构建,而从今天这个大章节开始我们要来学习光照有关的知识。
颜色
现实世界中有无数种颜色,每一个物体都有它们自己的颜色。我们需要使用(有限的)数值来模拟真实世界中(无限)的颜色,所以并不是所有现实世界中的颜色都可以用数值来表示的。然而我们仍能通过数值来表现出非常多的颜色,甚至你可能都不会注意到与现实的颜色有任何的差异。颜色可以数字化的由红色(Red)、绿色(Green)和蓝色(Blue)三个分量组成,它们通常被缩写为RGB。仅仅用这三个值就可以组合出任意一种颜色。
在OpenGL中我们只需要将两个表示颜色的RGB(或者RGBA值)进行相乘就可以得到一个新的颜色,也就代表了我们现实中的颜色混合。
这是基本的颜色理论,让我们来看一下在场景中创建一个可以表现出不同颜色的光源,同样的,我只展示与之前代码不同的部分:
// lighting
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
首先当然是定义光源的具体位置。
Shader lightingShader("1.colors.vs", "1.colors.fs");
Shader lightCubeShader("1.light_cube.vs", "1.light_cube.fs");
这是两个光源的着色器的实例化,那么着色器的定义具体是什么呢?
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
这是1.colors.vs,内容并没有新鲜之处。
#version 330 core
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
FragColor = vec4(lightColor * objectColor, 1.0);
}
这是1.color.fs,主要就是将uniform变量光源的颜色和物体本身的颜色进行一个相乘之后输出。
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
这是1.light_cube.vs,依然只是进行一个MVP变换。
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
接收1.color.fs发送的颜色。
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
// view/projection transformations
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
lightingShader.setMat4("projection", projection);
lightingShader.setMat4("view", view);
// world transformation
glm::mat4 model = glm::mat4(1.0f);
lightingShader.setMat4("model", model);
// render the cube
glBindVertexArray(cubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
// also draw the lamp object
lightCubeShader.use();
lightCubeShader.setMat4("projection", projection);
lightCubeShader.setMat4("view", view);
model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f)); // a smaller cube
lightCubeShader.setMat4("model", model);
然后就是在render loop中启用我们的着色器,我们给定着色器中的uniform变量值。效果如下:
长得有些不明显,但是出效果就行。
Phong Lighting Model
接下来要接触的就是我们光照里最基础的模型:冯模型。
这个模型认为光照主要是由三部分组成的:
现在我们来逐个模拟这三部分的光照。
环境光是一个非常麻烦的事,因为所谓的环境光的定义应该是所有除了本物体以外的物体发射过来或者反射过来的光的总和,在实际的环境中非常难以想象和模拟。在冯模型中,我们不如直接极简化,直接把环境光定义为一个给定的常量即可。
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
就像这样,我们给定一个环境光常数,然后直接用定义的光照颜色乘以常数得到环境光的强度。
漫反射则需要我们去进行一个计算了,如果大家都还记得初中学过的光的反射的话应该也不难理解:
简单地说,我们想要得到漫反射的效果就需要一个法向量和一个光线向量,这个光线向量指的是作为光源的点和平面上的一点的向量差。
对于我们的立方体来说,得到每个面的法向量只需要知道顶点坐标后设计进行叉乘即可,当然,我们的例子比较简单,所以可以直接把法向量写进我们的着色器中,值得注意的是我们会在顶点着色器中给出法向量,然后需要将这些法向量给到片元着色器进行光照的计算。
现在我们有了立方体每个面的法向量、顶点的位置向量,我们还需要光源的位置以及片元的位置;获得之后我们就可以去计算漫反射光照了:
接下来是我们的镜面高光:
相比起漫反射,镜面反射还会突出强调我们的眼睛的观察角度,所以具体的计算方式也不再是简单的法向量和光线向量进行点乘:
这样我们就实现了一个光照模型为冯模型的场景了,让我们看看代码里的具体实现:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
gl_Position = projection * view * vec4(FragPos, 1.0);
}
#version 330 core
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main()
{
// ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// specular
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;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
}
这是我们2.2basic_lighting.vs和fs的内容,vs里除了常规的MVP变换以外主要贡献就是传递了片元位置和法线矩阵,这里法线矩阵的定义值得一提:
片元着色器中就主要是我们之前提到的冯模型光照的具体实现了,将我们的环境光、漫反射光和镜面高光全部相加就得到我们的光照了。效果如下:
材质
材质的意思应该算非常好理解的了,就像我们的衣服有毛皮也有纤维的一样,不同的材质的属性不同,那么对于对于光照的反应也不一样。
我们在片元着色器中定义一个结构体来定义材质的属性:
#version 330 core
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
修改材质后我们需要去更新光照的计算方式来适应材质的写法。
总的来说我们的片元着色器代码如下所示:
#version 330 core
out vec4 FragColor;
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
in vec3 FragPos;
in vec3 Normal;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;
void main()
{
// ambient
vec3 ambient = light.ambient * material.ambient;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(light.position - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = light.diffuse * (diff * material.diffuse);
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = light.specular * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
其实就是把材质和光照都写成结构体的形式,并在其中加入一些属性。